├── .gitignore ├── README.md ├── app.js ├── bin └── www ├── blockchain_lib ├── conf.js └── lib.js ├── bower.json ├── help_utils ├── base64_util.js └── geth_level_utils.js ├── imgs ├── 创建资产.png ├── 发行资产.png ├── 发行资产详情.png ├── 注册.png ├── 登录.png ├── 账户1交易记录.png ├── 账户1信息.png ├── 账户1发起交易.png ├── 账户1发起交易详情.png ├── 账户1资产列表.png ├── 账户2交易记录.png ├── 账户2信息.png ├── 账户2接收交易详情.png └── 账户2资产列表.png ├── models ├── Users.js ├── asset.js └── transaction.js ├── ng_app ├── .bowerrc ├── css │ ├── font-awesome.min.css │ └── my.css ├── fonts │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── img │ ├── aif.svg │ ├── group-logo.svg │ ├── group.svg │ ├── logo.png │ ├── logo2.png │ ├── logo3.png │ └── logo4.png ├── index.html ├── js │ ├── controllers.js │ ├── directives.js │ ├── interceptors.js │ ├── loginAndRegisterModule.js │ ├── menu.js │ ├── router.js │ └── services.js └── views │ ├── assetCreate.html │ ├── assetDetail.html │ ├── assetIssue.html │ ├── assetList.html │ ├── changePwd.html │ ├── dashboard.html │ ├── login.html │ ├── register.html │ ├── test.html │ ├── transactionDetail.html │ ├── transactionList.html │ ├── transfer.html │ ├── transferConfirm.html │ ├── user.html │ └── verifyPwd.html ├── package-lock.json ├── package.json ├── public └── stylesheets │ └── style.css ├── routes ├── Users.js ├── asset.js ├── index.js ├── password.js ├── token.js └── transaction.js └── views ├── error.jade ├── index.jade └── layout.jade /.gitignore: -------------------------------------------------------------------------------- 1 | #os 2 | .DS_Store 3 | 4 | #ide 5 | .idea/ 6 | *.iml 7 | *.sublime-workspace 8 | 9 | #python 10 | venv/ 11 | __pycache__/ 12 | *.pyc 13 | 14 | #front 15 | node_modules/ 16 | bower_components/ 17 | 18 | #others 19 | .vagrant/ 20 | *.sqlite 21 | *.log 22 | *.bak 23 | nohup.out 24 | /npm-debug.log 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 浙大区块链实验室 2 | --- 3 | 本文将介绍区块链的使用,以帮助读者理解资产创建、发行、交易的整个流程。 4 | ## 准备 5 | 6 | 获取代码 7 | `git clone https://github.com/hyperchaincn/Digital-Wallet.git` 8 | 9 | 配置开发者平台账号信息 10 | * [注册](https://dev.hyperchain.cn)趣链科技开发者平台账号 11 | * 登陆后创建应用 12 | * 进入应用详情,获取AppKey、AppSecret 13 | * 编辑项目目录下`blockchain_lib/conf.js`文件,将AppKey、AppSecret以及账号和密码替换至相应位置 14 | 15 | 配置数据库 16 | * 下载并安装mongodb 17 | * [mongodb安装地址](https://www.mongodb.com/download-center#community) 18 | 19 | 部署项目 20 | * 安装nodejs 21 | * [nodejs安装地址](https://nodejs.org/zh-cn/) 22 | * 安装bower 23 | * [bower安装地址](https://bower.io/) 24 | * 安装依赖并启动 `npm start` 25 | * 端口访问 `localhost:3000` 26 | 27 | 28 | 29 | --- 30 | ## 简介 31 | ``` 32 | 这里我们采用固定合约源码并给出了编译合约生成的的abi(合约源码对应的abi数组)以及bin(合约编译而成的字节码),这里用户也可以自己给定合约源码以及编译生成的bin,abi。 33 | const TOKEN_SOURCE = 'contract Token { address issuer; mapping (address => uint) balances; event Issue(address account, uint amount); event Transfer(address from, address to, uint amount); function Token() { issuer = msg.sender; } function issue(address account, uint amount) { if (msg.sender != issuer) throw; balances[account] += amount; } function transfer(address to, uint amount) { if (balances[msg.sender] < amount) throw; balances[msg.sender] -= amount; balances[to] += amount; Transfer(msg.sender, to, amount); } function getBalance(address account) constant returns (uint) { return balances[account]; } }'; 34 | const TOKEN_BIN = '0x6060604052341561000c57fe5b5b60008054600160a060020a03191633600160a060020a03161790555b5b6101ca806100396000396000f300606060405263ffffffff60e060020a600035041663867904b48114610037578063a9059cbb14610058578063f8b2cb4f14610079575bfe5b341561003f57fe5b610056600160a060020a03600435166024356100a7565b005b341561006057fe5b610056600160a060020a03600435166024356100e6565b005b341561008157fe5b610095600160a060020a036004351661017f565b60408051918252519081900360200190f35b60005433600160a060020a039081169116146100c35760006000fd5b600160a060020a03821660009081526001602052604090208054820190555b5050565b600160a060020a0333166000908152600160205260409020548190101561010d5760006000fd5b600160a060020a0333811660008181526001602090815260408083208054879003905593861680835291849020805486019055835192835282015280820183905290517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360600190a15b5050565b600160a060020a0381166000908152600160205260409020545b9190505600a165627a7a72305820299e9bb6a492d60cb690d97c76ac26d821ff6bba1b863ce1b8720e449789692c0029' 35 | const TOKEN_ABI = '[{"constant":false,"inputs":[{"name":"account","type":"address"},{"name":"amount","type":"uint256"}],"name":"issue","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Issue","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Transfer","type":"event"}]' 36 | 37 | 38 | ``` 39 | ## 注册 40 | 41 | ``` 42 | 打开实验室链接地址,并利用手机号码注册一个交易帐号,同时会生成账号的地址address。 43 | 44 | ``` 45 | ``` 46 | 这里要调用远程api createaccount接口生成账户地址信息 47 | request({ 48 | url: "https://api.hyperchain" + "/v1/dev/account/create" , 49 | method: "GET", 50 | json: true, 51 | headers: { 52 | "Accept": "Accept: text/html", 53 | "Authorization": accessToken 54 | } 55 | }, 56 | ``` 57 | 58 | [实验室链接地址](http://localhost:3000) 59 | 60 | ![注册](imgs/注册.png) 61 | --- 62 | 63 | ## 登录 64 | ``` 65 | 登录已注册的账号 66 | ``` 67 | ![登录](imgs/登录.png) 68 | 69 | --- 70 | ## 账户信息 71 | ``` 72 | 账户信息,包括账户名称,交易地址,通过地址能够进行后续的创建货币、发行货币以及进行交易 73 | ``` 74 | ![账户](imgs/账户1信息.png) 75 | 76 | --- 77 | 78 | ## 创建资产 79 | ``` 80 | 这里创建资产需要填写资产的名称,资产的单位以及资产的描述信息,填写之后点击创建按钮, 81 | 就能够创建一份我们自定义的财产,同时页面跳转到资产的详细信息。资产列表新加一项内容, 82 | 后台将合约源码部署到了区块链上,之后我们能够进行发行以及交易等操作。 83 | ``` 84 | ``` 85 | 调用远程api deploy接口将合约部署到区块链上 86 | request({ 87 | url: "https://api.hyperchain" + "/v1/dev/contract/deploy", 88 | method: "POST", 89 | json: true, 90 | headers: { 91 | "content-type": "application/json", 92 | "Accept": "application/json", 93 | "Authorization": accessToken 94 | } 95 | , body: requestData 96 | } 97 | ``` 98 | ``` 99 | 资产详情中我们能够看到资产的名称等信息,主要信息为资产的地址即为合约部署到区块链上的地址, 100 | 发行方账户地址即为我们发行账户的地址。但是发行总数量以及资产余额为零,这些值的改变需要后 101 | 续发行资产等操作。 102 | ``` 103 | * 填写资产信息 104 | ![资产](imgs/创建资产.png) 105 | --- 106 | ## 发行资产 107 | 108 | ``` 109 | 发行资产为根据方法方账户以及合约的地址发行的资产,该资产为发行方自定义的资产,可以指定资产的总数, 110 | 这里我们发行100000个货币。资产详情中有我们发行的资产的详细信息,资产地址为我们部署到区块链上的地址。 111 | ``` 112 | ``` 113 | 发行资产对应着合约中abi的一下部分,主要使用了源码中发行(issue)方法发行货币,可以通过abi等信息生成payload用于invokecontract(调用合约)使用 114 | var theAbi = { 115 | "constant": false, 116 | "inputs": [{"name": "account", "type": "address"}, {"name": "amount", "type": "uint256"}], 117 | "name": "issue", 118 | "outputs": [], 119 | "payable": false, 120 | "type": "function" 121 | }; 122 | var methodParams = [from, amount]; 123 | ... 124 | var encodedparams = encodeMethodParams(theAbi, methodParams); 125 | request({ 126 | url: "https://api.hyperchain" + "/v1/dev/contract/invoke", 127 | method: "POST", 128 | json: true, 129 | headers: { 130 | "content-type": "application/json", 131 | "Accept": "application/json", 132 | "Authorization": accessToken 133 | } 134 | , body: requestData 135 | } 136 | 137 | ``` 138 | 139 | * 发行资产 140 | * 141 | ![发行资产](imgs/发行资产.png) 142 | * 资产详情 143 | ![发行资产详情](imgs/发行资产详情.png) 144 | --- 145 | ## 交易 146 | ``` 147 | 这里注册另一个账户后可以实现账户之间的交易,首先我们注册另一个账户,账户1,输入交易金融5000以及 148 | 对方账户地址之后发起交易。账户2,得到交易金额5000以及交易详细信息。此时账户1的金额会减少5000在 149 | 交易详细信息中能够看到,账户2的交易信息增加5000,交易详情中能够看到 150 | ``` 151 | ``` 152 | 账户1交易详情中能够看到资产名称为我们定义好的比特币,交易hash为这次交易生成的交易hash,转出数量 153 | 为5000,对方账号以及地址。发行方即为我们的账号以及地址。 154 | ``` 155 | ``` 156 | 账户2交易详情中能够看到资产名称为比特币,资产类型为转入,资产余额为账户1中转入的5000,资产地址 157 | 为资产在合约上的地址,发行方地址为账户1的地址,还要单位以及资产描述等信息。 158 | ``` 159 | ``` 160 | abi采用以下方式,主要使用了源码中的交易(Transfer)的方法,调用交易的账号会减少相关的金额,目标账户增加相应的金额,使用以下abi生成对应的payload接下来调用合约(invokecontract)完成交易 161 | var theAbi = { 162 | "constant": false, 163 | "inputs": [{"name": "to", "type": "address"}, {"name": "amount", "type": "uint256"}], 164 | "name": "transfer", 165 | "outputs": [], 166 | "payable": false, 167 | "type": "function" 168 | }; 169 | 170 | var methodParams = [to, amount]; 171 | var encodedparams = encodeMethodParams(theAbi, methodParams); 172 | ... 173 | request({ 174 | url: "https://api.hyperchain" + "/v1/dev/contract/invoke", 175 | method: "POST", 176 | json: true, 177 | headers: { 178 | "content-type": "application/json", 179 | "Accept": "application/json", 180 | "Authorization": accessToken 181 | } 182 | , body: requestData 183 | } 184 | ``` 185 | * 注册账户 186 | ![账户2](imgs/账户2信息.png) 187 | * 账户1发起交易 188 | ![账户1发起交易](imgs/账户1发起交易.png) 189 | * 账户1发起交易详情 190 | ![账户1发起交易](imgs/账户1发起交易详情.png) 191 | * 账户2接收交易详情 192 | ![账户2接收交易](imgs/账户2接收交易详情.png) 193 | 194 | ## 资产列表 195 | ``` 196 | 资产列表中能够看到账户的全部发行资产以及交易等信息点击进去可以看到交易详情。 197 | ``` 198 | * 账户1资产列表 199 | ![账户1资产列表](imgs/账户1资产列表.png) 200 | * 账户2资产列表 201 | ![账户2资产列表](imgs/账户2资产列表.png) 202 | * 账户1交易记录 203 | ![账户1交易记录](imgs/账户1交易记录.png) 204 | * 账户2交易记录 205 | ![账户2交易记录](imgs/账户2交易记录.png) 206 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | //routes 9 | var routes = require('./routes/index').router; 10 | var token = require('./routes/token'); 11 | var User = require('./routes/Users'); 12 | var assets = require('./routes/asset'); 13 | var transaction = require('./routes/transaction'); 14 | var password = require('./routes/password'); 15 | 16 | var mongoose = require('mongoose'); 17 | var Users = require('./models/Users').Users; 18 | var authToken = require('./models/Users').authToken; 19 | var app = express(); 20 | 21 | //connect to database 22 | mongoose.connect('mongodb://127.0.0.1/blockchain'); 23 | //mongoose.connect('mongodb://118.31.187.17/blockchain'); 24 | //mongoose.connect('mongodb://139.129.44.55/blockchain'); 25 | // view engine setup 26 | app.set('views', path.join(__dirname, 'ng_app')); 27 | app.set('view engine', 'jade'); 28 | 29 | // uncomment after placing your favicon in /public 30 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 31 | app.use(logger('dev')); 32 | app.use(bodyParser.json()); 33 | app.use(bodyParser.urlencoded({ extended: false })); 34 | app.use(cookieParser()); 35 | app.use(express.static(path.join(__dirname, 'ng_app'))); 36 | app.use(express.static(path.join(__dirname, ''))); 37 | 38 | // We are going to protect /api routes with JWT 39 | app.use('/',token); 40 | app.use('/', routes); 41 | app.use('/',User); 42 | app.use('/asset', authToken, assets); 43 | app.use('/transaction', authToken, transaction); 44 | app.use('/password',password); 45 | // catch 404 and forward to error handler 46 | app.use(function(req, res, next) { 47 | var err = new Error('Not Found'); 48 | err.status = 404; 49 | next(err); 50 | }); 51 | 52 | // error handlers 53 | 54 | // development error handler 55 | // will print stacktrace 56 | if (app.get('env') === 'development') { 57 | app.use(function(err, req, res) { 58 | res.status(err.status || 500); 59 | res.send({"status":"error"}); 60 | }); 61 | } 62 | 63 | // production error handler 64 | // no stacktraces leaked to user 65 | app.use(function(err, req, res) { 66 | res.status(err.status || 500); 67 | res.send({"status":"error"}); 68 | }); 69 | 70 | module.exports = app; 71 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('web:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | 81 | /** 82 | * Event listener for HTTP server "listening" event. 83 | */ 84 | 85 | function onListening() { 86 | var addr = server.address(); 87 | var bind = typeof addr === 'string' 88 | ? 'pipe ' + addr 89 | : 'port ' + addr.port; 90 | debug('Listening on ' + bind); 91 | } 92 | -------------------------------------------------------------------------------- /blockchain_lib/conf.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | client_id: 'APPKEY STRING', 3 | client_secret: 'APPSECRET STRING', 4 | phone: 'HYPERCHAIN ACCOUNT', 5 | password: 'PASSWORD' 6 | } -------------------------------------------------------------------------------- /blockchain_lib/lib.js: -------------------------------------------------------------------------------- 1 | var Web3 = require('hpc-web3'); 2 | var SolidityFunction = require('hpc-web3/lib/web3/function'); 3 | var ethereumUtil = require('ethereumjs-util'); 4 | var _ = require("lodash"); 5 | var request = require('request'); 6 | var coder = require('hpc-web3/lib/solidity/coder'); 7 | var conf = require('./conf') 8 | 9 | const secp256k1 = require('secp256k1'); 10 | const TOKEN_ABI = '[{"constant":false,"inputs":[{"name":"account","type":"address"},{"name":"amount","type":"uint256"}],"name":"issue","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Issue","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Transfer","type":"event"}]' 11 | const TOKEN_SOURCE = 'contract Token { address issuer; mapping (address => uint) balances; event Issue(address account, uint amount); event Transfer(address from, address to, uint amount); function Token() { issuer = msg.sender; } function issue(address account, uint amount) { if (msg.sender != issuer) throw; balances[account] += amount; } function transfer(address to, uint amount) { if (balances[msg.sender] < amount) throw; balances[msg.sender] -= amount; balances[to] += amount; Transfer(msg.sender, to, amount); } function getBalance(address account) constant returns (uint) { return balances[account]; } }'; 12 | const TOKEN_BIN = '0x6060604052341561000c57fe5b5b60008054600160a060020a03191633600160a060020a03161790555b5b6101ca806100396000396000f300606060405263ffffffff60e060020a600035041663867904b48114610037578063a9059cbb14610058578063f8b2cb4f14610079575bfe5b341561003f57fe5b610056600160a060020a03600435166024356100a7565b005b341561006057fe5b610056600160a060020a03600435166024356100e6565b005b341561008157fe5b610095600160a060020a036004351661017f565b60408051918252519081900360200190f35b60005433600160a060020a039081169116146100c35760006000fd5b600160a060020a03821660009081526001602052604090208054820190555b5050565b600160a060020a0333166000908152600160205260409020548190101561010d5760006000fd5b600160a060020a0333811660008181526001602090815260408083208054879003905593861680835291849020805486019055835192835282015280820183905290517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360600190a15b5050565b600160a060020a0381166000908152600160205260409020545b9190505600a165627a7a72305820299e9bb6a492d60cb690d97c76ac26d821ff6bba1b863ce1b8720e449789692c0029' 13 | const URL_GTOKEN = 'https://api.hyperchain.cn/v1/token/gtoken' 14 | 15 | var options = { 16 | method: 'POST', 17 | url: URL_GTOKEN, 18 | formData:{ 19 | client_id: conf.client_id, 20 | client_secret: conf.client_secret, 21 | phone: conf.phone, 22 | password: conf.password 23 | } 24 | }; 25 | 26 | var newAsset = function (from, privkey, cb) { 27 | 28 | console.log(from) 29 | console.log("----------------") 30 | var account = from; 31 | 32 | var requestData = { 33 | "Bin": TOKEN_BIN, 34 | "From": account 35 | } 36 | 37 | 38 | request(options, function (error, response, body) { 39 | if (error) throw new Error(error); 40 | var obj = JSON.parse(body) 41 | var accessToken = obj['access_token']; 42 | 43 | console.log(accessToken) 44 | request({ 45 | url: "https://api.hyperchain.cn" + "/v1/dev/contract/deploy", 46 | method: "POST", 47 | json: true, 48 | headers: { 49 | "content-type": "application/json", 50 | "Accept": "application/json", 51 | "Authorization": accessToken 52 | } 53 | , body: requestData 54 | }, function (error, response, body) { 55 | if (!error && response.statusCode == 200) { 56 | console.log(body) 57 | console.log(body.TxHash) 58 | var hash = body.TxHash; 59 | console.log(hash) 60 | cb && checkForContractAddress(hash, cb); 61 | } else { 62 | console.error(error); 63 | } 64 | }); 65 | }); 66 | // cb && checkForContractAddress(hash, cb); 67 | } 68 | 69 | //invoke 70 | var encodeMethodParams = function (theAbi, params, cb) { 71 | var types = theAbi.inputs.map(function (input) { 72 | return input.type; 73 | }); 74 | for (var i = 0; i < types.length; i++) { 75 | switch (types[i]) { 76 | case "bytes32[]": 77 | try { 78 | params[i] = JSON.parse(params[i]); 79 | } catch (e) { 80 | if (typeof cb == 'function') { 81 | cb(new Error("param " + param[i] + " not match the format of type bytes32[]")) 82 | } 83 | } 84 | if (params[i] instanceof Array) { 85 | for (var j = 0; j < params[i].length; j++) { 86 | params[i][j] += "" 87 | } 88 | } 89 | //add others case to here 90 | } 91 | } 92 | return coder.encodeParams(types, params); 93 | }; 94 | 95 | var issueAsset = function (from, privkey, address, amount, cb) { 96 | console.log("from:" + from + "address" + address) 97 | var theAbi = { 98 | "constant": false, 99 | "inputs": [{"name": "account", "type": "address"}, {"name": "amount", "type": "uint256"}], 100 | "name": "issue", 101 | "outputs": [], 102 | "payable": false, 103 | "type": "function" 104 | }; 105 | var methodParams = [from, amount]; 106 | var encodedparams = encodeMethodParams(theAbi, methodParams); 107 | var fun = new SolidityFunction('', theAbi, ''); 108 | var payloadData = '0x' + fun.signature() + encodedparams; 109 | 110 | console.log(payloadData) 111 | var hash = ""; 112 | var requestData = { 113 | "Const": true, 114 | "From": from, 115 | "Payload": payloadData, 116 | "To": address 117 | } 118 | request(options, function (error, response, body) { 119 | if (error) throw new Error(error); 120 | 121 | var obj = JSON.parse(body) 122 | var accessToken = obj['access_token']; 123 | 124 | console.log(accessToken) 125 | request({ 126 | url: "https://api.hyperchain.cn" + "/v1/dev/contract/invoke", 127 | method: "POST", 128 | json: true, 129 | headers: { 130 | "content-type": "application/json", 131 | "Accept": "application/json", 132 | "Authorization": accessToken 133 | } 134 | , body: requestData 135 | }, function (error, response, body) { 136 | if (!error && response.statusCode == 200) { 137 | console.log(body) 138 | console.log(body.TxHash) 139 | hash = body.TxHash; 140 | cb && checkForContractTransaction(hash, cb) 141 | } else { 142 | 143 | console.log(response.statusCode); 144 | 145 | } 146 | }); 147 | }); 148 | 149 | 150 | } 151 | 152 | var random_16bits = function () { 153 | var num = Math.random().toString(); 154 | if (num.substr(num.length - 16, 1) === '0' || num.length <= 17) { 155 | return random_16bits(); 156 | } 157 | return num.substring(num.length - 16); 158 | } 159 | 160 | var getTimestamp = function (time) { 161 | if (_.isUndefined(time) || _.isNull(time)) { 162 | return Math.round(new Date().getTime() / 1000); 163 | } else { 164 | return Math.round(new Date(time).getTime() / 1000); 165 | } 166 | }; 167 | 168 | var checkForContractTransaction = function (hash, callback) { 169 | // wait for receipt 170 | var flag = false; 171 | var startTime = new Date().getTime(); 172 | var getResp = function () { 173 | if (!flag) { 174 | if ((new Date().getTime() - startTime) < 8000) { 175 | request(options, function (error, response, body) { 176 | if (error) throw new Error(error); 177 | 178 | var obj = JSON.parse(body) 179 | var accessToken = obj['access_token']; 180 | 181 | console.log(accessToken) 182 | var receipt = request({ 183 | url: "https://api.hyperchain.cn" + "/v1/dev/transaction/txreceipt" + "?txhash=" + hash, 184 | method: "GET", 185 | json: true, 186 | headers: { 187 | "Accept": "Accept: text/html", 188 | "Authorization": accessToken 189 | } 190 | }, 191 | function (err, receipt) { 192 | if (receipt && receipt.body.TxHash) { 193 | console.log("----" + receipt.body.TxHash) 194 | console.log(receipt.body) 195 | var temp = { 196 | status: receipt.body.Status, 197 | txHash: receipt.body.TxHash, 198 | postState: receipt.body.PostState, 199 | contractAddress: receipt.body.ContractAddress, 200 | ret: receipt.body.Ret 201 | } 202 | 203 | callback(null, temp); 204 | } else { 205 | setTimeout(getResp, 400); 206 | } 207 | }); 208 | }); 209 | } else { 210 | callback(new Error("getTransactionReceipt timeout...")); 211 | } 212 | } 213 | }; 214 | setTimeout(getResp, 400); 215 | }; 216 | 217 | var checkForContractAddress = function (hash, callback) { 218 | // wait for receipt 219 | var flag = false; 220 | var startTime = new Date().getTime(); 221 | var getResp = function () { 222 | if (!flag) { 223 | if ((new Date().getTime() - startTime) < 8000) { 224 | var t1 = getTimestamp(); 225 | 226 | request(options, function (error, response, body) { 227 | if (error) throw new Error(error); 228 | 229 | var obj = JSON.parse(body) 230 | var accessToken = obj['access_token']; 231 | 232 | console.log(accessToken) 233 | request({ 234 | 235 | url: "https://api.hyperchain.cn" + "/v1/dev/transaction/txreceipt" + "?txhash=" + hash, 236 | method: "GET", 237 | json: true, 238 | headers: { 239 | "Accept": "Accept: text/html", 240 | "Authorization": accessToken 241 | } 242 | }, 243 | function (err, receipt) { 244 | // if (receipt && receipt != undefined) { 245 | if (receipt.body.ContractAddress != undefined && receipt.body.ContractAddress != "") { 246 | flag = true; 247 | console.log(receipt.body) 248 | console.log(receipt.body.ContractAddress) 249 | 250 | callback && callback(null, receipt.body.ContractAddress); 251 | } else { 252 | console.log("resend after 50"); 253 | setTimeout(getResp, 50) 254 | } 255 | }); 256 | }); 257 | var t2 = getTimestamp(); 258 | 259 | } else { 260 | callback(new Error("getTransactionReceipt timeout...")); 261 | } 262 | } 263 | }; 264 | getResp() 265 | }; 266 | 267 | var newTransaction = function (from, to, amount, privKey, contractAddr, cb) { 268 | 269 | var theAbi = { 270 | "constant": false, 271 | "inputs": [{"name": "to", "type": "address"}, {"name": "amount", "type": "uint256"}], 272 | "name": "transfer", 273 | "outputs": [], 274 | "payable": false, 275 | "type": "function" 276 | }; 277 | 278 | var methodParams = [to, amount]; 279 | var encodedparams = encodeMethodParams(theAbi, methodParams); 280 | var fun = new SolidityFunction('', theAbi, ''); 281 | var payloadData = '0x' + fun.signature() + encodedparams; 282 | 283 | console.log(payloadData) 284 | var hash = ""; 285 | var requestData = { 286 | "Const": true, 287 | "From": from, 288 | "Payload": payloadData, 289 | "To": to 290 | } 291 | 292 | request(options, function (error, response, body) { 293 | if (error) throw new Error(error); 294 | 295 | var obj = JSON.parse(body) 296 | var accessToken = obj['access_token']; 297 | 298 | console.log(accessToken) 299 | request({ 300 | url: "https://api.hyperchain.cn" + "/v1/dev/contract/invoke", 301 | method: "POST", 302 | json: true, 303 | headers: { 304 | "content-type": "application/json", 305 | "Accept": "application/json", 306 | "Authorization": accessToken 307 | } 308 | , body: requestData 309 | }, function (error, response, body) { 310 | if (!error && response.statusCode == 200) { 311 | console.log(body) 312 | console.log(body.TxHash) 313 | hash = body.TxHash; 314 | cb && checkForContractTransaction(hash, cb) 315 | } else { 316 | 317 | console.log(response.statusCode); 318 | 319 | } 320 | }); 321 | }); 322 | 323 | } 324 | 325 | module.exports = { 326 | checkForContractAddress: checkForContractAddress, 327 | newAsset: newAsset, 328 | getTimestamp: getTimestamp, 329 | random_16bits: random_16bits, 330 | checkForContractTransaction: checkForContractTransaction, 331 | issueAsset: issueAsset, 332 | newTransaction: newTransaction 333 | } 334 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ether-admin", 3 | "description": "", 4 | "main": "app.js", 5 | "license": "MIT", 6 | "homepage": "index.html", 7 | "dependencies": { 8 | "angular": "^1.5.5", 9 | "angular-route": "^1.5.5", 10 | "bootstrap": "^3.3.6", 11 | "ng-file-upload-shim": "^12.0.4", 12 | "angular-bootstrap": "^1.3.3", 13 | "angular-base64": "^2.0.5", 14 | "ng-img-crop": "ngImgCrop#^0.3.2", 15 | "angular-loading-bar": "^0.9.0", 16 | "font-awesome-animation": "^0.0.9" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /help_utils/base64_util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Tyrion on 2016/5/26. 3 | */ 4 | module.exports.decode = function(string){ 5 | return new Buffer(string,'base64').toString(); 6 | }; -------------------------------------------------------------------------------- /help_utils/geth_level_utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Tyrion on 2016/6/8. 3 | */ 4 | var BASE_URL = 'http://10.214.20.118:3000'; 5 | module.exports.BASE_URL = BASE_URL; -------------------------------------------------------------------------------- /imgs/创建资产.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/创建资产.png -------------------------------------------------------------------------------- /imgs/发行资产.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/发行资产.png -------------------------------------------------------------------------------- /imgs/发行资产详情.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/发行资产详情.png -------------------------------------------------------------------------------- /imgs/注册.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/注册.png -------------------------------------------------------------------------------- /imgs/登录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/登录.png -------------------------------------------------------------------------------- /imgs/账户1交易记录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/账户1交易记录.png -------------------------------------------------------------------------------- /imgs/账户1信息.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/账户1信息.png -------------------------------------------------------------------------------- /imgs/账户1发起交易.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/账户1发起交易.png -------------------------------------------------------------------------------- /imgs/账户1发起交易详情.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/账户1发起交易详情.png -------------------------------------------------------------------------------- /imgs/账户1资产列表.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/账户1资产列表.png -------------------------------------------------------------------------------- /imgs/账户2交易记录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/账户2交易记录.png -------------------------------------------------------------------------------- /imgs/账户2信息.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/账户2信息.png -------------------------------------------------------------------------------- /imgs/账户2接收交易详情.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/账户2接收交易详情.png -------------------------------------------------------------------------------- /imgs/账户2资产列表.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/imgs/账户2资产列表.png -------------------------------------------------------------------------------- /models/Users.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Created by Tyrion on 2016/5/25. 4 | */ 5 | var mongoose = require('mongoose'); 6 | var jwt = require('jwt-simple'); 7 | var base64_util = require("../help_utils/base64_util"); 8 | 9 | var Schema = mongoose.Schema; 10 | var _User = new Schema({ 11 | phone : { type: String, required: true, unique: true }, 12 | password : { type: String, required: true}, 13 | account_addr : { type: String, required: true}, 14 | private_key : {type: String, required: true}, 15 | create_time : { type: Date, default: Date.now }, 16 | assets:{type: Array}, 17 | key: {type: String,required:true,default:"aVeryHardGuessString"} 18 | }); 19 | //Password verification 20 | _User.methods.comparePassword = function(password, cb) { 21 | if(password == this.password){ 22 | return cb(true); 23 | } 24 | return cb(false); 25 | }; 26 | _User.methods.toJson = function(){ 27 | return { 28 | phone: this.phone, 29 | create_time: this.create_time.toLocaleString(), 30 | account_addr: this.account_addr 31 | } 32 | }; 33 | var Users = mongoose.model('User', _User); 34 | 35 | 36 | //A middleware to verify User token 37 | /** 38 | * When use this middleware ,your code should gose like this: 39 | * app.get('/something', authToken, function(req, res){ 40 | * // do something 41 | * }); 42 | * @param req 43 | * @param res 44 | * @param next 45 | * @returns {*} 46 | */ 47 | var authToken = function(req, res, next) { 48 | // code goes here 49 | var authSequence = req.headers['authorization']; 50 | if(!authSequence){ 51 | return res.status(401).send({ 52 | status: 'failed', 53 | msg: "Token is required" 54 | }); 55 | } 56 | var segments = authSequence.split(' '); 57 | if (segments.length !== 2) { 58 | throw new Error('Not enough or too many authorization segments'); 59 | } 60 | var pass_secret = base64_util.decode(segments[1]).split(":"); 61 | if (pass_secret.length !== 2) { 62 | throw new Error('Not enough or too many pass_secret segments'); 63 | } 64 | var token = pass_secret[0]; 65 | if (token) { 66 | try { 67 | var decoded = jwt.decode(token, 'jwtTokenSecret'); 68 | // handle token here 69 | if (decoded.exp <= Date.now()) { 70 | return res.status(401).send({ 71 | status: 'failed', 72 | msg:'Access token has expired' 73 | }); 74 | } 75 | Users.findOne({ _id: decoded.id }, function(err, user) { 76 | if(err){ 77 | return res.status(401).send({ 78 | status: 'failed', 79 | msg: "cannot find user,mongo error." 80 | }); 81 | } 82 | if(!user){ 83 | return res.status(401).send({ 84 | status: 'failed', 85 | msg: "cannot find user" 86 | }); 87 | 88 | } 89 | if(decoded.password != user.password){ 90 | return res.status(401).send({ 91 | status: 'failed', 92 | msg: "token已失效,(token过期,密码修改可能导致这个问题),请重新登陆" 93 | }) 94 | } 95 | req.user = user; 96 | return next(); 97 | }); 98 | } catch (err) { 99 | console.log(err); 100 | return res.status(401).send({ 101 | status: 'failed', 102 | msg: "cannot find user,catch error" 103 | }); 104 | } 105 | } else { 106 | console.log("6"); 107 | return res.status(401).send({ 108 | status: 'failed', 109 | msg:"I need a token!" 110 | }); 111 | } 112 | }; 113 | var verify_psw = function(req,res,next){ 114 | // console.log("==========="+req.body); 115 | var psw = req.body.psw; 116 | if(psw){ 117 | if(!req.user){ 118 | return res.status(401).send({ 119 | status: "refuse", 120 | msg: "token错误" 121 | }) 122 | } 123 | req.user.comparePassword(psw, function(result){ 124 | if(result){ 125 | return next(); 126 | }else{ 127 | return res.status(401).send({ 128 | status: "refuse", 129 | msg: "请输入正确的密码" 130 | }) 131 | } 132 | }) 133 | }else{ 134 | return res.status(401).send({ 135 | status: "refuse", 136 | msg: "请输入密码" 137 | }) 138 | } 139 | }; 140 | module.exports.authToken = authToken; 141 | module.exports.Users = Users; 142 | module.exports.verify_psw = verify_psw; 143 | -------------------------------------------------------------------------------- /models/asset.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Vampire on 2016/5/27. 3 | */ 4 | var mongoose = require('mongoose'); 5 | var jwt = require('jwt-simple'); 6 | var base64_util = require("../help_utils/base64_util"); 7 | 8 | var Schema = mongoose.Schema; 9 | 10 | var ASSET_TYPE={ 11 | RECEIVING:"转入", 12 | ISSUE:"发行" 13 | }; 14 | var _Asset = new Schema({ 15 | name : { type: String, required: true 16 | // , unique: true TODO:资产名可以不唯一 17 | }, 18 | amount : { type: Number, required: true, default: 0}, 19 | balance: {type: Number, required: true, default: 0}, 20 | unit : { type: String, required: true}, 21 | description : { type: String, required: true}, 22 | logo : { type: String, required: true}, 23 | account_addr : { type: String, required: true}, 24 | create_time : { type: Date, default: Date.now }, 25 | asset_addr: {type: String, required: true}, 26 | holder: {type: Array}, 27 | type: {type:String, required:true, default:ASSET_TYPE.ISSUE} 28 | }); 29 | _Asset.methods.toJson = function(){ 30 | return { 31 | name: this.name, 32 | amount: this.amount, 33 | balance: this.balance, 34 | unit: this.unit, 35 | description: this.description, 36 | logo: this.logo, 37 | type: this.type, 38 | account_addr: this.account_addr, 39 | create_time: this.create_time.toLocaleString(), 40 | asset_addr: this.asset_addr 41 | } 42 | }; 43 | var Assets = mongoose.model('Asset', _Asset); 44 | 45 | module.exports.Assets = Assets; 46 | module.exports.ASSET_TYPE = ASSET_TYPE; 47 | -------------------------------------------------------------------------------- /models/transaction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Vampire on 2016/5/27. 3 | */ 4 | var mongoose = require('mongoose'); 5 | var ASSET_TYPE = require('./asset').ASSET_TYPE; 6 | var Asset = require('./asset').Assets; 7 | var Users = require('./Users').Users; 8 | 9 | var Schema = mongoose.Schema; 10 | 11 | var TRANSACTION_TYPE={ 12 | TRANSACTION:"交易", 13 | ISSUE:"发行" 14 | }; 15 | var _Transaction = new Schema({ 16 | hash: { type: String, required: true, unique:true}, 17 | from : { type: String, required: true, default:"null"}, 18 | amount : { type: Number, required: true, default: 0}, 19 | to : { type: String, required: true, default:"null"}, 20 | asset_addr : { type: String, required: true}, 21 | create_time : { type: Date, default: Date.now }, 22 | type : {type :String, required: true, default: TRANSACTION_TYPE.TRANSACTION} 23 | }); 24 | 25 | _Transaction.methods.toJson = function(callback){ 26 | var tr = this; 27 | Asset.findOne({asset_addr:this.asset_addr,type:ASSET_TYPE.ISSUE}, function (err, origin_asset) { 28 | Asset.findOne({asset_addr: tr.asset_addr},function(err,asset){ 29 | Users.findOne({account_addr:origin_asset.account_addr},function(err,issuer){ 30 | 31 | callback(err,{ 32 | hash:tr.hash, 33 | from: tr.from, 34 | amount: tr.amount, 35 | to: tr.to, 36 | asset_addr: tr.assets_addr, 37 | asset_name: asset?asset.name:"无", 38 | issuer: issuer?{ 39 | phone:issuer.phone, 40 | account_addr:issuer.account_addr 41 | }:{}, 42 | create_time: tr.create_time.toLocaleString() 43 | }); 44 | }) 45 | }) 46 | }); 47 | }; 48 | var Transaction = mongoose.model('Transaction', _Transaction); 49 | 50 | module.exports.Transaction = Transaction; 51 | module.exports.TRANSACTION_TYPE = TRANSACTION_TYPE; 52 | -------------------------------------------------------------------------------- /ng_app/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "cwd": "..", 3 | "directory": "ng_app/bower_components" 4 | } 5 | -------------------------------------------------------------------------------- /ng_app/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} 5 | -------------------------------------------------------------------------------- /ng_app/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/ng_app/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /ng_app/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/ng_app/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /ng_app/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/ng_app/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /ng_app/img/aif.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ng_app/img/group-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Develope r 7 | 8 | 9 | 开发者平台 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ng_app/img/group.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ng_app/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/ng_app/img/logo.png -------------------------------------------------------------------------------- /ng_app/img/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/ng_app/img/logo2.png -------------------------------------------------------------------------------- /ng_app/img/logo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/ng_app/img/logo3.png -------------------------------------------------------------------------------- /ng_app/img/logo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperchaincn/Digital-Wallet/e2cef1587aa6e34c3bb16149bc2119770237a711/ng_app/img/logo4.png -------------------------------------------------------------------------------- /ng_app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 浙大区块链实验室 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 | -------------------------------------------------------------------------------- /ng_app/js/controllers.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('myApp'); 2 | 3 | app.controller('WhiteController', function($scope, $http, $window) { 4 | $http({ 5 | method: 'GET', 6 | url: '/user' 7 | }).then(function successCallback(response) { 8 | if (response.status == 200) { 9 | $window.location.href = '/dashboard'; 10 | } 11 | }).then(function errorCallback(response) {}); 12 | }); 13 | 14 | app.controller('DashboardController', function($scope, $route, $window) { 15 | $scope.$route = $route; 16 | $scope.account = { 17 | phone: '' 18 | }; 19 | $scope.signOut = function($scope) { 20 | delete $window.sessionStorage.token; 21 | $window.location.href = '/login'; 22 | }; 23 | }); 24 | 25 | // ------------------------------账户--------------------------------------- 26 | app.controller('UserController', function($scope, $http, $route, $uibModal) { 27 | $scope.$route = $route; 28 | $http({ 29 | method: 'GET', 30 | url: '/user' 31 | }).then(function(result) { 32 | data = result.data 33 | $scope.account.phone = data.data.phone; 34 | $scope.user = { 35 | account: data.data.phone, 36 | address: data.data.account_addr, 37 | time: data.data.create_time 38 | }; 39 | },function(data, status, headers, config) {}); 40 | 41 | $scope.open = function(size) { 42 | var modalInstance = $uibModal.open({ 43 | templateUrl: 'views/verifyPwd.html', 44 | controller: 'KeyPwdModalCtrl', 45 | size: size, 46 | resolve: { 47 | title: function() { 48 | return '查看密钥'; 49 | }, 50 | httpArgs: function() { 51 | return { 52 | method: 'POST', 53 | url: '/key' 54 | }; 55 | } 56 | } 57 | }); 58 | modalInstance.result.then(function(key) { 59 | $scope.user.key = key; 60 | }); 61 | }; 62 | 63 | $scope.changePwd = function() { 64 | var modalInstance = $uibModal.open({ 65 | templateUrl: 'views/changePwd.html', 66 | controller: 'changePwdCtrl', 67 | size: 'md' 68 | }); 69 | }; 70 | }); 71 | 72 | app.controller('changePwdCtrl', function($scope, $uibModalInstance, $http, $window) { 73 | $scope.ok = function() { 74 | console.log($scope.psw); 75 | if ($scope.psw.new_psw != $scope.repeat_psw) { 76 | $scope.repeat_error = '重复新密码错误!'; 77 | return; 78 | } else { 79 | delete $scope.repeat_error; 80 | } 81 | $http({ 82 | method: 'PUT', 83 | url: '/password', 84 | data: $scope.psw 85 | }).then(function(response) { 86 | if (response.status == 200) { 87 | $uibModalInstance.close(); 88 | delete $window.sessionStorage.token; 89 | $window.location.href = '/login'; 90 | } else { 91 | $scope.error = response.data.msg; 92 | } 93 | }); 94 | }; 95 | 96 | $scope.close = function() { 97 | $uibModalInstance.close(); 98 | }; 99 | }); 100 | 101 | app.controller('KeyPwdModalCtrl', function($scope, $uibModalInstance, $http, title, httpArgs) { 102 | var key = "default"; 103 | $scope.title = title; 104 | $scope.ok = function() { 105 | $http(httpArgs).then(function() { 106 | key = "privbvqVf51x29pE79Sr2HDiasGqnUDQU9zRUWwEbRcdaPZhgFqXDP6Z"; 107 | $uibModalInstance.close(key); 108 | }, function() { 109 | key = "privbvqVf51x29pE79Sr2HDiasGqnUDQU9zRUWwEbRcdaPZhgFqXDP6Z"; 110 | $uibModalInstance.close(key); 111 | }); 112 | }; 113 | }); 114 | 115 | //--------------------------------资产-------------------------------------------------- 116 | app.controller('CreateAssetController', function($scope, Upload, $timeout, $http, $uibModal) { 117 | // $scope.create = function(file) { 118 | // console.log(file); 119 | // console.log($scope.asset); 120 | // if (file) { 121 | // file.upload = Upload.upload({ 122 | // url: 'https://angular-file-upload-cors-srv.appspot.com/upload', 123 | // data: { 124 | // file: file, 125 | // asset: $scope.asset 126 | // } 127 | // }); 128 | // file.upload.then(function(response) { 129 | // $timeout(function() { 130 | // file.result = response.data; 131 | // }); 132 | // }, function(response) { 133 | // if (response.status > 0) 134 | // $scope.errorMsg = response.status + ': ' + response.data; 135 | // }, function(evt) { 136 | // file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total)); 137 | // }); 138 | // } 139 | // }; 140 | 141 | // $scope.cropped = false; 142 | // $scope.logo = false; 143 | 144 | // $scope.selectImg = function(){ 145 | // console.log($scope.logo); 146 | // console.log($scope.cropped); 147 | // if($scope.cropped){ 148 | // $scope.cropped = !$scope.cropped; 149 | // } 150 | // } 151 | 152 | $scope.create = function(dataUrl, name) { 153 | var modalInstance = $uibModal.open({ 154 | templateUrl: 'views/verifyPwd.html', 155 | controller: 'DetailModalInstanceCtrl', 156 | size: 'sm', 157 | backdrop: 'static', 158 | resolve: { 159 | title: function() { 160 | return "创建资产"; 161 | }, 162 | httpArgs: function() { 163 | return { 164 | method: 'POST', 165 | url: '/asset', 166 | data: $scope.asset 167 | }; 168 | }, 169 | url: function() { 170 | return '/asset/detail'; 171 | } 172 | } 173 | }); 174 | }; 175 | }); 176 | 177 | app.controller('CreateAssetModalInstanceCtrl', function($scope, $http, $uibModalInstance, $location, Upload, data, file, DetailService, $window, $base64) { 178 | $scope.title = '创建资产'; 179 | $scope.ok = function() { 180 | data.psw = $scope.psw; 181 | Upload.http({ 182 | url: '/asset', 183 | data: { 184 | data: data, 185 | file: file 186 | } 187 | }) 188 | // $http({ 189 | // method: 'POST', 190 | // url: '/asset', 191 | // data: data 192 | // }) 193 | .then(function(response) { 194 | DetailService.setDetail(response.data.data); 195 | $uibModalInstance.close(); 196 | $location.url('/asset/detail'); 197 | }, function(response) { 198 | $scope.error = "密码错误!"; 199 | }); 200 | }; 201 | }); 202 | 203 | app.controller('IssueAssetController', function($scope, $http, $uibModal) { 204 | $http({ 205 | method: 'GET', 206 | url: '/asset?type=issue', 207 | }).then(function(response) { 208 | $scope.items = response.data.data; 209 | }); 210 | 211 | $scope.changeIssueAsset = function(asset) { 212 | $scope.issue = { 213 | asset_addr: asset.asset_addr 214 | }; 215 | }; 216 | 217 | $scope.doIssue = function() { 218 | var modalInstance = $uibModal.open({ 219 | templateUrl: 'views/verifyPwd.html', 220 | controller: 'DetailModalInstanceCtrl', 221 | size: 'sm', 222 | backdrop: 'static', 223 | resolve: { 224 | title: function() { 225 | return '发行资产'; 226 | }, 227 | httpArgs: function() { 228 | return { 229 | method: 'PUT', 230 | url: '/asset', 231 | data: $scope.issue 232 | }; 233 | }, 234 | url: function() { 235 | return '/asset/detail'; 236 | } 237 | } 238 | }); 239 | }; 240 | }); 241 | 242 | function down(x, y) { 243 | return (x.create_time < y.create_time) ? 1 : -1; 244 | } 245 | 246 | function getDetail($scope, $http, url) { 247 | $http({ 248 | method: 'GET', 249 | url: url 250 | }).then(function(response) { 251 | var items = response.data.data; 252 | items.sort(down); 253 | $scope.totalItems = items.length; 254 | $scope.perPage = 10; 255 | $scope.items = items.slice(0, $scope.perPage); 256 | $scope.pageChanged = function() { 257 | $scope.items = items.slice(($scope.perPage * ($scope.currentPage - 1)), ($scope.perPage * $scope.currentPage)); //通过当前页数筛选出表格当前显示数据 258 | }; 259 | }); 260 | } 261 | 262 | app.controller('AssetListController', function($scope, $http, $location, DetailService) { 263 | $scope.selectedType = "all"; 264 | getDetail($scope, $http, '/asset'); 265 | 266 | $scope.changeType = function(selectedType) { 267 | if (selectedType == 'in') { 268 | getDetail($scope, $http, '/asset?type=receive'); 269 | } else if (selectedType == 'issue') { 270 | getDetail($scope, $http, '/asset?type=issue'); 271 | } else if (selectedType == 'all') { 272 | getDetail($scope, $http, '/asset'); 273 | } 274 | }; 275 | 276 | $scope.detail = function(asset) { 277 | DetailService.setDetail(asset); 278 | $location.url('/asset/detail'); 279 | }; 280 | }); 281 | 282 | app.controller('AssetDetailController', function($scope, DetailService) { 283 | $scope.asset = DetailService.getDetail(); 284 | }); 285 | 286 | //-----------------------------------交易--------------------------------- 287 | app.controller('AssetTransferController', function($scope, $uibModal, $http) { 288 | $http({ 289 | method: 'GET', 290 | url: '/asset', 291 | }).then(function(response) { 292 | $scope.assets = response.data.data; 293 | }); 294 | 295 | $scope.changeAsset = function(asset) { 296 | $scope.trx = { 297 | asset_addr: asset.asset_addr 298 | }; 299 | }; 300 | 301 | $scope.setBalance = function(asset) { 302 | if (asset) { 303 | $scope.balance = asset.balance; 304 | } 305 | }; 306 | 307 | $scope.transfer = function() { 308 | if ($scope.trx.amount > $scope.balance) { 309 | $scope.balanceError = '余额不足'; 310 | return; 311 | } else { 312 | delete $scope.balanceError; 313 | } 314 | var modalInstance = $uibModal.open({ 315 | templateUrl: 'views/verifyPwd.html', 316 | controller: 'DetailModalInstanceCtrl', 317 | size: 'sm', 318 | backdrop: 'static', 319 | resolve: { 320 | title: function() { 321 | return '转出资产'; 322 | }, 323 | httpArgs: function() { 324 | return { 325 | method: 'POST', 326 | url: '/transaction', 327 | data: $scope.trx 328 | }; 329 | }, 330 | url: function() { 331 | return '/transaction/detail'; 332 | } 333 | } 334 | }); 335 | }; 336 | }); 337 | 338 | app.controller('TransactionListController', function($scope, $uibModal, $http, DetailService, $location) { 339 | $scope.selectedType = "all"; 340 | getDetail($scope, $http, '/transaction'); 341 | 342 | $scope.changeType = function(selectedType) { 343 | if (selectedType == 'all') { 344 | getDetail($scope, $http, '/transaction'); 345 | } else if (selectedType == 'in') { 346 | getDetail($scope, $http, 'transaction?type=receive'); 347 | } else if (selectedType == 'out') { 348 | getDetail($scope, $http, 'transaction?type=transfer'); 349 | } else if (selectedType == 'issue') { 350 | getDetail($scope, $http, 'transaction?type=issue'); 351 | } 352 | }; 353 | 354 | $scope.detail = function(record) { 355 | DetailService.setDetail(record); 356 | $location.url('/transaction/detail'); 357 | }; 358 | }); 359 | 360 | app.controller('TransactionDetailController', function($scope, DetailService) { 361 | $scope.trx = DetailService.getDetail(); 362 | }); 363 | 364 | app.controller('DetailModalInstanceCtrl', function($scope, $uibModalInstance, $http, title, httpArgs, DetailService, $location, url) { 365 | $scope.title = title; 366 | $scope.disappear = false; 367 | $scope.ok = function() { 368 | $scope.disappear = true; 369 | delete $scope.error; 370 | httpArgs.data.psw = $scope.psw; 371 | $http(httpArgs).then(function(response) { 372 | if (response.status == 200) { 373 | DetailService.setDetail(response.data.data); 374 | $uibModalInstance.close(); 375 | $location.url(url); 376 | } else { 377 | $scope.disappear = false; 378 | $scope.error = response.data.msg; 379 | } 380 | }, function(response) { 381 | $scope.disappear = false; 382 | $scope.error = "密码错误!"; 383 | }); 384 | }; 385 | 386 | $scope.close = function() { 387 | $uibModalInstance.close(); 388 | }; 389 | }); -------------------------------------------------------------------------------- /ng_app/js/directives.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('myApp'); 2 | app.directive('my-file-input', ['', function() { 3 | // Runs during compile 4 | return { 5 | // name: '', 6 | // priority: 1, 7 | // terminal: true, 8 | // scope: {}, // {} = isolate, true = child, false/undefined = no change 9 | // controller: function($scope, $element, $attrs, $transclude) {}, 10 | // require: 'ngModel', // Array = multiple requires, ? = optional, ^ = check parent elements 11 | restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment 12 | template: "", 13 | // templateUrl: '', 14 | // replace: true, 15 | // transclude: true, 16 | // compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})), 17 | link: function($scope, iElm, iAttrs, controller) { 18 | console.log("my-directive here"); 19 | iElm.fileinput({ 20 | language: "zh", 21 | // uploadUrl: "/file-upload-batch/2", 22 | allowedFileExtensions: ["jpg", "png", "gif"] 23 | }); 24 | } 25 | }; 26 | }]); 27 | 28 | app.directive('hello', function() { 29 | return { 30 | restrict: 'E', 31 | template: '

Hi there fsdaffffffffffffssdasfffffffffffff

', 32 | replace: true 33 | }; 34 | }); -------------------------------------------------------------------------------- /ng_app/js/interceptors.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('myApp'); 2 | app.factory('authInterceptor', function($q, $window, $location, $base64) { 3 | var interceptor = { 4 | 'request': function(config) { 5 | config.headers = config.headers || {}; 6 | if ($window.sessionStorage.token) { 7 | config.headers.Authorization = 'Basic ' + $base64.encode($window.sessionStorage.token + ":"); 8 | }else{ 9 | // $location.url("/login"); 10 | $window.location.href = "/login"; 11 | } 12 | return config; 13 | }, 14 | 'response': function(response) { 15 | response.config.responseTimestamp = new Date().getTime(); 16 | return response || $q.when(response); 17 | }, 18 | 'requestError': function(rejection) { 19 | return response; 20 | }, 21 | 'responseError': function(response) { 22 | if (response.status === 401) { 23 | // $window.location.href = "/login"; 24 | } 25 | return response; 26 | } 27 | }; 28 | return interceptor; 29 | }); -------------------------------------------------------------------------------- /ng_app/js/loginAndRegisterModule.js: -------------------------------------------------------------------------------- 1 | var lar = angular.module('myLoginRegister', ['ngRoute']); 2 | 3 | lar.controller('LoginController', function($scope, $http, $location, $window) { 4 | $scope.login = function() { 5 | // console.log($.param($scope.loginFormData)); 6 | $http({ 7 | url: '/token', 8 | method: 'POST', 9 | data: $scope.loginFormData 10 | }).then(function(result) { 11 | $window.sessionStorage.token = result.data.token; 12 | $window.location.href = '/dashboard'; 13 | },function(result) { 14 | $scope.loginError = "手机号或者密码错误!"; 15 | }) 16 | }; 17 | }); 18 | 19 | lar.controller('RegisterController', function($scope, $http, $location, $window) { 20 | $scope.register = function() { 21 | $http({ 22 | method: 'POST', 23 | url: '/user', 24 | data: $scope.registerFormData 25 | }).then(function(result) { 26 | alert(JSON.stringify(result.data.data.token)) 27 | $window.sessionStorage.token = result.data.data.token; 28 | $window.location.href = '/dashboard'; 29 | },function(result) { 30 | $scope.registerError = result.msg; 31 | }); 32 | }; 33 | }); 34 | -------------------------------------------------------------------------------- /ng_app/js/menu.js: -------------------------------------------------------------------------------- 1 | //Menu JS 2 | (function($) { 3 | 4 | $.fn.bluemoonMenu = function(options) { 5 | 6 | var cssmenu = $(this), 7 | settings = $.extend({ 8 | title: "Menu", 9 | format: "dropdown", 10 | sticky: false 11 | }, options); 12 | 13 | return this.each(function() { 14 | cssmenu.prepend(''); 15 | var mainmenu; 16 | $(this).find("#menu-button").on('click', function() { 17 | $(this).toggleClass('menu-opened'); 18 | mainmenu = $(this).next('ul'); 19 | if (mainmenu.hasClass('open')) { 20 | mainmenu.hide().removeClass('open'); 21 | } else { 22 | mainmenu.show().addClass('open'); 23 | if (settings.format === "dropdown") { 24 | mainmenu.find('ul').show(); 25 | } 26 | } 27 | }); 28 | 29 | $(this).find("#asset").on('click', function() { 30 | if (mainmenu && mainmenu.hasClass('open')) { 31 | mainmenu.hide().removeClass('open'); 32 | } 33 | }); 34 | 35 | $(this).find("#trx").on('click', function() { 36 | if (mainmenu && mainmenu.hasClass('open')) { 37 | mainmenu.hide().removeClass('open'); 38 | } 39 | }); 40 | 41 | $(this).find("#user").on('click', function() { 42 | if (mainmenu && mainmenu.hasClass('open')) { 43 | mainmenu.hide().removeClass('open'); 44 | } 45 | }); 46 | 47 | cssmenu.find('li ul').parent().addClass('has-sub'); 48 | 49 | multiTg = function() { 50 | cssmenu.find(".has-sub").prepend(''); 51 | cssmenu.find('.submenu-button').on('click', function() { 52 | $(this).toggleClass('submenu-opened'); 53 | if ($(this).siblings('ul').hasClass('open')) { 54 | $(this).siblings('ul').removeClass('open').hide(); 55 | } else { 56 | $(this).siblings('ul').addClass('open').show(); 57 | } 58 | }); 59 | }; 60 | if (settings.format === 'multitoggle') multiTg(); 61 | else cssmenu.addClass('dropdown'); 62 | if (settings.sticky === true) cssmenu.css('position', 'fixed'); 63 | resizeFix = function() { 64 | if ($(window).width() > 768) { 65 | cssmenu.find('ul').show(); 66 | } 67 | if ($(window).width() <= 768) { 68 | cssmenu.find('ul').hide().removeClass('open'); 69 | } 70 | }; 71 | resizeFix(); 72 | return $(window).on('resize', resizeFix); 73 | }); 74 | }; 75 | })(jQuery); 76 | 77 | (function($) { 78 | $(document).ready(function() { 79 | 80 | $("#cssmenu").bluemoonMenu({ 81 | title: "Menu", 82 | format: "multitoggle" 83 | }); 84 | }); 85 | })(jQuery); -------------------------------------------------------------------------------- /ng_app/js/router.js: -------------------------------------------------------------------------------- 1 | // angular.module('myApp', ['ngRoute', 'myApp.controller', 'myApp.service', 'myApp.directive', 'myApp.interceptor']) 2 | var app = angular.module('myApp', ['ngRoute', 'ui.bootstrap', 'ngFileUpload', 'base64', 'ngImgCrop', 'angular-loading-bar']); 3 | app.config(['$routeProvider', '$locationProvider', '$httpProvider', 'cfpLoadingBarProvider', function($routeProvider, $locationProvider, $httpProvider, cfpLoadingBarProvider) { 4 | $routeProvider 5 | .when('/', { 6 | controller: 'WhiteController' 7 | }) 8 | .when('/user', { 9 | templateUrl: '/views/user.html', 10 | controller: 'UserController', 11 | activetab: 'user' 12 | }) 13 | .when('/asset/create', { 14 | templateUrl: '/views/assetCreate.html', 15 | controller: 'CreateAssetController', 16 | activetab: 'asset' 17 | }) 18 | .when('/asset/issue', { 19 | templateUrl: '/views/assetIssue.html', 20 | controller: 'IssueAssetController', 21 | activetab: 'asset' 22 | }) 23 | .when('/asset/list', { 24 | templateUrl: '/views/assetList.html', 25 | controller: 'AssetListController', 26 | activetab: 'asset' 27 | }) 28 | .when('/asset/detail', { 29 | templateUrl: '/views/assetDetail.html', 30 | controller: 'AssetDetailController', 31 | activetab: 'asset' 32 | }) 33 | .when('/transaction/list', { 34 | templateUrl: '/views/transactionList.html', 35 | controller: 'TransactionListController', 36 | activetab: 'transaction' 37 | }) 38 | .when('/transaction/detail', { 39 | templateUrl: '/views/transactionDetail.html', 40 | controller: 'TransactionDetailController', 41 | activetab: 'transaction' 42 | }) 43 | .when('/transfer', { 44 | templateUrl: '/views/transfer.html', 45 | controller: 'AssetTransferController', 46 | activetab: 'transaction' 47 | }) 48 | .when('/test', { 49 | templateUrl: '/views/test.html', 50 | activetab: 'asset' 51 | }) 52 | .when('/dashboard', { 53 | templateUrl: 'views/user.html', 54 | controller: 'UserController', 55 | activetab: 'user' 56 | }) 57 | .otherwise({ 58 | redirectTo: '/test' 59 | }); 60 | $locationProvider.html5Mode(true); 61 | $httpProvider.interceptors.push('authInterceptor'); 62 | cfpLoadingBarProvider.parentSelector = '#loading-bar-container'; 63 | // cfpLoadingBarProvider.includeSpinner = false; 64 | cfpLoadingBarProvider.spinnerTemplate = '
正在处理中...
'; 65 | 66 | }]); -------------------------------------------------------------------------------- /ng_app/js/services.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('myApp'); 2 | app.factory('DetailService', function() { 3 | var detail = {}; 4 | return { 5 | getDetail: function() { 6 | return this.detail; 7 | }, 8 | setDetail: function(detail) { 9 | this.detail = detail; 10 | } 11 | }; 12 | }); 13 | 14 | app.factory('alertService', function($uibModal) { 15 | var alertService = {}; 16 | 17 | // // 创建一个全局的 alert 数组 18 | // $rootScope.alerts = []; 19 | 20 | // alertService.add = function(type, msg) { 21 | // $rootScope.alerts.push({ 22 | // 'type': type, 23 | // 'msg': msg, 24 | // 'close': function() { 25 | // alertService.closeAlert(this); 26 | // } 27 | // }); 28 | // }; 29 | 30 | // alertService.closeAlert = function(alert) { 31 | // alertService.closeAlertIdx($rootScope.alerts.indexOf(alert)); 32 | // }; 33 | 34 | // alertService.closeAlertIdx = function(index) { 35 | // $rootScope.alerts.splice(index, 1); 36 | // }; 37 | 38 | alertService.open = function(msg) { 39 | $uibModal.open({ 40 | template: '", 41 | size: 'sm' 42 | }); 43 | }; 44 | return alertService; 45 | }); -------------------------------------------------------------------------------- /ng_app/views/assetCreate.html: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 |
24 |
25 |
26 | 资产名称 27 |
28 |
29 |
30 |
31 |
32 | 不超过10个字符 33 |
34 |
35 | 44 |
45 | 资产单位 46 |
47 |
48 |
49 |
50 |
51 | 不超过5个字符 52 |
53 |
54 | 55 |
56 | 资产描述 57 |
58 | 59 |
60 |
61 | 62 | 63 | 65 | 83 | 84 | 90 | 92 | 98 | 100 |
101 |
102 | 103 |
104 |
105 |
106 |
107 |
108 |
-------------------------------------------------------------------------------- /ng_app/views/assetDetail.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 |
21 | 22 | 24 |
25 |
26 |
27 |

资产详情

28 |
29 |
30 |
31 | 32 | 33 |
34 |
35 | 资产名称 36 |
37 |
38 | {{asset.name}} 39 |
40 |
41 | 42 |
43 |
44 | 资产类型 45 |
46 |
47 | {{asset.type}} 48 |
49 |
50 | 51 |
52 |
53 | 资产发行总数量 54 |
55 |
56 | {{asset.amount}} 57 |
58 |
59 | 60 |
61 |
62 | 资产余额 63 |
64 |
65 | {{asset.balance}} 66 |
67 |
68 | 69 |
70 |
71 | 资产地址 72 |
73 |
74 | {{asset.asset_addr}} 75 |
76 |
77 | 78 |
79 |
80 | 发行方账户地址 81 |
82 |
83 | {{asset.account_addr}} 84 |
85 |
86 | 87 |
88 |
89 | 资产单位 90 |
91 |
92 | {{asset.unit}} 93 |
94 |
95 | 96 |
97 |
98 | 资产描述 99 |
100 |
101 | {{asset.description}} 102 |
103 |
104 | 105 |
106 |
107 | 资产创建时间 108 |
109 |
110 | {{asset.create_time}} 111 |
112 |
113 |
114 | -------------------------------------------------------------------------------- /ng_app/views/assetIssue.html: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 |
28 |
29 |
30 | 资产名称 31 |
32 | 34 |
35 |
36 |
37 | 资产地址 38 |
39 |
40 |
41 |
42 | 发行数量 43 |
44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 |
53 |
54 |
-------------------------------------------------------------------------------- /ng_app/views/assetList.html: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 |
25 | 26 |
27 | 资产类型 28 |
29 | 34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
资产名称资产数量资产类型创建时间
{{item.name}}{{item.balance}}{{item.type}}{{item.create_time}}
55 | 56 | 每页{{perPage}}条,共{{numPages}}页 57 | 58 |
-------------------------------------------------------------------------------- /ng_app/views/changePwd.html: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 30 | 34 |
-------------------------------------------------------------------------------- /ng_app/views/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 浙大区块链实验室 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | 51 | [电子钱包] 52 |
53 | 54 | 55 | 56 | {{account.phone}} 57 | 58 |
59 |
60 | 61 | 62 | 63 |
64 |
65 | 66 | 98 | 99 | 100 | 105 | 106 |
107 |
108 |
109 |
110 |
111 | 112 | 113 | 115 | 134 | 136 | 137 | 141 | 142 | -------------------------------------------------------------------------------- /ng_app/views/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 |
31 |
32 | 68 |
69 |
70 | 71 |
72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /ng_app/views/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 |
31 |
32 | 66 |
67 |
68 | 69 |
70 |
71 | 72 | 73 | -------------------------------------------------------------------------------- /ng_app/views/test.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 |
24 | 25 |

This is a test page!

26 |
-------------------------------------------------------------------------------- /ng_app/views/transactionDetail.html: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 |
18 |
19 |
20 |
21 |

交易详情

22 |
23 |
24 |
25 | 26 |
27 |
28 | 资产名称 29 |
30 |
31 | {{trx.asset_name}} 32 |
33 |
34 | 35 |
36 |
37 | 交易hash 38 |
39 |
40 | {{trx.hash}} 41 |
42 |
43 | 44 |
45 |
46 | {{trx.type}}数量 47 |
48 |
49 | {{trx.amount}} 50 |
51 |
52 | 53 |
54 |
55 | 交易时间 56 |
57 |
58 | {{trx.create_time}} 59 |
60 |
61 | 62 |
63 |
64 | 对方帐号 65 |
66 |
67 | {{trx.other_party.phone}} 68 |
69 |
70 | 71 |
72 |
73 | 对方地址 74 |
75 |
76 | {{trx.other_party.account_addr}} 77 |
78 |
79 | 80 |
81 |
82 | 发行方帐号 83 |
84 |
85 | {{trx.issuer.phone}} 86 |
87 |
88 | 89 |
90 |
91 | 发行方账户地址 92 |
93 |
94 | {{trx.issuer.account_addr}} 95 |
96 |
97 | 98 |
-------------------------------------------------------------------------------- /ng_app/views/transactionList.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 |
24 | 25 |
26 | 交易类型 27 |
28 | 34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
资产名称交易数量交易类型对方帐号交易时间
{{item.asset_name}}{{item.amount}}{{item.type}}{{item.other_party.phone}}{{item.create_time}}
56 | 每页{{perPage}}条,共{{numPages}}页 57 | 58 |
-------------------------------------------------------------------------------- /ng_app/views/transfer.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | 资产名称 23 |
24 | 25 | 26 |
27 |
28 |
29 | 资产地址 30 |
31 |
32 |
33 |
34 | 对方地址 35 |
36 |
37 |
38 |
39 | 资产数量 40 |
41 | 42 |
43 | {{balanceError}} 44 |
45 |
46 |
47 | 资产余额为{{balance}} 48 |
49 |
50 |
51 |
52 | 53 |
54 |
55 |
56 |
57 |
58 |
-------------------------------------------------------------------------------- /ng_app/views/transferConfirm.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ng_app/views/user.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |

基本信息

17 |
18 |
19 |
20 |
21 | 帐号 22 |
23 |
24 | {{user.account}} 25 |
26 |
27 |
28 |
29 | 地址 30 |
31 |
32 | {{user.address}} 33 |
34 |
35 |
36 |
37 | 帐号创建时间 38 |
39 |
40 | {{user.time}} 41 |
42 |
43 | 44 |
45 |
46 |

安全信息

47 |
48 |
49 | 60 |
61 |
62 | 密码 63 |
64 |
65 | 66 |
67 |
68 | 69 |
-------------------------------------------------------------------------------- /ng_app/views/verifyPwd.html: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 16 | 20 |
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "postinstall": "bower install", 7 | "prestart": "npm install", 8 | "start": "node ./bin/www" 9 | }, 10 | "dependencies": { 11 | "body-parser": "~1.13.2", 12 | "bower": "^1.8.0", 13 | "cookie-parser": "~1.3.5", 14 | "crypto": "0.0.3", 15 | "debug": "~2.2.0", 16 | "ethereumjs-util": "^5.1.2", 17 | "express": "~4.13.1", 18 | "express-jwt": "^3.4.0", 19 | "hpc-web3": "^0.1.29-alpha", 20 | "jade": "~1.11.0", 21 | "jwt-simple": "^0.5.0", 22 | "mongodb": ">= 1.3.19", 23 | "mongoose": ">= 3.8.0", 24 | "morgan": "~1.6.1", 25 | "passport": "^0.3.2", 26 | "passport-local": "^1.0.0", 27 | "request": "^2.72.0", 28 | "secp256k1": "^3.3.0", 29 | "serve-favicon": "~2.3.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /routes/Users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tyrion on 16-5-26. 3 | */ 4 | var express = require('express'); 5 | var Users = require('../models/Users').Users; 6 | var authToken = require('../models/Users').authToken; 7 | var jwt = require('jwt-simple'); 8 | var request = require('request'); 9 | var router = express.Router(); 10 | var crypto = require('crypto'); 11 | var ethereumUtil = require('ethereumjs-util'); 12 | var secp256k1 = require('secp256k1'); 13 | var GETH_URL = require('../help_utils/geth_level_utils').BASE_URL; 14 | var bcconf = require('../blockchain_lib/conf') 15 | 16 | var options = { 17 | method: 'POST', 18 | url: 'https://api.hyperchain.cn/v1/token/gtoken', 19 | formData: 20 | { 21 | client_id: bcconf.client_id, 22 | client_secret: bcconf.client_secret, 23 | phone: bcconf.phone, 24 | password: bcconf.password 25 | } 26 | }; 27 | 28 | router.post('/user', function (req, res) { 29 | console.log(typeof req.body); 30 | var phone = req.body.phone; 31 | var password = req.body.password; 32 | console.log(req.body.phone); 33 | console.log(req.body.password); 34 | if(!(req.body.phone && req.body.password)){ 35 | return res.status(400).send({ 36 | "status": "failed", 37 | "msg": "账号和密码不为空" 38 | }); 39 | } 40 | Users.findOne({"phone":phone},function(err, user){ 41 | //若未查询到,user为null,避免在回调中使用user.something 42 | console.log(err); 43 | console.log("find user:"+user); 44 | if(err){ 45 | return res.status(500).send({ 46 | "status": "failed", 47 | "msg": "Something wrong with Server" 48 | }); 49 | } 50 | if(user){ 51 | return res.status(403).send({ 52 | "status": "failed", 53 | "msg": "用户:"+phone+" 已被注册." 54 | }); 55 | } 56 | var new_user = new Users({ 57 | "phone": phone, 58 | "password": password, 59 | "create_time": new Date 60 | }); 61 | 62 | request(options, function (error, response, body) { 63 | if (error) throw new Error(error); 64 | 65 | console.log(body); 66 | console.log(body['token_type']) 67 | var obj = JSON.parse(body) 68 | var accessToken = obj['access_token']; 69 | // newKey = getNewKey() 70 | request({ 71 | url: "https://api.hyperchain.cn" + "/v1/dev/account/create" , 72 | method: "GET", 73 | json: true, 74 | headers: { 75 | "Accept": "Accept: text/html", 76 | "Authorization": accessToken 77 | } 78 | }, 79 | function (error, response, body) { 80 | if (!error && response.statusCode == 200) { 81 | var privatekey; 82 | do { 83 | privatekey = crypto.randomBytes(32); 84 | } while (!secp256k1.privateKeyVerify(privatekey)); 85 | var publicKey = ethereumUtil.privateToPublic(privatekey).toString('hex'); 86 | var address = "0x" + ethereumUtil.privateToAddress(privatekey).toString('hex'); 87 | console.log(body) 88 | console.log(body.address) 89 | address = body.address; 90 | // return { 91 | // "address": address, 92 | // "privateKey" : privatekey.toString('hex'), 93 | // "publicKey" : publicKey 94 | // } 95 | nextStep(address,privatekey) 96 | } else { 97 | console.log(response.statusCode); 98 | } 99 | }); 100 | }); 101 | function nextStep(address,privatekey) { 102 | new_user.account_addr = address; 103 | new_user.private_key = privatekey; 104 | new_user.save(function(err){ 105 | if (!err){ 106 | console.log("4"); 107 | var token = jwt.encode({ 108 | id: new_user.id, 109 | phone: new_user.phone, 110 | password: new_user.password, 111 | exp: Date.now()+1000*60*60*24*365*10 //10year 112 | }, "jwtTokenSecret"); 113 | var json_user=new_user.toJson(); 114 | json_user.token = token; 115 | return res.send({ 116 | "status":"ok", 117 | "msg":"创建成功", 118 | "data": json_user 119 | }); 120 | } else { 121 | return res.status(403).send({ 122 | "status":"failed", 123 | "msg":err 124 | }); 125 | } 126 | 127 | }); 128 | } 129 | }); 130 | }); 131 | 132 | 133 | router.get('/user',authToken, function(req, res){ 134 | console.log("req.body:"+req.body); 135 | console.log("req.header:"+req.headers); 136 | console.log("req.query:"+req.query); 137 | res.send({ 138 | data: req.user.toJson(), 139 | status: "ok" 140 | }) 141 | }); 142 | module.exports = router; 143 | -------------------------------------------------------------------------------- /routes/asset.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Vampire on 16-5-27. 3 | */ 4 | var express = require('express'); 5 | var Assets = require('../models/asset').Assets; 6 | var Transaction = require("../models/transaction").Transaction; 7 | var ASSET_TYPE = require("../models/asset").ASSET_TYPE; 8 | var verify_psw = require("../models/Users").verify_psw; 9 | var TRANSACTION_TYPE = require("../models/transaction").TRANSACTION_TYPE; 10 | var GETH_URL = require("../help_utils/geth_level_utils").BASE_URL; 11 | var blockchain_lib = require("../blockchain_lib/lib"); 12 | var checkForContractAddress = blockchain_lib.ForContractAddress; 13 | var newAsset = blockchain_lib.newAsset; 14 | var getTimestamp = blockchain_lib.getTimestamp; 15 | var random_16bits = blockchain_lib.random_16bits; 16 | var checkForContractTransaction = blockchain_lib.checkForContractTransaction; 17 | var issueAsset = blockchain_lib.issueAsset; 18 | 19 | var router = express.Router(); 20 | var request = require('request'); 21 | 22 | router.post('/', verify_psw, function(req, res) { 23 | console.log(JSON.stringify(req.user)) 24 | var name = req.body.name; 25 | var unit = req.body.unit; 26 | var description = req.body.description; 27 | 28 | //check the information 29 | if(!(name 30 | // && amount && logo 31 | && unit && description)){ 32 | return res.status(400).send({ 33 | 'status': 'failed', 34 | 'msg': 'please fill all fields!' 35 | }); 36 | 37 | //add fields check codes here 38 | 39 | } 40 | // request.post(GETH_URL+"/assets",{form:{address:req.user.account_addr}},function(error,response,body){ 41 | newAsset(req.user.account_addr, req.user.private_key, function(error, address){ 42 | if(!error && address){ 43 | var new_asset = new Assets({ 44 | name: name, 45 | unit: unit, 46 | description: description, 47 | logo: "http://fanyi.baidu.com/static/translation/img/header/logo_cbfea26.png", 48 | account_addr: req.user.account_addr, 49 | create_time: new Date, 50 | type: ASSET_TYPE.ISSUE 51 | }); 52 | new_asset.asset_addr = address; 53 | new_asset.save(); 54 | return res.send({ 55 | 'status': 'ok', 56 | 'msg': 'create new asset ok', 57 | 'data': new_asset.toJson() 58 | }); 59 | }else{ 60 | return res.status(412).send({ 61 | 'status': 'failed', 62 | 'msg': '创建资产失败,请重试', 63 | 'data': {} 64 | }) 65 | } 66 | }); 67 | }); 68 | 69 | /* 70 | var _Asset = new Schema({ 71 | name : { type: String, required: true 72 | // , unique: true TODO:资产名可以不唯一 73 | }, 74 | amount : { type: Number, required: true, default: 0}, 75 | unit : { type: String, required: true}, 76 | description : { type: String, required: true}, 77 | logo : { type: String, required: true}, 78 | account_addr : { type: String, required: true}, 79 | create_time : { type: Date, default: Date.now }, 80 | asset_addr: {type: String, required: true}, 81 | type: {type:String, required:true, default:ASSET_TYPE.ISSUE} 82 | }); 83 | */ 84 | router.get('/', function(req, res){ 85 | var type = req.query.type; 86 | var asset_addr = req.query.asset_addr;//查询某一特定资产 87 | if (asset_addr){ 88 | return Assets.findOne({asset_addr: asset_addr, account_addr: req.user.account_addr}, function (err, asset) { 89 | if(err){ 90 | return res.status(400).send({ 91 | status: "failed", 92 | msg: "something wrong with query asset" 93 | }) 94 | } 95 | if (asset){ 96 | return res.send({ 97 | status: "ok", 98 | data: asset.toJson() 99 | }) 100 | } 101 | }) 102 | } 103 | 104 | var filter = {'account_addr': req.user.account_addr}; 105 | switch(type){ 106 | case "issue": filter['type'] = ASSET_TYPE.ISSUE; break; 107 | case "receive": filter['type'] = ASSET_TYPE.RECEIVING;break; 108 | default:break; 109 | } 110 | 111 | return Assets.find(filter, function(err, assets){ 112 | if(assets[0]==null){ 113 | return res.send({ 114 | data: [], 115 | status: 'success' 116 | }); 117 | }else{ 118 | var asset_list = []; 119 | for (var i =0; i< assets.length; i++){ 120 | asset_list[i] = assets[i].toJson(); 121 | } 122 | return res.send({ 123 | data: asset_list, 124 | status: 'ok' 125 | }); 126 | } 127 | }); 128 | }); 129 | 130 | //发布资产 131 | router.put('/', verify_psw, function(req, res){ 132 | var asset_addr = req.body.asset_addr; 133 | var amount = req.body.amount || 0; 134 | if(!(asset_addr && amount)){ 135 | return res.status(400).send({ 136 | 'status': 'failed', 137 | 'msg': '请求数据错误' 138 | }); 139 | } 140 | Assets.find({'asset_addr': asset_addr, account_addr:req.user.account_addr}, null, {sort: [{'_id': -1}]}, function (err, assets) { 141 | if(assets[0]==null){ 142 | return res.status(404).send({ 143 | msg: '资产不存在', 144 | status: 'failed' 145 | }); 146 | }else if(assets[0].type!=ASSET_TYPE.ISSUE){ 147 | return res.status(412).send({ 148 | status:"failed", 149 | msg:"没有发行权" 150 | }) 151 | }else{ 152 | issueAsset(req.user.account_addr, req.user.private_key, asset_addr, amount, function(error, receipt){ 153 | if(!error && receipt.txHash){ 154 | var new_trsac = new Transaction({ 155 | from: req.user.account_addr, 156 | to: req.user.account_addr, 157 | amount: amount, 158 | create_time: new Date, 159 | asset_addr: asset_addr, 160 | type: TRANSACTION_TYPE.ISSUE, 161 | hash: receipt.txHash 162 | }); 163 | new_trsac.save(function(err){ 164 | console.log(err); 165 | }); 166 | assets[0].amount += amount; 167 | assets[0].balance += amount; 168 | assets[0].save(function(err){ 169 | console.log(err); 170 | }); 171 | return res.send({ 172 | status: 'ok', 173 | msg: "发布成功", 174 | data: assets[0].toJson() 175 | }); 176 | }else{ 177 | return res.send({ 178 | 'status': 'failed', 179 | 'msg': '发布资产失败,请重试', 180 | 'data': {} 181 | }) 182 | } 183 | }) 184 | } 185 | }) 186 | }); 187 | 188 | module.exports = router; 189 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | router.get('/', function (req, res) { 4 | res.sendfile("./ng_app/index.html"); 5 | }); 6 | 7 | router.get('/login', function (req, res) { 8 | res.sendfile("./ng_app/views/login.html"); 9 | }); 10 | 11 | 12 | router.get('/register', function (req, res) { 13 | res.sendfile("./ng_app/views/register.html"); 14 | }); 15 | 16 | router.get('/dashboard', function (req, res) { 17 | res.sendfile("./ng_app/views/dashboard.html"); 18 | }); 19 | 20 | module.exports.router = router; 21 | -------------------------------------------------------------------------------- /routes/password.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Tyrion on 2016/6/8. 3 | */ 4 | var express = require('express'); 5 | var Users = require('../models/Users').Users; 6 | var authToken = require('../models/Users').authToken; 7 | var veryfy_psw = require('../models/Users').verify_psw; 8 | var jwt = require('jwt-simple'); 9 | var router = express.Router(); 10 | 11 | router.put('/',authToken,veryfy_psw,function(req,res){ 12 | var new_psw = req.body.new_psw; 13 | if(!new_psw){ 14 | return res.status(400).send({ 15 | status:"failed", 16 | msg:"请输入新密码" 17 | }) 18 | }else{ 19 | req.user.password = new_psw; 20 | var token = jwt.encode({ 21 | id: req.user.id, 22 | phone: req.user.phone, 23 | password: req.user.password, 24 | exp: Date.now()+1000*60*60*24*365*10 //10year 25 | }, "jwtTokenSecret"); 26 | req.user.save(); 27 | return res.send({ 28 | status: "ok", 29 | msg: "密码修改成功", 30 | token:token 31 | }) 32 | } 33 | }); 34 | 35 | module.exports = router; -------------------------------------------------------------------------------- /routes/token.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Tyrion on 2016/5/25. 3 | */ 4 | var express = require('express'); 5 | var router = express.Router(); 6 | var Users = require('../models/Users').Users; 7 | var jwt = require('jwt-simple'); 8 | var app = require('../app'); 9 | 10 | router.post('/token', function (req, res) { 11 | //TODO validate req.body.username and req.body.password 12 | //if is invalid, return 401 13 | console.log(req.body.phone); 14 | console.log(req.body.password); 15 | Users.findOne({"phone":req.body.phone},function(err, user){ 16 | if(!user){ 17 | return res.status(404).send({ 18 | "status": "failed", 19 | "msg": "no such user" 20 | }) 21 | } 22 | user.comparePassword(req.body.password,function(is_match){ 23 | if(is_match){ 24 | console.log('enter if'); 25 | console.log("ready to run jwt.sign"); 26 | var token = jwt.encode({ 27 | id: user.id, 28 | phone: user.phone, 29 | password: user.password, 30 | exp: Date.now()+1000*60*60*24*365*10 //10year 31 | }, "jwtTokenSecret"); 32 | res.send({ token: token }); 33 | }else{ 34 | console.log('enter else'); 35 | res.status(401).send({ 36 | "status": "failed", 37 | "msg": "Wrong password,authenticate failed" 38 | }) 39 | } 40 | }) 41 | }); 42 | }); 43 | 44 | module.exports = router; -------------------------------------------------------------------------------- /routes/transaction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Vampire on 16-6-1. 3 | * Update by Tyrion on 16/6/2 4 | */ 5 | var express = require('express'); 6 | var Transaction = require('../models/transaction').Transaction; 7 | var User = require('../models/Users').Users; 8 | var verify_psw = require('../models/Users').verify_psw; 9 | var Asset = require("../models/asset").Assets; 10 | var ASSET_TYPE = require("../models/asset").ASSET_TYPE; 11 | var TRANSACTION_TYPE = require('../models/transaction').TRANSACTION_TYPE; 12 | var request = require('request'); 13 | var GETH_URL = require('../help_utils/geth_level_utils').BASE_URL; 14 | var blockchain_lib = require("../blockchain_lib/lib"); 15 | var checkForContractAddress = blockchain_lib.ForContractAddress; 16 | var newAsset = blockchain_lib.newAsset; 17 | var getTimestamp = blockchain_lib.getTimestamp; 18 | var random_16bits = blockchain_lib.random_16bits; 19 | var checkForContractTransaction = blockchain_lib.checkForContractTransaction; 20 | var newTransaction = blockchain_lib.newTransaction; 21 | var issueAsset = blockchain_lib.issueAsset; 22 | 23 | var router = new express.Router(); 24 | 25 | router.get('/',function(req, res){ 26 | console.log(req.query); 27 | var type = req.query.type; 28 | var filter = {}; 29 | console.log("user_id:"+req.user.id); 30 | switch(type){ 31 | case "issue": filter = { 32 | type:TRANSACTION_TYPE.ISSUE, 33 | from:req.user.account_addr 34 | }; break; 35 | case "receive": filter = { 36 | type:TRANSACTION_TYPE.TRANSACTION, 37 | to:req.user.account_addr 38 | }; break; 39 | case "transfer": filter = { 40 | type:TRANSACTION_TYPE.TRANSACTION, 41 | from:req.user.account_addr 42 | }; break; 43 | default:filter = {"$or":[{from:req.user.account_addr},{to:req.user.account_addr}]}; 44 | } 45 | console.log(filter); 46 | Transaction.find(filter, null, {sort: [{'_id': -1}]},function(err, transactions){ 47 | if(err) { 48 | return res.status(500).send({ 49 | "status": "failed", 50 | "msg": "Something wrong with Server" 51 | }); 52 | } 53 | console.log('123'); 54 | if(transactions[0]){ 55 | var tr_list=[]; 56 | //console.log(transactions); 57 | for (var i=0; i