├── CNAME ├── favicon.ico ├── screenshot.png ├── app ├── assets │ └── logo.png ├── index.html ├── main.js └── App.vue ├── dist ├── element.53a05c943ce6effb2bf0.js ├── 6f0a76321d30f3c8120915e57f7bd77e.ttf ├── element.53a05c943ce6effb2bf0.js.map └── index.html ├── .eslintignore ├── truffle-config.js ├── migrations ├── 2_deploy_contracts.js └── 1_initial_migration.js ├── .gitignore ├── .editorconfig ├── .babelrc ├── truffle.js ├── .eslintrc ├── index.html ├── contracts ├── Migrations.sol └── SecretNote.sol ├── package.json ├── webpack.config.js ├── README.md └── test └── SecretNote.js /CNAME: -------------------------------------------------------------------------------- 1 | www.secret-note.one -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenspirit/secret-note/HEAD/favicon.ico -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenspirit/secret-note/HEAD/screenshot.png -------------------------------------------------------------------------------- /app/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenspirit/secret-note/HEAD/app/assets/logo.png -------------------------------------------------------------------------------- /dist/element.53a05c943ce6effb2bf0.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([2],[],[656]); 2 | //# sourceMappingURL=element.53a05c943ce6effb2bf0.js.map -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/** 2 | node_modules/** 3 | contracts/** 4 | migrations/1_initial_migration.js 5 | migrations/2_deploy_contracts.js 6 | -------------------------------------------------------------------------------- /dist/6f0a76321d30f3c8120915e57f7bd77e.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenspirit/secret-note/HEAD/dist/6f0a76321d30f3c8120915e57f7bd77e.ttf -------------------------------------------------------------------------------- /dist/element.53a05c943ce6effb2bf0.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"element.53a05c943ce6effb2bf0.js","sourceRoot":""} -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // See 3 | // to customize your Truffle configuration! 4 | }; 5 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var SecretNote = artifacts.require('SecretNote'); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(SecretNote); 5 | } 6 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | yarn-error.log 5 | 6 | # Editor directories and files 7 | .idea 8 | .vscode 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Decentralized Secret Note by blockchain 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-3" 5 | ], 6 | "plugins": [["component", [ 7 | { 8 | "libraryName": "element-ui", 9 | "styleLibraryName": "theme-chalk" 10 | } 11 | ]]] 12 | } 13 | -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | import { Message, Notification } from 'element-ui'; 5 | 6 | Vue.prototype.$message = Message; 7 | Vue.prototype.$notify = Notification; 8 | 9 | new Vue({ 10 | el: '#app', 11 | render: h => h(App) 12 | }) 13 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // See 3 | // to customize your Truffle configuration! 4 | networks: { 5 | development: { 6 | host: "127.0.0.1", 7 | port: 7545, 8 | network_id: "*" // Match any network id 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "standard" 5 | ], 6 | "plugins": [ 7 | "babel" 8 | ], 9 | "rules": { 10 | "key-spacing" : 0, 11 | "jsx-quotes" : [2, "prefer-single"], 12 | "max-len" : [2, 120, 2], 13 | "object-curly-spacing" : [2, "always"] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Decentralized Secret Note by blockchain 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Decentralized Secret Note by blockchain 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secret-note", 3 | "version": "1.0.0", 4 | "description": "Decentralized Secret Note powered by blockchain", 5 | "scripts": { 6 | "lint": "./node_modules/.bin/eslint ./", 7 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 8 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules && cp dist/index.html index.html" 9 | }, 10 | "author": "Ken Chen", 11 | "license": "MIT", 12 | "dependencies": { 13 | "base-58": "0.0.1", 14 | "buffer": "^5.0.8", 15 | "element-ui": "^2.0.11", 16 | "ipfs-api": "^17.3.0", 17 | "jsencrypt": "^2.3.1", 18 | "vue": "^2.5.11" 19 | }, 20 | "browserslist": [ 21 | "> 1%", 22 | "last 2 versions", 23 | "not ie <= 8" 24 | ], 25 | "devDependencies": { 26 | "babel-cli": "^6.22.2", 27 | "babel-core": "^6.26.0", 28 | "babel-eslint": "^8.0.0", 29 | "babel-loader": "^7.1.2", 30 | "babel-plugin-component": "^1.1.0", 31 | "babel-preset-env": "^1.6.0", 32 | "babel-preset-stage-3": "^6.24.1", 33 | "babel-register": "^6.22.0", 34 | "cross-env": "^5.0.5", 35 | "css-loader": "^0.28.7", 36 | "eslint": "^4.1.0", 37 | "eslint-config-babel": "^7.0.0", 38 | "eslint-plugin-flowtype": "^2.25.0", 39 | "eslint-plugin-mocha": "^4.8.0", 40 | "eslint-plugin-promise": "^3.0.0", 41 | "eslint-plugin-standard": "^2.0.0", 42 | "file-loader": "^1.1.4", 43 | "html-webpack-plugin": "^2.30.1", 44 | "json-loader": "^0.5.7", 45 | "truffle-contract": "^3.0.2", 46 | "uglifyjs-webpack-plugin": "^1.1.6", 47 | "url-loader": "^0.6.2", 48 | "vue-loader": "^13.0.5", 49 | "vue-template-compiler": "^2.4.4", 50 | "web3": "^0.20.4", 51 | "webpack": "^3.10.0", 52 | "webpack-dev-server": "^2.9.1" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 5 | 6 | module.exports = { 7 | entry: './app/main.js', 8 | output: { 9 | path: path.resolve(__dirname, './dist'), 10 | publicPath: '/dist/', 11 | filename: 'build.js' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.css$/, 17 | use: [ 18 | 'vue-style-loader', 19 | 'css-loader' 20 | ], 21 | }, { 22 | test: /\.vue$/, 23 | loader: 'vue-loader', 24 | options: { 25 | loaders: { 26 | } 27 | // other vue-loader options go here 28 | } 29 | }, 30 | { 31 | test: /\.js$/, 32 | loader: 'babel-loader', 33 | exclude: /node_modules/ 34 | }, 35 | { 36 | test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/, 37 | use: [{ 38 | loader: 'url-loader', 39 | options: { 40 | limit: 10000 41 | } 42 | }] 43 | } 44 | ] 45 | }, 46 | resolve: { 47 | alias: { 48 | 'vue$': 'vue/dist/vue.esm.js' 49 | }, 50 | extensions: ['*', '.js', '.vue', '.json'] 51 | }, 52 | devServer: { 53 | historyApiFallback: true, 54 | noInfo: true, 55 | overlay: true 56 | }, 57 | performance: { 58 | hints: false 59 | }, 60 | devtool: '#eval-source-map' 61 | } 62 | 63 | if (process.env.NODE_ENV === 'production') { 64 | function isExternal(mdl, vendorName) { 65 | var context = mdl.context; 66 | 67 | if (typeof context !== 'string') { 68 | return false; 69 | } 70 | 71 | return context.indexOf('node_modules') !== -1 && 72 | (!vendorName || context.indexOf(vendorName) !== -1); 73 | } 74 | 75 | module.exports.devtool = '#source-map'; 76 | module.exports.entry = { 77 | ipfs: 'ipfs-api', 78 | element: 'element-ui', 79 | app: './app/main.js' 80 | }; 81 | module.exports.output = { 82 | path: path.resolve(__dirname, './dist'), 83 | publicPath: '/dist/', 84 | filename: '[name].[chunkhash].js', 85 | chunkFilename: '[id].[chunkhash].js' 86 | }; 87 | // http://vue-loader.vuejs.org/en/workflow/production.html 88 | module.exports.plugins = (module.exports.plugins || []).concat([ 89 | new webpack.DefinePlugin({ 90 | 'process.env': { 91 | NODE_ENV: '"production"' 92 | } 93 | }), 94 | new HtmlWebpackPlugin({ 95 | template: './app/index.html', 96 | filename: 'index.html', 97 | inject: 'body' 98 | }), 99 | // split vendor js into its own file 100 | new webpack.optimize.CommonsChunkPlugin({ 101 | names: ['ipfs', 'element'], 102 | minChunks: Infinity 103 | }), 104 | new webpack.optimize.CommonsChunkPlugin({ 105 | name: 'vendors', 106 | minChunks: function(mdl) { 107 | return isExternal(mdl); 108 | } 109 | }), 110 | // extract webpack runtime and module manifest to its own file in order to 111 | // prevent vendor hash from being updated whenever app bundle is updated 112 | // new webpack.optimize.CommonsChunkPlugin({ 113 | // name: 'manifest', 114 | // chunks: ['vendor'] 115 | // }), 116 | new UglifyJsPlugin({ 117 | sourceMap: true, 118 | uglifyOptions: { 119 | ecma: 8, 120 | compress: { 121 | warnings: false 122 | } 123 | } 124 | }), 125 | new webpack.LoaderOptionsPlugin({ 126 | minimize: true 127 | }) 128 | ]) 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decentralized Secret Note 2 | 3 | ## Why am I doing this? 4 | 5 | Blockchain development has always been mysterious to me before. Lately I finally got a chance to look into it. 6 | 7 | Really learning the blockchain and make something genuinely useful in decentralized scenario is quite difficult. This idea comes to me because many so-promised life-long storage service can suddenly terminated. Hence, a publicly accepted blockchain seems a perfect place to put something that you never want to lose. Your information is never in control of some particular companies. 8 | 9 | 10 | ## Is it really decentralized and how safe is my secret? 11 | 12 | [IPFS]: https://ipfs.io/ 13 | 14 | All your secret notes have their own name/key which are stored in a Smart Contract in Ethereum. Each of it points to an address in [IPFS][], which is a distributed web. The backbone of this tool is completely decentralized and last forever unless Ethereum and IPFS themselves vanished. This tool itself is open-source and hosted in Github. You are freely to make a backup and host yourself if you need further assurance. 15 | 16 | The concern you may have is that the data in public blockchain is viewable by anyone. However, through normal access, each Ethereum address should only be able to see the note names stored by this address only. Even if your notes' names are revealed, so others can get the address in IPFS, they still cannot see your secret because they are protected by your private key (HTTPS private key, NOT WALLET private key). And this key should be known by yourself only. That is why I emphasize that "**IF LOST, NO ONE is able to restore your notes**". 17 | 18 | 19 | ## How to use it 20 | 21 | [MetaMask]: https://metamask.io/ 22 | 23 | The authentication of using this tool is completely based on [MetaMask][] which is another great Dapp in Ethereum. Hence, this tool DOES NOT require you to input any password or provide your wallet private key. And for god's sake, you should not provide those info to anybody. 24 | 25 | Once you installed MetaMask in any of your favorite browser and connect your account to Ethereum network, you should see similar screen as below. 26 | 27 | ![Screen Shot](./screenshot.png) 28 | 29 | [0xb01b98a50781c454c9daa3d43eb5399ff5b604ee]: https://etherscan.io/address/0xb01b98a50781c454c9daa3d43eb5399ff5b604ee#code 30 | 31 | The Ethereum network connected to and what account is currently autenticated are shown at the top of the screen. The tool hosted can only be used in Main network as the smart contract is deployed there at [0xb01b98a50781c454c9daa3d43eb5399ff5b604ee][]. 32 | 33 | Usage steps: 34 | 35 | 1. Input your own SSH-generated private key or ad-hoc generate one. 36 | 2. Backup this private key no matter you decided to remember locally in your browser or not. 37 | 3. Input note name and content, press Save. 38 | 4. MetaMask will popup (this might be a little slow) and ask you to confirm the transaction to save your note to Ethereum smart contract. 39 | 5. You only have to pay Gas for saving data in ETH blockchain. 40 | 6. Once your transaction is submitted and confirmed, your note will be listed at the right hand side. 41 | 7. Everytime you login using this ETH account and connected to Main net, you should see all your secrets. Click on any item to view. 42 | 43 | _Notes: saving & seeing data must have your private key inplace. Viewing data doesn't need to pay Gas. Only changing data requires it._ 44 | 45 | 46 | ## Donation & Encouragement 47 | 48 | Blockchain & Dapp development is really exciting. I hope this tool can benefit any blockchain enthusiast as you. 49 | 50 | Donation of any ERC20 token to 51 | `0x713C8C77112858A3bd14A5FB380Fa0c4c5b1A8Bd` is greatly appreciated. I wonder what kind of token I can get. ;) 52 | 53 | Or send Bitcoin to `196XA8S8ZwBu7UNap2A84cLzCAKoPPGck3` if you are such a generous rich in blockchain world. :D 54 | 55 | 56 | ## For developer 57 | 58 | If you are a developer, I think you can easily and freely use the source code here to use any Test network and store your secret note there. You have complete control and what is more important is that you do not need to pay real money for it. Enjoy. :D 59 | 60 | ### Build Setup 61 | 62 | ``` bash 63 | # install dependencies 64 | npm install 65 | 66 | # serve with hot reload at localhost:8080 67 | npm run dev 68 | 69 | # build for production with minification 70 | npm run build 71 | ``` 72 | -------------------------------------------------------------------------------- /contracts/SecretNote.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | /** 4 | * @title Ownable 5 | * @dev The Ownable contract has an owner address, and provides basic authorization control 6 | * functions, this simplifies the implementation of "user permissions". 7 | */ 8 | contract Ownable { 9 | address owner; 10 | 11 | /** 12 | * @dev The Ownable constructor sets the original `owner` of the contract to the sender 13 | * account. 14 | */ 15 | function Ownable() public { 16 | owner = msg.sender; 17 | } 18 | 19 | /** 20 | * @dev Throws if called by any account other than the owner. 21 | */ 22 | modifier onlyOwner() { 23 | require(owner == msg.sender); 24 | _; 25 | } 26 | } 27 | 28 | /** 29 | * @title Secret Note 30 | */ 31 | contract SecretNote is Ownable { 32 | struct UserInfo { 33 | mapping(bytes32 => bytes32) notes; 34 | bytes32[] noteKeys; 35 | uint256 index; // 1-based 36 | } 37 | 38 | mapping(address => UserInfo) private registerUsers; 39 | address[] private userIndex; 40 | 41 | event SecretNoteUpdated(address indexed _sender, bytes32 indexed _noteKey, bool _success); 42 | 43 | function SecretNote() public { 44 | } 45 | 46 | function userExisted(address _user) public constant returns (bool) { 47 | if (userIndex.length == 0) { 48 | return false; 49 | } 50 | 51 | return (userIndex[registerUsers[_user].index - 1] == _user); 52 | } 53 | 54 | function () public payable { 55 | } 56 | 57 | /** 58 | * @dev for owner to withdraw ETH from donators if there is any. :) 59 | * @param _to The address where withdraw to 60 | * @param _amount The amount of ETH to withdraw 61 | */ 62 | function withdraw(address _to, uint _amount) public onlyOwner { 63 | _to.transfer(_amount); 64 | } 65 | 66 | /** 67 | * @dev For owner to check registered user count 68 | */ 69 | function getUserCount() public view onlyOwner returns (uint256) { 70 | return userIndex.length; 71 | } 72 | 73 | /** 74 | * @dev For owner to check registered user address based on index 75 | * @param _index Starting from 1 76 | */ 77 | function getUserAddress(uint256 _index) public view onlyOwner returns (address) { 78 | require(_index > 0); 79 | return userIndex[_index - 1]; 80 | } 81 | 82 | /** 83 | * @dev For user to get their own secret note 84 | * @param _noteKey The key identifier for particular note 85 | */ 86 | function getNote(bytes32 _noteKey) public view returns (bytes32) { 87 | return registerUsers[msg.sender].notes[_noteKey]; 88 | } 89 | 90 | /** 91 | * @dev For user to get their own secret note keys count 92 | */ 93 | function getNoteKeysCount() public view returns (uint256) { 94 | return registerUsers[msg.sender].noteKeys.length; 95 | } 96 | 97 | /** 98 | * @dev For user to get their own secret note key by index 99 | * @param _index The 0-based index for particular note 100 | */ 101 | function getNoteKeyByIndex(uint256 _index) public view returns (bytes32) { 102 | return registerUsers[msg.sender].noteKeys[_index]; 103 | } 104 | 105 | /** 106 | * @dev For user to update their own secret note 107 | * @param _noteKey The key identifier for particular note 108 | * @param _content The note path hash 109 | */ 110 | function setNote(bytes32 _noteKey, bytes32 _content) public payable { 111 | require(_noteKey != ""); 112 | require(_content != ""); 113 | 114 | var userAddr = msg.sender; 115 | var user = registerUsers[userAddr]; 116 | if (user.notes[_noteKey] == "") { 117 | user.noteKeys.push(_noteKey); 118 | } 119 | user.notes[_noteKey] = _content; 120 | 121 | if (user.index == 0) { 122 | userIndex.push(userAddr); 123 | user.index = userIndex.length; 124 | } 125 | SecretNoteUpdated(userAddr, _noteKey, true); 126 | } 127 | 128 | /** 129 | * @dev Destroy one's account 130 | */ 131 | function destroyAccount() public returns (bool) { 132 | var userAddr = msg.sender; 133 | require(userExisted(userAddr)); 134 | 135 | uint delIndex = registerUsers[userAddr].index; 136 | address userToMove = userIndex[userIndex.length - 1]; 137 | 138 | if (userToMove == userAddr) { 139 | delete(registerUsers[userAddr]); 140 | userIndex.length = 0; 141 | return true; 142 | } 143 | 144 | userIndex[delIndex - 1] = userToMove; 145 | registerUsers[userToMove].index = delIndex; 146 | userIndex.length--; 147 | delete(registerUsers[userAddr]); 148 | return true; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /test/SecretNote.js: -------------------------------------------------------------------------------- 1 | var SecretNote = artifacts.require('SecretNote'); 2 | 3 | contract('SecretNote', function(accounts) { 4 | it('Fallback function available to accept transfer', function() { 5 | var contractAccount = accounts[0]; 6 | var transferAmount = web3.toWei(1, "ether"); 7 | var originalBalance; 8 | 9 | return SecretNote.deployed() 10 | .then(function(instance) { 11 | originalBalance = web3.eth.getBalance(contractAccount); 12 | return web3.eth.sendTransaction({ 13 | from: accounts[1], 14 | to: contractAccount, 15 | value: transferAmount, 16 | gas: 820621 17 | }); 18 | }) 19 | .then(function() { 20 | var newBalance = web3.eth.getBalance(contractAccount); 21 | assert.equal(newBalance.minus(transferAmount).equals(originalBalance), true); 22 | }) 23 | }); 24 | 25 | it('User count should be 0 initially', function() { 26 | return SecretNote.deployed() 27 | .then(function(instance) { 28 | return instance.getUserCount() 29 | .then(function(userCount) { 30 | return assert.equal(userCount, 0); 31 | }); 32 | }); 33 | }); 34 | 35 | it('User count should be 1 after first setNote', function() { 36 | return SecretNote.deployed() 37 | .then(function(instance) { 38 | var noteKey = 'key1'; 39 | var noteContent = 'key2'; 40 | return instance.setNote(noteKey, noteContent, { from: accounts[1] }) 41 | .then(function() { 42 | return instance.getUserCount(); 43 | }) 44 | .then(function(userCount) { 45 | return assert.equal(userCount, 1); 46 | }); 47 | }); 48 | }); 49 | 50 | it('User at index 1 should be the first setNote user.', function() { 51 | return SecretNote.deployed() 52 | .then(function(instance) { 53 | var noteKey = 'key1'; 54 | var noteContent = 'key2'; 55 | return instance.setNote(noteKey, noteContent, { from: accounts[1] }) 56 | .then(function() { 57 | return instance.getUserAddress(1); 58 | }) 59 | .then(function(addr) { 60 | return assert.equal(addr, accounts[1]); 61 | }); 62 | }); 63 | }); 64 | 65 | it('Should set & retrieve note correctly', function() { 66 | return SecretNote.deployed() 67 | .then(function(instance) { 68 | var noteKey = web3.toHex('key1'); 69 | var noteContent = 'key2'; 70 | return instance.setNote(noteKey, web3.toHex(noteContent), { from: accounts[1] }) 71 | .then(function() { 72 | return instance.getNote(noteKey, { from: accounts[1] }); 73 | }) 74 | .then(function(savedContent) { 75 | return assert.equal( 76 | web3.toAscii(savedContent).replace(/\u0000/g, ''), noteContent); 77 | }); 78 | }); 79 | }); 80 | 81 | it('Note count should be correct after setting note', function() { 82 | return SecretNote.deployed() 83 | .then(function(instance) { 84 | var noteKey = web3.toHex('key1'); 85 | var noteContent = 'key2'; 86 | return instance.setNote(noteKey, web3.toHex(noteContent), { from: accounts[1] }) 87 | .then(function() { 88 | return instance.getNoteKeysCount.call({ from: accounts[1] }); 89 | }) 90 | .then(function(count) { 91 | return assert.equal(count.valueOf(), 1); 92 | }); 93 | }); 94 | }); 95 | 96 | it('Note key should be able to retrieved by index', function() { 97 | return SecretNote.deployed() 98 | .then(function(instance) { 99 | var noteKey = 'key1'; 100 | var noteContent = 'key2'; 101 | return instance.setNote(web3.toHex(noteKey), web3.toHex(noteContent), { from: accounts[1] }) 102 | .then(function() { 103 | return instance.getNoteKeyByIndex.call(0, { from: accounts[1] }); 104 | }) 105 | .then(function(savedKey) { 106 | return assert.equal( 107 | web3.toAscii(savedKey).replace(/\u0000/g, ''), noteKey); 108 | }); 109 | }); 110 | }); 111 | 112 | it('Destroy account should clear user\'s note', function() { 113 | return SecretNote.deployed() 114 | .then(function(instance) { 115 | var noteKey = 'key1'; 116 | var noteContent = 'key2'; 117 | return instance.setNote(web3.toHex(noteKey), web3.toHex(noteContent), { from: accounts[1] }) 118 | .then(function() { 119 | return instance.destroyAccount({ from: accounts[1] }); 120 | }) 121 | .then(function() { 122 | return instance.getNoteKeysCount.call({ from: accounts[1] }); 123 | }) 124 | .then(function(count) { 125 | return assert.equal(count.valueOf(), 0); 126 | }); 127 | }); 128 | }); 129 | 130 | it('Destroy account should move another user\'s index correctly', function() { 131 | return SecretNote.deployed() 132 | .then(function(instance) { 133 | var contractAccount = accounts[0]; 134 | var accountToRemove = accounts[1]; 135 | var accountToMove = accounts[2]; 136 | 137 | var noteKey = 'key1'; 138 | var noteContent = 'key2'; 139 | var userCountAfterDestroy; 140 | return instance.setNote(web3.toHex(noteKey), web3.toHex(noteContent), { from: accountToRemove }) 141 | .then(function() { 142 | return instance.setNote(web3.toHex(noteKey), web3.toHex(noteContent), { from: accountToMove }) 143 | }) 144 | .then(function() { 145 | return instance.destroyAccount({ from: accountToRemove }); 146 | }) 147 | .then(function() { 148 | return instance.getUserCount(); 149 | }) 150 | .then(function(userCount) { 151 | userCountAfterDestroy = userCount.valueOf(); 152 | return instance.getUserAddress.call(1, { from: contractAccount }); 153 | }) 154 | .then(function(remainedAccount) { 155 | assert.equal(userCountAfterDestroy, 1); 156 | assert.equal(remainedAccount, accountToMove); 157 | }); 158 | }); 159 | }); 160 | }); 161 | -------------------------------------------------------------------------------- /app/App.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 468 | 469 | 542 | --------------------------------------------------------------------------------