├── src ├── assets │ ├── .gitkeep │ ├── background.jpg │ ├── fonts │ │ ├── Domine-Bold.ttf │ │ ├── Domine-Regular.ttf │ │ ├── Montserrat-Black.ttf │ │ ├── Montserrat-Bold.ttf │ │ ├── Montserrat-Light.ttf │ │ ├── Montserrat-Thin.ttf │ │ └── Montserrat-Regular.ttf │ └── font-awesome-4.7.0 │ │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ │ ├── less │ │ ├── screen-reader.less │ │ ├── fixed-width.less │ │ ├── larger.less │ │ ├── list.less │ │ ├── core.less │ │ ├── stacked.less │ │ ├── font-awesome.less │ │ ├── bordered-pulled.less │ │ ├── rotated-flipped.less │ │ ├── path.less │ │ ├── animated.less │ │ └── mixins.less │ │ ├── scss │ │ ├── _fixed-width.scss │ │ ├── _screen-reader.scss │ │ ├── _larger.scss │ │ ├── _list.scss │ │ ├── _core.scss │ │ ├── font-awesome.scss │ │ ├── _stacked.scss │ │ ├── _bordered-pulled.scss │ │ ├── _rotated-flipped.scss │ │ ├── _path.scss │ │ ├── _animated.scss │ │ └── _mixins.scss │ │ └── HELP-US-OUT.txt ├── app │ ├── components │ │ ├── list-papers │ │ │ ├── list-papers.component.scss │ │ │ ├── manuscript-voting-status.ts │ │ │ ├── manuscript-view-model.ts │ │ │ ├── list-papers.component.html │ │ │ ├── list-papers.component.spec.ts │ │ │ └── list-papers.component.ts │ │ ├── how-it-works │ │ │ ├── how-it-works.component.scss │ │ │ ├── how-it-works.component.html │ │ │ └── how-it-works.component.ts │ │ ├── become-a-reviewer │ │ │ ├── become-a-reviewer.component.scss │ │ │ ├── become-a-reviewer.component.html │ │ │ └── become-a-reviewer.component.ts │ │ ├── submit-paper │ │ │ ├── submit-paper-modal │ │ │ │ ├── submit-paper-modal.component.scss │ │ │ │ ├── submit-paper-modal.component.html │ │ │ │ ├── submit-paper-modal.component.ts │ │ │ │ └── submit-paper-modal.component.spec.ts │ │ │ ├── submit-paper.component.scss │ │ │ ├── insufficient-balance-modal │ │ │ │ ├── insufficient-balance-modal.component.scss │ │ │ │ ├── insufficient-balance-modal.component.html │ │ │ │ ├── insufficient-balance-modal.component.spec.ts │ │ │ │ └── insufficient-balance-modal.component.ts │ │ │ ├── submit-paper.component.html │ │ │ ├── submit-paper.component.ts │ │ │ └── submit-paper.component.spec.ts │ │ ├── home │ │ │ ├── home.component.ts │ │ │ ├── home.component.html │ │ │ ├── home.component.spec.ts │ │ │ └── home.component.scss │ │ └── network-status │ │ │ ├── network-status.component.scss │ │ │ ├── network-status.component.html │ │ │ ├── network-status.component.ts │ │ │ └── network-status.component.spec.ts │ ├── providers │ │ ├── web3 │ │ │ ├── web3 │ │ │ │ ├── web3.token.ts │ │ │ │ └── web3.factory.ts │ │ │ ├── web3-provider │ │ │ │ ├── web3-provider.token.ts │ │ │ │ └── web3-provider.factory.ts │ │ │ ├── web3-network-id │ │ │ │ ├── web3-network-id.token.ts │ │ │ │ └── web3-network-id.factory.ts │ │ │ ├── web3-helper │ │ │ │ ├── web3-helper.service.spec.ts │ │ │ │ └── web3-helper.service.ts │ │ │ ├── web3-monitor │ │ │ │ ├── web3-network-status.ts │ │ │ │ ├── web3-monitor.service.spec.ts │ │ │ │ └── web3-monitor.service.ts │ │ │ ├── web3-account │ │ │ │ └── web3-account.service.ts │ │ │ └── web3-client │ │ │ │ ├── web3-client.service.spec.ts │ │ │ │ └── web3-client.service.ts │ │ ├── contracts │ │ │ ├── aletheia-promise.token.ts │ │ │ ├── minimal-manuscript-promise.token.ts │ │ │ └── contract-loader.service.ts │ │ ├── config │ │ │ └── config.factory.ts │ │ ├── file-helper │ │ │ └── file-helper.ts │ │ ├── error-handler │ │ │ ├── error-handler.service.ts │ │ │ └── error-handler.service.spec.ts │ │ ├── encoding-helper │ │ │ ├── encoding-helper.spec.ts │ │ │ └── encoding-helper.ts │ │ ├── electron.service.ts │ │ └── ipfs │ │ │ └── ipfs-client │ │ │ └── ipfs-client.service.ts │ ├── Injection-tokens.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app-routing.module.ts │ └── app.module.ts ├── favicon.ico ├── favicon.icns ├── theme │ ├── modal.scss │ ├── notifications.scss │ ├── variables-and-mixins.scss │ └── type.scss ├── environments │ ├── index.ts │ └── index.prod.ts ├── index.html ├── tsconfig.app.json ├── typings.d.ts ├── main.ts ├── tsconfig.spec.json ├── test.ts ├── styles.scss └── polyfills.ts ├── postcss.config.js ├── test ├── data │ ├── test.txt │ ├── test2.txt │ └── spacer.gif ├── mocha.opts ├── helpers │ ├── test-encoding-helper.js │ ├── expectThrow.js │ └── expectRevert.js ├── Reputation.spec.js ├── Accessible.spec.js ├── ManuscriptFactory.spec.js ├── Aletheia.spec.js └── CommunityVotes.spec.js ├── scripts ├── install-geth-osx.sh ├── env-testnet.sh ├── attach-geth-testnet.sh ├── test-truffle-ci.sh ├── get-testnet-addresses.sh ├── ipfs-local.sh ├── add-balance-to-test-account.js ├── init-testnet.sh ├── install-ubuntu.sh ├── ipfs-local.js ├── start-geth-testnet.sh ├── testnet-bootnode │ └── setup-bootnode-debian.sh ├── add-test-data.js └── mine.js ├── migrations ├── 0001-initial-migration.js ├── 0002-deploy-Ownable.js ├── 0002-deploy-ReputationStorage.js └── 0003-deploy-Aletheia.js ├── .gitignore ├── CONTRIBUTING.md ├── config ├── Config.ts ├── default.js └── test │ └── genesis.json ├── truffle.js ├── contracts ├── Migrations.sol ├── Reputation.sol ├── Manuscript.sol ├── ManuscriptFactory.sol ├── Accessible.sol ├── ManuscriptIndex.sol ├── CloneFactory.sol ├── CommunityVotes.sol ├── Aletheia.sol ├── CheapManuscript.sol └── MinimalManuscript.sol ├── tsconfig.json ├── Bootnode.Jenkinsfile ├── PULL_REQUEST_TEMPLATE.md ├── .angular-cli.json ├── karma.conf.js ├── package.js ├── Jenkinsfile ├── main.ts ├── main.js ├── tslint.json ├── ISSUE_TEMPLATE.md └── package.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; -------------------------------------------------------------------------------- /test/data/test.txt: -------------------------------------------------------------------------------- 1 | this is a test 2 | -------------------------------------------------------------------------------- /test/data/test2.txt: -------------------------------------------------------------------------------- 1 | this is a test 2 2 | -------------------------------------------------------------------------------- /src/app/components/list-papers/list-papers.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/how-it-works/how-it-works.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/become-a-reviewer/become-a-reviewer.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --globals window 2 | --timeout 20000 3 | app/**/*.spec.js 4 | -------------------------------------------------------------------------------- /src/app/components/how-it-works/how-it-works.component.html: -------------------------------------------------------------------------------- 1 |

How it works

-------------------------------------------------------------------------------- /src/app/components/submit-paper/submit-paper-modal/submit-paper-modal.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/become-a-reviewer/become-a-reviewer.component.html: -------------------------------------------------------------------------------- 1 |

Become a Reviewer

-------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/favicon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/favicon.icns -------------------------------------------------------------------------------- /src/app/components/submit-paper/submit-paper.component.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | text-align: center; 3 | } -------------------------------------------------------------------------------- /test/data/spacer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/test/data/spacer.gif -------------------------------------------------------------------------------- /src/assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/background.jpg -------------------------------------------------------------------------------- /src/assets/fonts/Domine-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/fonts/Domine-Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Domine-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/fonts/Domine-Regular.ttf -------------------------------------------------------------------------------- /scripts/install-geth-osx.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | brew update 3 | brew upgrade 4 | brew tap ethereum/ethereum 5 | brew install ethereum 6 | -------------------------------------------------------------------------------- /src/assets/fonts/Montserrat-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/fonts/Montserrat-Black.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/fonts/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Montserrat-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/fonts/Montserrat-Light.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Montserrat-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/fonts/Montserrat-Thin.ttf -------------------------------------------------------------------------------- /scripts/env-testnet.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | export TEST_DATA_DIR=.ethereum-test 4 | export TEST_NET_PASSWORD=.testnet-password 5 | -------------------------------------------------------------------------------- /src/assets/fonts/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/fonts/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /scripts/attach-geth-testnet.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$(pwd)/scripts/env-testnet.sh" 4 | 5 | geth attach ipc:$TEST_DATA_DIR/geth.ipc 6 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3/web3.token.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core' 2 | 3 | export const Web3Token = new InjectionToken('any') 4 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3/web3.factory.ts: -------------------------------------------------------------------------------- 1 | import * as Web3 from 'web3' 2 | export function web3Factory(provider) { 3 | return new Web3(provider) 4 | } 5 | -------------------------------------------------------------------------------- /src/app/providers/contracts/aletheia-promise.token.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core' 2 | 3 | export const AletheiaPromise = new InjectionToken('any') 4 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3-provider/web3-provider.token.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core' 2 | 3 | export const Web3Provider = new InjectionToken('any') 4 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/font-awesome-4.7.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /migrations/0001-initial-migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require('./Migrations.sol') 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations) 5 | } 6 | -------------------------------------------------------------------------------- /scripts/test-truffle-ci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | npm run ethereum-local & 3 | echo $! >> testrpc.pid 4 | ./node_modules/.bin/truffle test 5 | kill -9 `cat testrpc.pid` 6 | rm testrpc.pid -------------------------------------------------------------------------------- /src/app/providers/contracts/minimal-manuscript-promise.token.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core' 2 | 3 | export const MinimalManuscriptPromise = new InjectionToken('any') 4 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3-network-id/web3-network-id.token.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core' 2 | 3 | export const Web3NetworkIdPromise = new InjectionToken('Promise') 4 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /migrations/0002-deploy-Ownable.js: -------------------------------------------------------------------------------- 1 | var Accessible = artifacts.require('../contracts/Accessible.sol') 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Accessible) 5 | } 6 | -------------------------------------------------------------------------------- /src/app/components/list-papers/manuscript-voting-status.ts: -------------------------------------------------------------------------------- 1 | export enum ManuscriptVotingStatus { 2 | Open = 'Voting in progress', 3 | Accepted = 'Accepted', 4 | Rejected = 'Rejected' 5 | } -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aletheia-foundation/aletheia-app/HEAD/src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/less/screen-reader.less: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { .sr-only(); } 5 | .sr-only-focusable { .sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /scripts/get-testnet-addresses.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source ./scripts/env-testnet.sh 3 | geth --datadir $TEST_DATA_DIR account list | awk -F[\{\}] '{print $2}' | xargs | sed -e 's/ /,/g' 4 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/Injection-tokens.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core' 2 | 3 | export const WEB3_URL = new InjectionToken('string') 4 | export const POLL_INTERVAL_MS = new InjectionToken('string') 5 | -------------------------------------------------------------------------------- /src/app/providers/config/config.factory.ts: -------------------------------------------------------------------------------- 1 | import {Config} from '../../../../config/Config' 2 | 3 | export function configFactory(): Config { 4 | return require('../../../../config/default.js') 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/scss/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { @include sr-only(); } 5 | .sr-only-focusable { @include sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3-provider/web3-provider.factory.ts: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3' 2 | 3 | export function web3ProviderFactory() { 4 | return new Web3.providers.HttpProvider('http://localhost:8545') 5 | } 6 | -------------------------------------------------------------------------------- /src/theme/modal.scss: -------------------------------------------------------------------------------- 1 | 2 | .modal-header, .modal-body, .modal-content { 3 | color: $gray-900; 4 | a { 5 | color: $primary; 6 | &:hover { 7 | color: darken($primary, 15%); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/environments/index.ts: -------------------------------------------------------------------------------- 1 | // This file contains development variables. (When you work in DEV MODE) 2 | // This file is use by webpack. Please don't rename it and don't move it to another directory. 3 | export const environment = { 4 | production: false 5 | }; 6 | -------------------------------------------------------------------------------- /src/environments/index.prod.ts: -------------------------------------------------------------------------------- 1 | // This file contains production variables. (When you work in PROD MODE) 2 | // This file is use by webpack. Please don't rename it and don't move it to another directory. 3 | export const environment = { 4 | production: true 5 | }; 6 | -------------------------------------------------------------------------------- /src/theme/notifications.scss: -------------------------------------------------------------------------------- 1 | div.simple-notification-wrapper { 2 | width: 600px; 3 | z-index: 2000; 4 | .simple-notification { 5 | box-shadow: $popover-box-shadow; 6 | width: 600px; 7 | .icon { 8 | display: none; 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /scripts/ipfs-local.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | IPFS_EXECUTABLE='./node_modules/go-ipfs-dep/go-ipfs/ipfs' 3 | 4 | IPFS_DIR='.ipfs-develop' 5 | if [ ! -d "$IPFS_DIR" ]; then 6 | # Control will enter here if $DIRECTORY exists. 7 | IPFS_EXECUTABLE init -c $IPFS_DIR 8 | fi 9 | IPFS_EXECUTABLE daemon -c $IPFS_DIR 10 | -------------------------------------------------------------------------------- /src/app/components/submit-paper/insufficient-balance-modal/insufficient-balance-modal.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../theme/variables-and-mixins.scss'; 2 | .captcha-section { 3 | .captcha-answer, .captcha-img { 4 | display: block; 5 | margin: 10px auto; 6 | } 7 | .captcha-img { 8 | height: 50px; 9 | } 10 | } -------------------------------------------------------------------------------- /src/app/components/list-papers/manuscript-view-model.ts: -------------------------------------------------------------------------------- 1 | import {ManuscriptVotingStatus} from './manuscript-voting-status' 2 | export class ManuscriptViewModel { 3 | authors: string[] 4 | dataAddress: string 5 | contractAddress: string 6 | blocksRemaining: number 7 | votingStatus: ManuscriptVotingStatus 8 | title: string 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | ipfs-repo 4 | .DS_Store 5 | out 6 | .ethereum-test 7 | .testnet-password 8 | .node-xmlhttprequest-sync-* 9 | *.sol*.bin 10 | chains.json 11 | .ipfs-develop/ 12 | app-builds 13 | dist 14 | package-lock.json 15 | build 16 | MinimalManuscriptTest.spec.js 17 | CommunityVoteSimple.sol 18 | app-builds 19 | dist 20 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Aletheia 2 6 | 7 | 8 | 9 | 10 | 11 | Loading... 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "baseUrl": "", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts", 12 | "dist", 13 | "app-builds", 14 | "node_modules" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var nodeModule: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | 7 | declare var window: Window; 8 | interface Window { 9 | process: any; 10 | require: any; 11 | } 12 | 13 | declare module "*.json" { 14 | const value: any; 15 | export default value; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/components/how-it-works/how-it-works.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-how-it-works', 5 | templateUrl: './how-it-works.component.html', 6 | styleUrls: ['./how-it-works.component.scss'] 7 | }) 8 | export class HowItWorksComponent { 9 | 10 | constructor() {} 11 | } 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ![alt tag](https://cloud.githubusercontent.com/assets/24201238/24583976/ced4c43e-179f-11e7-9c40-c0988c346f55.png) 2 | 3 | _**Publish science for free, access science for free.**_ 4 | 5 | Please see the master Contributing file in the admin repository, [here](https://github.com/aletheia-foundation/admin/blob/master/CONTRIBUTING.md), for contributing guidelines. 6 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 7 |
8 | 9 | -------------------------------------------------------------------------------- /src/app/components/become-a-reviewer/become-a-reviewer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-become-a-reviewer', 5 | templateUrl: './become-a-reviewer.component.html', 6 | styleUrls: ['./become-a-reviewer.component.scss'] 7 | }) 8 | export class BecomeAReviewerComponent { 9 | 10 | constructor() {} 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/HELP-US-OUT.txt: -------------------------------------------------------------------------------- 1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project, 2 | Fort Awesome (https://fortawesome.com). It makes it easy to put the perfect icons on your website. Choose from our awesome, 3 | comprehensive icon sets or copy and paste your own. 4 | 5 | Please. Check it out. 6 | 7 | -Dave Gandy 8 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from 'environments'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | 12 | 13 | platformBrowserDynamic().bootstrapModule(AppModule); 14 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3-network-id/web3-network-id.factory.ts: -------------------------------------------------------------------------------- 1 | export function web3NetworkIdFactory(web3): Promise { 2 | return new Promise((resolve, reject) => { 3 | web3.version.getNetwork((error, netowrkId) => { 4 | if(error) { 5 | return reject(error) 6 | } 7 | console.log('network id is:', netowrkId) 8 | return resolve(netowrkId) 9 | }) 10 | }) 11 | } -------------------------------------------------------------------------------- /config/Config.ts: -------------------------------------------------------------------------------- 1 | export abstract class Config { 2 | ipfsRepoPath: string 3 | ipfs: IpfsConfig 4 | faucetUrl: string 5 | web3: Web3Config 6 | } 7 | 8 | export interface IpfsConfig { 9 | gatewayUrl: string 10 | pollIntervalMs: string 11 | } 12 | 13 | export interface Web3Config { 14 | accountNumber: number 15 | defaultGas: number 16 | url: string 17 | pollIntervalMs: number 18 | aletheiaContractAddress: string 19 | } 20 | -------------------------------------------------------------------------------- /config/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "ipfsRepoPath": "./ipfs-repo", 3 | "ipfs": { 4 | "gatewayUrl": "/ip4/127.0.0.1/tcp/5001", 5 | "pollIntervalMs": 1000 6 | }, 7 | "faucetUrl": "https://theserverbythe.stream", 8 | "web3": { 9 | "accountNumber": process.env.ACCOUNT_NUMBER || 0, 10 | "defaultGas": 4712388, 11 | "url": "http://localhost:8545", 12 | "pollIntervalMs": 1000, 13 | "aletheiaContractAddress": "" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/components/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-home', 6 | templateUrl: './home.component.html', 7 | styleUrls: ['./home.component.scss'] 8 | }) 9 | export class HomeComponent implements OnInit { 10 | constructor(private router: Router) { } 11 | 12 | onSearch() { 13 | this.router.navigateByUrl('/list-papers'); 14 | } 15 | ngOnInit() { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3-helper/web3-helper.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { Web3HelperService } from './web3-helper.service'; 4 | 5 | describe('Web3HelperService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [Web3HelperService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([Web3HelperService], (service: Web3HelperService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | body { 2 | height: 100%; 3 | height: 100vh; 4 | } 5 | 6 | header { 7 | display: flex; 8 | justify-content: space-between; 9 | background-color: #32B67A; 10 | align-items: center; 11 | margin-bottom: 40px; 12 | } 13 | 14 | nav { 15 | display: flex; 16 | padding: 20px 20px; 17 | background-color: #32B67A; 18 | a { 19 | text-decoration: none; 20 | color: white; 21 | font-size: 18px; 22 | padding: 0px 10px; 23 | } 24 | } -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | @import "screen-reader"; 19 | -------------------------------------------------------------------------------- /migrations/0002-deploy-ReputationStorage.js: -------------------------------------------------------------------------------- 1 | var Reputation = artifacts.require('../contracts/Reputation.sol') 2 | var ManuscriptIndex = artifacts.require('../contracts/ManuscriptIndex.sol') 3 | var CommunityVotes = artifacts.require('../contracts/CommunityVotes.sol') 4 | var MinimalManuscript = artifacts.require('../contracts/MinimalManuscript.sol') 5 | 6 | module.exports = function (deployer) { 7 | deployer.deploy(Reputation) 8 | deployer.deploy(ManuscriptIndex) 9 | deployer.deploy(CommunityVotes, 10) 10 | deployer.deploy(MinimalManuscript) 11 | } 12 | -------------------------------------------------------------------------------- /config/test/genesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "nonce": "0x6952268836800000", 3 | "difficulty": "0x0", 4 | "config": {}, 5 | "alloc": { 6 | "0xfa22d01f0c39a4c7e3a6868928b157efaf030a5e": { 7 | "balance": "15000000000000000000" 8 | } 9 | }, 10 | "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", 11 | "coinbase": "0x3333333333333333333333333333333333333333", 12 | "timestamp": "0x00", 13 | "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 14 | "extraData": "0x", 15 | "gasLimit": "0x4c4b40" 16 | } 17 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | testnet: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "123039281", 7 | gas: 4600000 8 | }, 9 | development: { 10 | host: "localhost", 11 | port: 8545, 12 | network_id: "*", // Match any network id 13 | gas: 4600000 14 | }, 15 | ganache: { 16 | host: "127.0.0.1", 17 | port: 7545, 18 | network_id: "5777", 19 | gas: 4600000 20 | } 21 | }, 22 | rpc: { 23 | host: "127.0.0.1", 24 | port: 8545 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /test/helpers/test-encoding-helper.js: -------------------------------------------------------------------------------- 1 | const bs58 = require('bs58') 2 | 3 | module.exports = { 4 | ipfsAddressToHexSha256: function (ipfsMultiHash) { 5 | if (ipfsMultiHash.length !== 46) { 6 | throw {msg: 'expected ipfs MultiHash address to be 46 characters long.', ipfsMultiHash} 7 | } 8 | const hexString = this.bs58ToWeb3Bytes(ipfsMultiHash) 9 | return '0x' + hexString.slice(4); // the first four bytes are the hash algorithm and length. Always the same 10 | }, 11 | 12 | bs58ToWeb3Bytes: function (bs58Str) { 13 | return bs58.decode(bs58Str).toString('hex'); 14 | } 15 | } -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 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() { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | @import "screen-reader.less"; 19 | -------------------------------------------------------------------------------- /src/app/providers/file-helper/file-helper.ts: -------------------------------------------------------------------------------- 1 | 2 | const path = window.require ? window.require('path') : null 3 | const os = window.require ? window.require('os') : null 4 | const fs = window.require ? window.require('fs') : null 5 | 6 | export class FileHelper { 7 | 8 | 9 | public static toAbsoluteDownloadFilePath(title: string, extension: string) { 10 | return path.join(os.homedir(), 'Downloads', title.replace(/\W/, '-') + extension) 11 | } 12 | 13 | public static writeFileStream(stream, filePath) { 14 | const writeStream = fs.createWriteStream(filePath) 15 | stream.pipe(writeStream) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /scripts/add-balance-to-test-account.js: -------------------------------------------------------------------------------- 1 | // usage `node add-balance-to-test-accounts.js [ADDRESS1] ... [ADDRESSN]` 2 | const fs = require('fs') 3 | const genesisJson = require('../config/test/genesis.testnet.json') 4 | const addressesToCredit = process.argv[2].split(',') 5 | const ammountToCreditInWei = '1000000000000000000000' 6 | console.log(`crediting ${ammountToCreditInWei} to accounts: ${addressesToCredit}`) 7 | 8 | for (var i = 0; i < addressesToCredit.length; i++) { 9 | genesisJson.alloc[addressesToCredit[i]] = {'balance': ammountToCreditInWei} 10 | } 11 | 12 | fs.writeFileSync('build/genesis.testnet.json', JSON.stringify(genesisJson)) 13 | -------------------------------------------------------------------------------- /src/theme/variables-and-mixins.scss: -------------------------------------------------------------------------------- 1 | @import "../../node_modules/bootstrap/scss/functions"; 2 | @import "../../node_modules/bootstrap/scss/variables"; 3 | @import "../../node_modules/bootstrap/scss/mixins"; 4 | 5 | // Colors 6 | $error-color: #b60c19; 7 | $warning-color: #b6b213; 8 | $link-color: #fff; 9 | $link-hover-color: #fff; 10 | $body-color: $gray-100; 11 | 12 | 13 | // Fonts 14 | $font-family-sans-serif: "Montserrat", sans-serif; 15 | $font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 16 | $font-family-serif: "Domine", serif; 17 | $font-family-base: $font-family-sans-serif; 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "baseUrl": "src", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "allowJs": true, 12 | "target": "es5", 13 | "paths": { 14 | "environments": [ 15 | "./environments" 16 | ] 17 | }, 18 | "types": [ 19 | "node", 20 | "jasmine" 21 | ], 22 | "typeRoots": [ 23 | "node_modules/@types" 24 | ], 25 | "lib": [ 26 | "es2016", 27 | "dom" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | // AngularCompilerPlugin currently ignores the `target` attribute for setting the output target JavaScript version 7 | // https://github.com/angular/angular-cli/issues/8375 8 | // this means that es2016 is always produced 9 | "target": "es5", 10 | "baseUrl": "", 11 | "types": [ 12 | "jasmine", 13 | "node" 14 | ] 15 | }, 16 | "files": [ 17 | "test.ts" 18 | ], 19 | "include": [ 20 | "**/*.spec.ts", 21 | "**/*.d.ts" 22 | ], 23 | "exclude": [ 24 | "dist", 25 | "app-builds", 26 | "node_modules" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { float: left; } 11 | .@{fa-css-prefix}-pull-right { float: right; } 12 | 13 | .@{fa-css-prefix} { 14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .@{fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { float: left; } 11 | .#{$fa-css-prefix}-pull-right { float: right; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .#{$fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/Reputation.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./Accessible.sol"; 4 | import "zeppelin-solidity/contracts/math/SafeMath.sol"; 5 | 6 | 7 | contract Reputation is Accessible { 8 | 9 | using SafeMath for uint256; 10 | 11 | mapping(address => uint256) public reputation; 12 | 13 | function reputationOf(address account) public view returns (uint256) { 14 | return reputation[account]; 15 | } 16 | 17 | function addReputation(address account, uint256 amount) public onlyAllowedAccount { 18 | reputation[account] = reputation[account].add(amount); 19 | } 20 | 21 | function removeReputation(address account, uint256 amount) public onlyAllowedAccount { 22 | reputation[account] = reputation[account].sub(amount); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3-monitor/web3-network-status.ts: -------------------------------------------------------------------------------- 1 | export enum ConnectionStatusEnum { 2 | Connected, 3 | Error, 4 | NoPeers 5 | } 6 | 7 | export class Web3NetworkStatus { 8 | error: any 9 | peers: number 10 | address: string 11 | balance: number 12 | 13 | constructor(error: any, peers: number, address: string, balance: number) { 14 | this.error = error 15 | this.peers = peers 16 | this.address = address 17 | this.balance = balance 18 | } 19 | 20 | getStatus(): ConnectionStatusEnum { 21 | if (this.error) { 22 | return ConnectionStatusEnum.Error 23 | } 24 | if (this.peers === 0) { 25 | return ConnectionStatusEnum.NoPeers 26 | } 27 | return ConnectionStatusEnum.Connected 28 | } 29 | 30 | // todo: add blocknumber 31 | } 32 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /scripts/init-testnet.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source ./scripts/env-testnet.sh 3 | 4 | if [ -d "$TEST_DATA_DIR" ]; then 5 | rm -r $TEST_DATA_DIR 6 | fi 7 | 8 | if [ -f "$TEST_NET_PASSWORD" ]; then 9 | rm -f $TEST_NET_PASSWORD 10 | fi 11 | 12 | randomUtf8=$(head -c 64 /dev/urandom | base64) 13 | (umask 377 ; echo $randomUtf8 > "$TEST_NET_PASSWORD") 14 | 15 | # set up a local account 16 | geth --datadir $TEST_DATA_DIR --password $TEST_NET_PASSWORD account new 17 | 18 | # get the account that was just generated 19 | TESTNET_ACCOUNTS=`./scripts/get-testnet-addresses.sh` 20 | 21 | # update the testnet config to make the account(s) start with ether 22 | node ./scripts/add-balance-to-test-account.js $TESTNET_ACCOUNTS 23 | 24 | # initialise the testnet chain 25 | geth --datadir $TEST_DATA_DIR init build/genesis.testnet.json 26 | -------------------------------------------------------------------------------- /scripts/install-ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | 4 | # install nodejs 6.x from nodesource 5 | curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | sudo apt-key add - 6 | sudo sh -c "echo 'deb https://deb.nodesource.com/node_6.x xenial main' > \ 7 | /etc/apt/sources.list.d/nodesource.list" 8 | 9 | # add ethereum repo and install geth (ethereum). 10 | # note, this is not strictly required for local development as the fake ethereum client `testrpc` can be used. 11 | sudo add-apt-repository -y ppa:ethereum/ethereum 12 | sudo apt-get install -y software-properties-common # required by geth 13 | 14 | sudo apt-get update 15 | 16 | sudo apt-get install ethereum -y 17 | 18 | sudo apt-get install -y nodejs 19 | # and create an alias in your .bash_profile: alias node=nodejs 20 | 21 | npm install -g electron 22 | -------------------------------------------------------------------------------- /src/app/components/submit-paper/submit-paper.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Submit a document to Aletheia

3 |
4 |
5 |
6 |
7 |
8 | 9 | 12 | 13 | 14 |
15 | View all submitted papers 16 |
17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /scripts/ipfs-local.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process'); 2 | const fs = require('fs'); 3 | 4 | const devPath = ".ipfs-develop"; 5 | const ipfsExecutable = './node_modules/go-ipfs-dep/go-ipfs/ipfs'; 6 | let isWin = /^win/.test(process.platform); 7 | 8 | let shell = (cmd, callback) => { 9 | let child = child_process.exec(cmd, callback); 10 | child.stdout.pipe(process.stdout); 11 | child.stderr.pipe(process.stderr); 12 | process.stdin.pipe(child.stdin); 13 | } 14 | 15 | let runIpfs = () => { 16 | shell (`${ipfsExecutable} daemon -c ${devPath}`); 17 | } 18 | 19 | let initDev = !fs.existsSync(devPath); 20 | 21 | if(initDev) { 22 | shell (`${ipfsExecutable} init -c ${devPath}`, (err, stdout, stderr) => { 23 | if(!err) runIpfs(); 24 | }); 25 | } else { 26 | runIpfs(); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/theme/type.scss: -------------------------------------------------------------------------------- 1 | //note: url paths are relative to ../styles.scss 2 | 3 | @font-face { 4 | font-family: "Domine"; 5 | src: url('./assets/fonts/Domine-Bold.ttf'); 6 | font-weight: bold; 7 | } 8 | 9 | @font-face { 10 | font-family: "Domine"; 11 | src: url('./assets/fonts/Domine-Regular.ttf'); 12 | font-weight: normal; 13 | } 14 | 15 | @font-face { 16 | font-family: "Montserrat"; 17 | src: url('./assets/fonts/Montserrat-Bold.ttf'); 18 | font-weight: bold; 19 | } 20 | 21 | @font-face { 22 | font-family: "Montserrat"; 23 | src: url('./assets/fonts/Montserrat-Regular.ttf'); 24 | font-weight: normal; 25 | } 26 | 27 | @font-face { 28 | font-family: "Montserrat"; 29 | src: url('./assets/fonts/Montserrat-Light.ttf'); 30 | font-weight: 300; 31 | } 32 | 33 | h1, .h1 { 34 | font-family: $font-family-serif; 35 | } -------------------------------------------------------------------------------- /src/app/providers/error-handler/error-handler.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import {NotificationsService} from 'angular2-notifications' 3 | 4 | export class MockErrorHandlerService { 5 | handleError (error: Error, message? :string) {} 6 | handleWarning (warning: Error, message? :string) {} 7 | } 8 | 9 | @Injectable() 10 | export class ErrorHandlerService { 11 | constructor(private notificationService: NotificationsService) { } 12 | 13 | handleError(error: Error, message? :string) { 14 | //todo: log full error 15 | console.error(error) 16 | this.notificationService.error("Error", message || error.message) 17 | } 18 | 19 | handleWarning(warning: Error, message? :string) { 20 | console.log(warning) 21 | this.notificationService.warn("Warning", message || warning.message) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/components/network-status/network-status.component.scss: -------------------------------------------------------------------------------- 1 | /* TODO: mobile devices */ 2 | @import '../../../theme/variables-and-mixins'; 3 | 4 | .network-status { 5 | display: flex; 6 | padding: 20px 20px; 7 | .error-status { 8 | display:inline-block; 9 | border: 1px solid $error-color; 10 | border-radius: 3px; 11 | padding: 5px 10px; 12 | .fa { 13 | color: $error-color; 14 | } 15 | } 16 | .peers-connected { 17 | display: inline-block; 18 | padding: 5px 10px; 19 | &.no-peers { 20 | border: 1px solid $warning-color; 21 | border-radius: 3px; 22 | .fa { 23 | color: $warning-color; 24 | } 25 | } 26 | } 27 | .profile-link { 28 | display: inline-block; 29 | padding: 5px 10px; 30 | } 31 | .balance { 32 | display: inline-block; 33 | padding: 5px 10px; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/components/network-status/network-status.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Error connecting to Aletheia network 4 |
5 |
6 | 7 | 8 | {{(this.web3Monitor.networkStatus | async).peers}} peers connected 9 |
10 | 11 | 12 |
Ψ{{(this.web3Monitor.networkStatus | async).balance}}
13 |
14 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/providers/encoding-helper/encoding-helper.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed, inject} from '@angular/core/testing' 2 | 3 | import {EncodingHelper} from './encoding-helper' 4 | 5 | describe('EncodingHelper', () => { 6 | 7 | describe('ipfsAddressToHexSha256', () => { 8 | it('should convert sample address ', () => { 9 | const result = EncodingHelper.ipfsAddressToHexSha256('QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH') 10 | expect(result).toBe('0xbfccda787baba32b59c78450ac3d20b633360b43992c77289f9ed46d843561e6') 11 | }) 12 | }) 13 | 14 | describe('hexSha256ToIpfsMultiHash', () => { 15 | it('should convert sample address ', () => { 16 | const result = EncodingHelper.hexSha256ToIpfsMultiHash('0xbfccda787baba32b59c78450ac3d20b633360b43992c77289f9ed46d843561e6') 17 | expect(result).toBe('QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH') 18 | }) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/components/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

Aletheia

5 | 6 | Search the world's peer reviewed scientific knowledge. 7 | 8 |
9 | 10 | 13 |
14 |
15 | How it works | View all papers | Submit a paper | Become a reviewer 16 |
17 |
18 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Bootnode.Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | stages { 4 | stage('Deploy bootnode') { 5 | steps { 6 | echo 'deploying testnet bootnode' 7 | sh 'ls -al scripts' 8 | sshPublisher( 9 | publishers: [sshPublisherDesc( 10 | configName: 'aletheia-infrastructure', 11 | verbose: true, 12 | transfers: [ sshTransfer ( 13 | sourceFiles: 'scripts/**/*.*', 14 | remoteDirectory: '/var/aletheia-bootnode/bootnode', 15 | execCommand: 'sudo /var/aletheia-bootnode/restart-bootnode.sh' 16 | )] 17 | )] 18 | ) 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /contracts/Manuscript.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.15; 2 | 3 | 4 | interface Manuscript { 5 | function dataAddress() external constant returns(bytes32); 6 | function title() external constant returns(string); 7 | function addAuthor(address newAuthor) external; 8 | function citePaper(address citee) external; 9 | function removeCitation(address citee) external; 10 | function removeAuthor(address author) external; 11 | function citationCount() external constant returns (uint); 12 | function authorCount() external constant returns (uint); 13 | function citation(uint authorIdx) external constant returns (address); 14 | function author(uint paperIdx) external constant returns (address); 15 | function isOwner(address account) external constant returns(bool); 16 | function authorSigned(address _author) external constant returns (bool); 17 | function signAuthorship() external; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | import { ElectronService } from 'app/providers/electron.service'; 5 | import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core' 6 | 7 | describe('AppComponent', () => { 8 | beforeEach(async(() => { 9 | TestBed.configureTestingModule({ 10 | declarations: [ 11 | AppComponent 12 | ], 13 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 14 | providers : [ 15 | ElectronService, 16 | ], 17 | imports: [RouterTestingModule] 18 | }).compileComponents(); 19 | })); 20 | 21 | it('should create the app', async(() => { 22 | const fixture = TestBed.createComponent(AppComponent); 23 | const app = fixture.debugElement.componentInstance; 24 | expect(app).toBeTruthy(); 25 | })); 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ElectronService } from './providers/electron.service'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'] 8 | }) 9 | export class AppComponent { 10 | notificationOptions = { 11 | position: ['top', 'right'], 12 | timeOut: 10000, 13 | showProgressBar: false 14 | } 15 | constructor(public electronService: ElectronService) { 16 | 17 | if (electronService.isElectron()) { 18 | console.log('Mode electron'); 19 | // Check if electron is correctly injected (see externals in webpack.config.js) 20 | console.log('c', electronService.ipcRenderer); 21 | // Check if nodeJs childProcess is correctly injected (see externals in webpack.config.js) 22 | console.log('c', electronService.childProcess); 23 | } else { 24 | console.log('Mode web'); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/components/list-papers/list-papers.component.html: -------------------------------------------------------------------------------- 1 |

All manuscripts

2 | 20 |
21 | No manuscripts found. 22 |
-------------------------------------------------------------------------------- /migrations/0003-deploy-Aletheia.js: -------------------------------------------------------------------------------- 1 | var Aletheia = artifacts.require('./Aletheia.sol') 2 | var Reputation = artifacts.require('../contracts/Reputation.sol') 3 | var ManuscriptIndex = artifacts.require('../contracts/ManuscriptIndex.sol') 4 | var CommunityVotes = artifacts.require('../contracts/CommunityVotes.sol') 5 | var MinimalManuscript = artifacts.require('../contracts/MinimalManuscript.sol') 6 | var ManuscriptFactory = artifacts.require('../contracts/ManuscriptFactory.sol') 7 | 8 | module.exports = function (deployer) { 9 | return deployer.deploy(Aletheia, Reputation.address,ManuscriptIndex.address, 10 | CommunityVotes.address, MinimalManuscript.address) 11 | .then(()=>{ 12 | console.log('granting access to' + Aletheia.address) 13 | Reputation.at(Reputation.address).grantAccess(Aletheia.address) 14 | ManuscriptIndex.at(ManuscriptIndex.address).grantAccess(Aletheia.address) 15 | CommunityVotes.at(CommunityVotes.address).grantAccess(Aletheia.address) 16 | }) 17 | }; 18 | -------------------------------------------------------------------------------- /contracts/ManuscriptFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.19; 2 | 3 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "./MinimalManuscript.sol"; 5 | import "./CloneFactory.sol"; 6 | 7 | 8 | contract ManuscriptFactory is CloneFactory, Ownable { 9 | 10 | address public libraryAddress; 11 | 12 | event ManuscriptCreated(address newManuscriptAddress, address libraryAddress); 13 | 14 | function ManuscriptFactory(address _libraryAddress) public { 15 | libraryAddress = _libraryAddress; 16 | } 17 | 18 | function onlyCreate() public onlyOwner { 19 | // simple cloning test 20 | createClone(libraryAddress); 21 | } 22 | 23 | function createManuscript(bytes32 _dataAddress, string title) public returns(address) { 24 | address clone = createClone(libraryAddress); 25 | MinimalManuscript(clone).init(_dataAddress, title); 26 | emit ManuscriptCreated(clone, libraryAddress); 27 | MinimalManuscript(clone).transferOwnership(msg.sender); 28 | return clone; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## I confirm: 2 | 3 | - [ ] I've read the [contributing guidelines](https://github.com/aletheia-foundation/aletheia-admin/blob/master/CONTRIBUTING.md) and the [code of conduct](https://github.com/aletheia-foundation/aletheia-admin/blob/master/CODE-OF-CONDUCT.md) 4 | - [ ] I've checked that this issue applies to this repo 5 | - [ ] I've checked the existing issues, no one else has reported this 6 | - [ ] I've limited the subject line to 50 characters 7 | - [ ] I've capitalised the subject line 8 | - [ ] I've not ended the subject line with a period 9 | - [ ] I've used the imperative mood in the subject line 10 | 11 | --------------------------- 12 | 13 | ### Making an enhancement - ignore this if not making an enhancement 14 | 15 | - [ ] I've **Checked** the [outstanding issues](https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+user%3Aaletheia-foundation+). 16 | - [ ] I've **Read** the latest version of the [whitepaper](https://github.com/aletheia-foundation/whitepaper). 17 | - [ ] I've **Emailed** contact@aletheia-foundation.io to discuss the enhancement. 18 | -------------------------------------------------------------------------------- /src/app/components/network-status/network-status.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Inject, OnInit} from '@angular/core' 2 | import {Web3MonitorService} from '../../providers/web3/web3-monitor/web3-monitor.service' 3 | import {ConnectionStatusEnum, Web3NetworkStatus} from '../../providers/web3/web3-monitor/web3-network-status' 4 | 5 | 6 | @Component({ 7 | selector: 'network-status', 8 | templateUrl: './network-status.component.html', 9 | styleUrls: ['./network-status.component.scss'] 10 | }) 11 | export class NetworkStatusComponent { 12 | web3Monitor: Web3MonitorService 13 | 14 | constructor( 15 | web3Monitor: Web3MonitorService) { 16 | this.web3Monitor = web3Monitor 17 | this.web3Monitor.start() 18 | } 19 | 20 | isConnected() { 21 | return this.web3Monitor.networkStatus.getValue().getStatus() === ConnectionStatusEnum.Connected 22 | } 23 | 24 | isError() { 25 | return this.web3Monitor.networkStatus.getValue().getStatus() === ConnectionStatusEnum.Error 26 | } 27 | 28 | isNoPeers() { 29 | return this.web3Monitor.networkStatus.getValue().getStatus() === ConnectionStatusEnum.NoPeers 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3-account/web3-account.service.ts: -------------------------------------------------------------------------------- 1 | import {Inject, Injectable} from '@angular/core' 2 | import {Web3Token} from '../web3/web3.token' 3 | import {Config} from '../../../../../config/Config' 4 | 5 | export function loadWeb3Account(web3AccountService: Web3AccountService) { 6 | return () => web3AccountService.load() 7 | } 8 | 9 | export class MockWeb3AccountService { 10 | getAccount() { 11 | } 12 | } 13 | 14 | @Injectable() 15 | export class Web3AccountService { 16 | account: string 17 | 18 | constructor( 19 | @Inject(Web3Token) private web3: any, 20 | private config: Config, 21 | ) { 22 | } 23 | 24 | getAccount() { 25 | return this.account 26 | } 27 | 28 | load() { 29 | return new Promise((res, rej) => { 30 | const existingAcc = this.web3.eth.accounts 31 | const accountNumber = this.config.web3.accountNumber || 0 32 | if (existingAcc && existingAcc[accountNumber]) { 33 | this.account = existingAcc[accountNumber] 34 | } else { 35 | this.account = this.web3.personal.newAccount() 36 | } 37 | return res(this.account) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/components/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | 6 | describe('HomeComponent', () => { 7 | let component: HomeComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ HomeComponent], 13 | imports: [RouterTestingModule] 14 | }) 15 | .compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(HomeComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | 28 | it('should render title in a h1 tag', async(() => { 29 | fixture = TestBed.createComponent(HomeComponent); 30 | fixture.detectChanges(); 31 | const compiled = fixture.debugElement.nativeElement; 32 | expect(compiled.querySelector('h1').textContent).toContain('Aletheia'); 33 | })); 34 | }); 35 | -------------------------------------------------------------------------------- /src/app/providers/electron.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | 3 | // If you import a module but never use any of the imported values other than as TypeScript types, 4 | // the resulting javascript file will look as if you never imported the module at all. 5 | import {ipcRenderer} from 'electron' 6 | import {dialog} from 'electron' 7 | import * as childProcess from 'child_process' 8 | 9 | export class MockElectronService { 10 | public dialog = { 11 | showOpenDialog: () => {} 12 | } 13 | isElectron() { 14 | return true 15 | } 16 | } 17 | 18 | @Injectable() 19 | export class ElectronService { 20 | public ipcRenderer: typeof ipcRenderer 21 | public dialog: typeof dialog 22 | public childProcess: typeof childProcess 23 | 24 | constructor() { 25 | // Conditional imports 26 | if (this.isElectron()) { 27 | this.ipcRenderer = window.require('electron').ipcRenderer 28 | this.childProcess = window.require('child_process') 29 | this.dialog = window.require('electron').remote.dialog 30 | } 31 | } 32 | 33 | isElectron = () => { 34 | return window && window.process && window.process.type 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/app/components/submit-paper/insufficient-balance-modal/insufficient-balance-modal.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 9 | 20 | 25 |
26 | -------------------------------------------------------------------------------- /src/app/providers/encoding-helper/encoding-helper.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import * as bs58 from 'bs58' 4 | const ipfsSha256PrefixHex = '1220' 5 | 6 | 7 | export class EncodingHelper { 8 | 9 | static bs58ToWeb3Bytes (bs58Str) { 10 | return bs58.decode(bs58Str).toString('hex'); 11 | } 12 | 13 | static ipfsAddressToHexSha256(ipfsMultiHash) { 14 | if (ipfsMultiHash.length !== 46) { 15 | throw {msg: 'expected ipfs MultiHash address to be 46 characters long.', ipfsMultiHash} 16 | } 17 | const hexString = this.bs58ToWeb3Bytes(ipfsMultiHash) 18 | return '0x' + hexString.slice(4); // the first four bytes are the hash algorithm and length. Always the same 19 | } 20 | 21 | static hexSha256ToIpfsMultiHash(hash) { 22 | const rawHex = ipfsSha256PrefixHex + this.remove0x(hash); 23 | const rawHexBuffer = new Buffer(rawHex, 'hex'); 24 | return bs58.encode(rawHexBuffer); 25 | } 26 | 27 | static remove0x(bytesStr) { 28 | if (typeof bytesStr === 'string' && bytesStr.startsWith('0x')) { 29 | return bytesStr.slice(2); 30 | } else { 31 | throw {msg: 'bytesStr is not a string starting with 0x', bytesStr} 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | declare const require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /scripts/start-geth-testnet.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source ./scripts/env-testnet.sh 3 | 4 | BOOTNODE_1_IP=`dig +short aletheia-infrastructure.org | awk '{print $1}'` 5 | 6 | 7 | # Even if we are not the admin(rich) node, 8 | # still init the network with the same genesis.testnet.json to make networks compatible 9 | if [ ! -d $TEST_DATA_DIR ]; then 10 | geth --datadir $TEST_DATA_DIR init ./build/genesis.testnet.json 11 | fi 12 | 13 | if [ ! -f "$TEST_NET_PASSWORD" ]; then 14 | randomUtf8=$(head -c 64 /dev/urandom | base64) 15 | (umask 377 ; echo $randomUtf8 > "$TEST_NET_PASSWORD") 16 | fi 17 | 18 | TESTNET_ACCOUNTS=`./scripts/get-testnet-addresses.sh` 19 | 20 | if [ -z "$TESTNET_ACCOUNTS" ]; then 21 | geth --datadir $TEST_DATA_DIR --password $TEST_NET_PASSWORD account new 22 | fi 23 | 24 | TESTNET_ACCOUNTS=`./scripts/get-testnet-addresses.sh` 25 | 26 | geth --datadir $TEST_DATA_DIR --rpc --rpcapi eth,net,web3,personal \ 27 | --networkid 123039281 --password $TEST_NET_PASSWORD \ 28 | --unlock "$TESTNET_ACCOUNTS" \ 29 | --bootnodes enode://682e65fffd55107fcc4ace95aca52ee9a06fe28507610a47a2c9c9c2e28175ca06b462085f6e845d209619ee5a26b597c4c9504da243c4a6503f8d5fa07b8175@$BOOTNODE_1_IP:30303 \ 30 | js ./scripts/mine.js 31 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { HomeComponent } from './components/home/home.component'; 2 | import { NgModule } from '@angular/core'; 3 | import { Routes, RouterModule } from '@angular/router'; 4 | import {BecomeAReviewerComponent} from './components/become-a-reviewer/become-a-reviewer.component'; 5 | import {HowItWorksComponent} from './components/how-it-works/how-it-works.component'; 6 | import {SubmitPaperComponent} from './components/submit-paper/submit-paper.component'; 7 | import {ListPapersComponent} from './components/list-papers/list-papers.component' 8 | 9 | const routes: Routes = [ 10 | { 11 | path: '', 12 | component: HomeComponent 13 | }, 14 | { 15 | path: 'become-a-reviewer', 16 | component: BecomeAReviewerComponent 17 | }, 18 | { 19 | path: 'how-it-works', 20 | component: HowItWorksComponent 21 | }, 22 | { 23 | path: 'submit-paper', 24 | component: SubmitPaperComponent 25 | }, 26 | { 27 | path: 'list-papers', 28 | component: ListPapersComponent 29 | }, 30 | ]; 31 | 32 | @NgModule({ 33 | imports: [RouterModule.forRoot(routes, {useHash: true})], 34 | exports: [RouterModule] 35 | }) 36 | export class AppRoutingModule { } 37 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3-monitor/web3-monitor.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing' 2 | import * as FakeWeb3Provider from 'web3-fake-provider' 3 | import { Web3MonitorService } from './web3-monitor.service' 4 | import {POLL_INTERVAL_MS} from '../../../Injection-tokens' 5 | import {web3Factory} from '../web3/web3.factory' 6 | import {Web3Provider} from '../web3-provider/web3-provider.token' 7 | import {Web3Token} from '../web3/web3.token' 8 | import {MockWeb3AccountService, Web3AccountService} from '../web3-account/web3-account.service' 9 | 10 | function fakeWeb3ProviderFactory () { 11 | return new FakeWeb3Provider() 12 | } 13 | 14 | describe('Web3MonitorService', () => { 15 | beforeEach(() => { 16 | TestBed.configureTestingModule({ 17 | providers: [ 18 | {provide: Web3Provider, useFactory: fakeWeb3ProviderFactory}, 19 | {provide: Web3Token, deps: [Web3Provider], useFactory: web3Factory}, 20 | {provide: Web3AccountService, useClass: MockWeb3AccountService}, 21 | {provide: POLL_INTERVAL_MS, useValue: ''}, 22 | Web3MonitorService 23 | ] 24 | }) 25 | }) 26 | 27 | it('should be created', inject([Web3MonitorService], (service: Web3MonitorService) => { 28 | expect(service).toBeTruthy() 29 | })) 30 | }) 31 | -------------------------------------------------------------------------------- /contracts/Accessible.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | 5 | 6 | contract Accessible is Ownable { 7 | //mapping public owners; 8 | mapping(address => bool) public allowedAccounts; 9 | // number of owners 10 | uint public numberOfAccounts; 11 | 12 | event AccessGranted(address indexed newAccount); 13 | event AccessRemoved(address indexed removedAccount); 14 | 15 | modifier onlyOwnerOrAllowed() { 16 | require(msg.sender == owner || allowedAccounts[msg.sender]); 17 | _; 18 | } 19 | 20 | modifier onlyAllowedAccount() { 21 | require(allowedAccounts[msg.sender]); 22 | _; 23 | } 24 | 25 | function grantAccess(address newAccount) public onlyOwnerOrAllowed { 26 | require(newAccount != address(0) && !allowedAccounts[newAccount]); 27 | emit AccessGranted(newAccount); 28 | allowedAccounts[newAccount] = true; 29 | numberOfAccounts += 1; 30 | } 31 | 32 | function removeAccess(address removeAccount) public onlyOwnerOrAllowed { 33 | require(allowedAccounts[removeAccount]); 34 | emit AccessRemoved(removeAccount); 35 | allowedAccounts[removeAccount] = false; 36 | numberOfAccounts -= 1; 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/app/components/submit-paper/submit-paper.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {Web3MonitorService} from '../../providers/web3/web3-monitor/web3-monitor.service' 3 | import {NgbModal} from '@ng-bootstrap/ng-bootstrap' 4 | import {InsufficientBalanceModalComponent} from './insufficient-balance-modal/insufficient-balance-modal.component' 5 | import {SubmitPaperModalComponent} from './submit-paper-modal/submit-paper-modal.component' 6 | 7 | @Component({ 8 | selector: 'app-submit-paper', 9 | templateUrl: './submit-paper.component.html', 10 | styleUrls: ['./submit-paper.component.scss'] 11 | }) 12 | export class SubmitPaperComponent { 13 | 14 | constructor( 15 | private web3Monitor: Web3MonitorService, 16 | private modalService: NgbModal, 17 | ) { 18 | } 19 | 20 | hasInsufficientBalance () { 21 | const balance = this.web3Monitor.networkStatus.getValue().balance 22 | return !balance || balance <= 0; 23 | } 24 | 25 | showTopUpModal() { 26 | this.modalService.open(InsufficientBalanceModalComponent); 27 | } 28 | 29 | showSubmitPaperModal() { 30 | this.modalService.open(SubmitPaperModalComponent); 31 | } 32 | 33 | submitPaperButtonClick() { 34 | if (this.hasInsufficientBalance()) { 35 | return this.showTopUpModal() 36 | } 37 | return this.showSubmitPaperModal() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "aletheia-app", 5 | "ejected": true 6 | }, 7 | "apps": [ 8 | { 9 | "root": "src", 10 | "outDir": "dist", 11 | "assets": [ 12 | "assets", 13 | "favicon.ico" 14 | ], 15 | "index": "index.html", 16 | "main": "main.ts", 17 | "polyfills": "polyfills.ts", 18 | "test": "test.ts", 19 | "tsconfig": "tsconfig.app.json", 20 | "testTsconfig": "tsconfig.spec.json", 21 | "prefix": "app", 22 | "styles": [ 23 | "styles.scss" 24 | ], 25 | "scripts": [ 26 | ], 27 | "environmentSource": "environments/environment.ts", 28 | "environments": { 29 | "dev": "environments/environment.ts", 30 | "prod": "environments/environment.prod.ts" 31 | } 32 | } 33 | ], 34 | "e2e": { 35 | "protractor": { 36 | "config": "./protractor.conf.js" 37 | } 38 | }, 39 | "lint": [ 40 | { 41 | "project": "src/tsconfig.app.json" 42 | }, 43 | { 44 | "project": "src/tsconfig.spec.json" 45 | }, 46 | { 47 | "project": "e2e/tsconfig.e2e.json" 48 | } 49 | ], 50 | "test": { 51 | "karma": { 52 | "config": "./karma.conf.js" 53 | } 54 | }, 55 | "defaults": { 56 | "styleExt": "scss", 57 | "component": { 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app/providers/ipfs/ipfs-client/ipfs-client.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core' 2 | import * as IPFS from 'ipfs' 3 | import * as fs from 'fs' 4 | import {Config} from '../../../../../config/Config' 5 | import {ElectronService} from '../../electron.service' 6 | import {EncodingHelper} from '../../encoding-helper/encoding-helper' 7 | 8 | export class MockIpfsClientService { 9 | addFileFromPath() { 10 | } 11 | async getStream() {} 12 | } 13 | 14 | // Ipfs can only be loaded in electron mode (not web or in karma unit tests) 15 | @Injectable() 16 | export class IpfsClientService { 17 | ipfsClient: IPFS 18 | 19 | constructor(config: Config, 20 | private electronService: ElectronService 21 | ) { 22 | const gatewayUrl = config.ipfs.gatewayUrl 23 | if (this.electronService.isElectron()) { 24 | const factory = require('ipfs-api') 25 | this.ipfsClient = factory(gatewayUrl) 26 | } 27 | } 28 | 29 | async getStream(_ethereumHash: string) { 30 | const ipfsHash = EncodingHelper.hexSha256ToIpfsMultiHash(_ethereumHash) 31 | return ( this.ipfsClient.files).catReadableStream(ipfsHash) 32 | } 33 | 34 | addFileFromPath(fileName: string, filePath: string) { 35 | const fileStream = fs.createReadStream(filePath) 36 | // using the lower level method so that we do not reveal the user's local file path to the network 37 | return this.ipfsClient.files.add([{path: fileName, content: fileStream}]) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/app/providers/error-handler/error-handler.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing' 2 | 3 | import { ErrorHandlerService } from './error-handler.service' 4 | import {NotificationsService} from 'angular2-notifications' 5 | 6 | class MockNotificationService { 7 | error(title: String, message: String, options: any){} 8 | warn(title: String, message: String, options: any){} 9 | } 10 | describe('ErrorHandlerService', () => { 11 | let mockNotificationService = new MockNotificationService () 12 | beforeEach(() => { 13 | this.mockNotificationService = new MockNotificationService() 14 | TestBed.configureTestingModule({ 15 | providers: [ 16 | ErrorHandlerService, 17 | {provide: NotificationsService, useValue: mockNotificationService} 18 | ] 19 | }) 20 | spyOn(mockNotificationService, 'error') 21 | spyOn(mockNotificationService, 'warn') 22 | 23 | }) 24 | 25 | it('handleError should call notification service error', inject([ErrorHandlerService], (service: ErrorHandlerService) => { 26 | service.handleError(new Error("test_error")) 27 | expect(mockNotificationService.error).toHaveBeenCalledWith("Error", "test_error") 28 | })) 29 | it('handleWarning should call notification service warning', inject([ErrorHandlerService], (service: ErrorHandlerService) => { 30 | service.handleWarning(new Error("test_warning")) 31 | expect(mockNotificationService.warn).toHaveBeenCalledWith("Warning", "test_warning") 32 | })) 33 | }) 34 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | webpack: { 16 | node: { 17 | fs: 'empty' 18 | } 19 | }, 20 | client:{ 21 | clearContext: false // leave Jasmine Spec Runner output visible in browser 22 | }, 23 | files: [ 24 | { pattern: './src/test.ts', watched: false } 25 | ], 26 | preprocessors: { 27 | './src/test.ts': ['@angular/cli'] 28 | }, 29 | mime: { 30 | 'text/x-typescript': ['ts','tsx'] 31 | }, 32 | coverageIstanbulReporter: { 33 | reports: [ 'html', 'lcovonly' ], 34 | fixWebpackSourcePaths: true 35 | }, 36 | angularCli: { 37 | environment: 'dev' 38 | }, 39 | reporters: config.angularCli && config.angularCli.codeCoverage 40 | ? ['progress', 'coverage-istanbul'] 41 | : ['progress', 'kjhtml'], 42 | port: 9876, 43 | colors: true, 44 | logLevel: config.LOG_INFO, 45 | autoWatch: true, 46 | browsers: ['Chrome'], 47 | singleRun: false 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3-helper/web3-helper.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | // thanks xavierlepretre https://gist.github.com/xavierlepretre/88682e871f4ad07be4534ae560692ee6 3 | 4 | @Injectable() 5 | export class Web3HelperService { 6 | 7 | constructor() { } 8 | 9 | getTransactionReceiptMined(web3, txnHash) { 10 | const interval = 500; 11 | const maxPolls = 60; 12 | let timesPolled = 0; 13 | const transactionReceiptAsync = function(txnHash, resolve, reject) { 14 | try { 15 | if (timesPolled > maxPolls) { 16 | reject(new Error('Timeout exceeded for an action to be accepted by the aletheia network')) 17 | } 18 | const receipt = web3.eth.getTransactionReceipt(txnHash); 19 | timesPolled++ 20 | if (receipt == null) { 21 | setTimeout(function () { 22 | transactionReceiptAsync(txnHash, resolve, reject); 23 | }, interval); 24 | } else { 25 | resolve(receipt); 26 | } 27 | } catch(e) { 28 | reject(e); 29 | } 30 | }; 31 | 32 | if (Array.isArray(txnHash)) { 33 | const promises = []; 34 | txnHash.forEach(function (oneTxHash) { 35 | promises.push(web3.eth.getTransactionReceiptMined(oneTxHash, interval)); 36 | }); 37 | return Promise.all(promises); 38 | } else { 39 | return new Promise(function (resolve, reject) { 40 | transactionReceiptAsync(txnHash, resolve, reject); 41 | }); 42 | } 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3-client/web3-client.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { Web3ClientService } from './web3-client.service'; 4 | import {Web3HelperService} from '../web3-helper/web3-helper.service' 5 | import {POLL_INTERVAL_MS} from '../../../Injection-tokens' 6 | import {web3Factory} from '../web3/web3.factory' 7 | import * as FakeWeb3Provider from 'web3-fake-provider' 8 | import {Web3Provider} from '../web3-provider/web3-provider.token' 9 | import {Web3Token} from '../web3/web3.token' 10 | import {ContractLoaderService, MockContractLoaderService} from '../../contracts/contract-loader.service' 11 | import {MockWeb3AccountService, Web3AccountService} from '../web3-account/web3-account.service' 12 | 13 | function fakeWeb3ProviderFactory () { 14 | return new FakeWeb3Provider() 15 | } 16 | 17 | describe('Web3ClientService', () => { 18 | beforeEach(() => { 19 | TestBed.configureTestingModule({ 20 | providers: [ 21 | {provide: Web3Provider, useFactory: fakeWeb3ProviderFactory}, 22 | Web3HelperService, 23 | {provide: Web3Token, deps: [Web3Provider], useFactory: web3Factory}, 24 | {provide: ContractLoaderService, useClass: MockContractLoaderService}, 25 | {provide: Web3AccountService, useClass: MockWeb3AccountService}, 26 | {provide: POLL_INTERVAL_MS, useValue: ''}, 27 | Web3ClientService 28 | ] 29 | }); 30 | }); 31 | 32 | it('should be created', inject([Web3ClientService], (service: Web3ClientService) => { 33 | expect(service).toBeTruthy(); 34 | })); 35 | }); 36 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var packager = require('electron-packager'); 4 | const pkg = require('./package.json'); 5 | const argv = require('minimist')(process.argv.slice(1)); 6 | 7 | const appName = argv.name || pkg.name; 8 | const buildVersion = pkg.version || '1.0'; 9 | const shouldUseAsar = argv.asar || false; 10 | const shouldBuildAll = argv.all || false; 11 | const arch = argv.arch || 'all'; 12 | const platform = argv.platform || 'darwin'; 13 | 14 | const DEFAULT_OPTS = { 15 | dir: './dist', 16 | name: appName, 17 | asar: shouldUseAsar, 18 | buildVersion: buildVersion 19 | }; 20 | 21 | 22 | pack(platform, arch, function done(err, appPath) { 23 | if (err) { 24 | console.log(err); 25 | } else { 26 | console.log('Application packaged successfuly!', appPath); 27 | } 28 | 29 | }); 30 | 31 | function pack(plat, arch, cb) { 32 | // there is no darwin ia32 electron 33 | if (plat === 'darwin' && arch === 'ia32') return; 34 | 35 | let icon = 'src/favicon'; 36 | 37 | if (icon) { 38 | DEFAULT_OPTS.icon = icon + (() => { 39 | let extension = '.png'; 40 | if (plat === 'darwin') { 41 | extension = '.icns'; 42 | } else if (plat === 'win32') { 43 | extension = '.ico'; 44 | } 45 | return extension; 46 | })(); 47 | } 48 | 49 | const opts = Object.assign({}, DEFAULT_OPTS, { 50 | platform: plat, 51 | arch, 52 | prune: true, 53 | overwrite: true, 54 | all: shouldBuildAll, 55 | out: `app-builds` 56 | }); 57 | 58 | console.log(opts) 59 | packager(opts, cb); 60 | } 61 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | void setBuildStatus(String message, String state) { 2 | step([ 3 | $class: "GitHubCommitStatusSetter", 4 | reposSource: [$class: "ManuallyEnteredRepositorySource", url: "https://github.com/aletheia-foundation/aletheia-app"], 5 | contextSource: [$class: "ManuallyEnteredCommitContextSource", context: "ci/jenkins/build-status"], 6 | errorHandlers: [[$class: "ChangingBuildStatusErrorHandler", result: "UNSTABLE"]], 7 | statusResultSource: [ $class: "ConditionalStatusResultSource", results: [[$class: "AnyBuildResult", message: message, state: state]] ] 8 | ]); 9 | } 10 | 11 | pipeline { 12 | agent any 13 | tools { nodejs "node9.4" } 14 | stages { 15 | stage('Build') { 16 | steps { 17 | echo 'build step' 18 | sh 'npm install' 19 | } 20 | } 21 | stage('Test') { 22 | steps { 23 | echo 'test step' 24 | sh 'npm run test:ci' 25 | sh 'npm run test-truffle:ci' 26 | } 27 | } 28 | stage('Generate app builds') { 29 | steps { 30 | echo 'Generate app builds step' 31 | sh 'npm run electron:linux' 32 | archiveArtifacts artifacts: 'app-builds/**', fingerprint: true 33 | } 34 | } 35 | stage('Deploy contracts') { 36 | steps { 37 | echo 'deploy contracts step' 38 | } 39 | } 40 | } 41 | post { 42 | success { 43 | setBuildStatus("Build complete", "SUCCESS"); 44 | } 45 | failure { 46 | setBuildStatus("Build failed", "FAILURE"); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/components/home/home.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../theme/variables-and-mixins.scss'; 2 | 3 | span { 4 | padding: 0px 10px 0px 10px; 5 | } 6 | 7 | #search-papers { 8 | color: $body-color; 9 | background-color: #32B67A; 10 | width: 400px; 11 | border-radius: 10px; 12 | border: 1px solid $gray-100; 13 | font-size: 16px; 14 | } 15 | 16 | input[type="text"] { 17 | width: 200px; 18 | height: 40px; 19 | padding-left: 15px; 20 | padding-right: 50px; 21 | outline: none; 22 | } 23 | 24 | button[type="submit"] { 25 | margin-left: -50px; 26 | height: 20px; 27 | width: 50px; 28 | background: none; 29 | border: none; 30 | outline: none; 31 | color: $body-color; 32 | border-left: 1px solid #eeeeee; 33 | font-size: 16px; 34 | } 35 | 36 | #search-papers::-webkit-input-placeholder { /* Chrome/Opera/Safari */ 37 | color: $body-color; 38 | } 39 | #search-papers::-moz-placeholder { /* Firefox 19+ */ 40 | color: $body-color; 41 | } 42 | #search-papers:-ms-input-placeholder { /* IE 10+ */ 43 | color: $body-color; 44 | } 45 | #search-papers:-moz-placeholder { /* Firefox 18- */ 46 | color: $body-color; 47 | } 48 | .container { 49 | display: flex; 50 | background-color: #32B67A; 51 | color: $body-color; 52 | font-family: $font-family-base; 53 | text-align: center; 54 | flex-direction: column; 55 | align-items: center; 56 | 57 | .title { 58 | font-family: $font-family-serif; 59 | font-size: 48px; 60 | } 61 | 62 | .subtitle { 63 | margin-bottom: 40px; 64 | } 65 | 66 | ul { 67 | list-style: none; 68 | padding-left: 0; 69 | } 70 | 71 | a { 72 | display: inline-block; 73 | color: inherit; 74 | font-weight: 300; 75 | font-size: 14px; 76 | text-decoration: none; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /scripts/testnet-bootnode/setup-bootnode-debian.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This assumes that a [jenkins-deployer user is setup](https://github.com/aletheia-foundation/jenkins) 3 | 4 | # install geth 5 | sudo add-apt-repository -y ppa:ethereum/ethereum 6 | sudo apt-get install -y software-properties-common # required by geth 7 | sudo apt-get update 8 | sudo apt-get install ethereum -y 9 | 10 | # This assumes that a [jenkins-deployer user is setup](https://github.com/aletheia-foundation/jenkins) 11 | # User and directory ownership 12 | sudo useradd -m aletheia-bootnode 13 | sudo groupadd bootnode-users 14 | sudo usermod -a -G bootnode-users jenkins-deployer 15 | sudo usermod -a -G bootnode-users aletheia-bootnode 16 | 17 | sudo mkdir /var/aletheia-bootnode 18 | 19 | # Add service restart script 20 | 21 | # Setup service 22 | sudo dd of=/etc/systemd/system/aletheia-bootnode.service << EOF 23 | [Service] 24 | ExecStart=/var/aletheia-bootnode/bootnode/scripts/start-geth-testnet.sh 25 | Restart=always 26 | StandardOutput=syslog 27 | StandardError=syslog 28 | SyslogIdentifier=aletheia-bootnode 29 | User=aletheia-bootnode 30 | Group=aletheia-bootnode 31 | WorkingDirectory=/var/aletheia-bootnode/bootnode/ 32 | 33 | [Install] 34 | WantedBy=multi-user.target 35 | EOF 36 | 37 | sudo systemctl enable aletheia-bootnode.service 38 | 39 | # Allow jenkins-deployer to restart the service 40 | sudo dd of=/var/aletheia-bootnode/restart-bootnode.sh << EOF 41 | chmod +x /var/aletheia-bootnode/bootnode/scripts/start-geth-testnet.sh 42 | systemctl restart aletheia-bootnode 43 | EOF 44 | 45 | sudo dd of=/etc/sudoers.d/aletheia-bootnode << EOF 46 | %jenkins-deployer ALL= NOPASSWD: /var/aletheia-bootnode/restart-bootnode.sh 47 | EOF 48 | 49 | sudo chown -R aletheia-bootnode:bootnode-users /var/aletheia-bootnode 50 | sudo chmod +x,g+x /var/aletheia-bootnode/restart-bootnode.sh 51 | 52 | sudo chmod g+rwxs /var/aletheia-bootnode 53 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | .sr-only() { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | .sr-only-focusable() { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; 16 | -webkit-transform: rotate($degrees); 17 | -ms-transform: rotate($degrees); 18 | transform: rotate($degrees); 19 | } 20 | 21 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; 23 | -webkit-transform: scale($horiz, $vert); 24 | -ms-transform: scale($horiz, $vert); 25 | transform: scale($horiz, $vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | @mixin sr-only { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | @mixin sr-only-focusable { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/ManuscriptIndex.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./Accessible.sol"; 4 | 5 | // A linked list for storing manuscripts inspired by: 6 | // https://github.com/Modular-Network/ethereum-libraries/blob/master/LinkedListLib/LinkedListLib.sol 7 | contract ManuscriptIndex is Accessible { 8 | 9 | // storage is realized as double linked list indexes 10 | mapping(bytes32 => mapping(bool => bytes32) ) public dllIndex; 11 | mapping(bytes32 => address) public manuscriptAddress; 12 | 13 | // adds a new manuscript address 14 | // `_hash` an ipfs hash to use for retrieving the manuscript 15 | // `_addr` the address of a new manuscript contract to add to Aletheia 16 | function add(bytes32 _hash, address _addr) public onlyAllowedAccount { 17 | // require input to be not empty 18 | require(_hash != 0x00 && _addr != 0x00); 19 | // require element to be addend to be newAuthor 20 | require(manuscriptAddress[_hash] == 0x00); 21 | // Link the new node 22 | dllIndex[_hash][false] = 0x0; 23 | dllIndex[_hash][true] = dllIndex[0x0][true]; 24 | 25 | // Insert the new node 26 | dllIndex[dllIndex[0x0][true]][false] = _hash; 27 | dllIndex[0x0][true] = _hash; 28 | manuscriptAddress[_hash] = _addr; 29 | } 30 | 31 | function remove(bytes32 _hash) public onlyAllowedAccount { 32 | // require input to be not empty 33 | require(_hash != 0x00); 34 | // check existens of element to be removed 35 | require(manuscriptAddress[_hash] != 0x00); 36 | // Stitch the neighbours together 37 | dllIndex[dllIndex[_hash][false]][true] = dllIndex[_hash][true]; 38 | dllIndex[dllIndex[_hash][true]][false] = dllIndex[_hash][false]; 39 | 40 | // Delete state storage 41 | delete dllIndex[_hash][false]; 42 | delete dllIndex[_hash][true]; 43 | delete manuscriptAddress[_hash]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/CloneFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.19; 2 | 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2018 Murray Software, LLC. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included 17 | in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | //solhint-disable max-line-length 28 | //solhint-disable no-inline-assembly 29 | 30 | contract CloneFactory { 31 | 32 | event CloneCreated(address indexed target, address clone); 33 | 34 | function createClone(address target) internal returns (address result) { 35 | bytes memory clone = hex"600034603b57602f80600f833981f36000368180378080368173bebebebebebebebebebebebebebebebebebebebe5af415602c573d81803e3d81f35b80fd"; 36 | bytes20 targetBytes = bytes20(target); 37 | for (uint i = 0; i < 20; i++) { 38 | clone[26 + i] = targetBytes[i]; 39 | } 40 | assembly { 41 | let len := mload(clone) 42 | let data := add(clone, 0x20) 43 | result := create(0, data, len) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/components/submit-paper/submit-paper-modal/submit-paper-modal.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 9 | 37 | 42 |
43 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow, screen } from 'electron'; 2 | 3 | let win, serve; 4 | const args = process.argv.slice(1); 5 | serve = args.some(val => val === '--serve'); 6 | 7 | if (serve) { 8 | require('electron-reload')(__dirname, { 9 | electron: './node_modules/.bin/electron' 10 | }); 11 | } 12 | 13 | function createWindow() { 14 | 15 | const electronScreen = screen; 16 | const size = electronScreen.getPrimaryDisplay().workAreaSize; 17 | 18 | // Create the browser window. 19 | win = new BrowserWindow({ 20 | x: 0, 21 | y: 0, 22 | width: size.width, 23 | height: size.height 24 | }); 25 | 26 | // and load the index.html of the app. 27 | win.loadURL('file://' + __dirname + '/index.html'); 28 | 29 | // Open the DevTools. 30 | if (serve) { 31 | win.webContents.openDevTools(); 32 | } 33 | 34 | // Emitted when the window is closed. 35 | win.on('closed', () => { 36 | // Dereference the window object, usually you would store window 37 | // in an array if your app supports multi windows, this is the time 38 | // when you should delete the corresponding element. 39 | win = null; 40 | }); 41 | } 42 | 43 | try { 44 | 45 | // This method will be called when Electron has finished 46 | // initialization and is ready to create browser windows. 47 | // Some APIs can only be used after this event occurs. 48 | app.on('ready', createWindow); 49 | 50 | // Quit when all windows are closed. 51 | app.on('window-all-closed', () => { 52 | // On OS X it is common for applications and their menu bar 53 | // to stay active until the user quits explicitly with Cmd + Q 54 | if (process.platform !== 'darwin') { 55 | app.quit(); 56 | } 57 | }); 58 | 59 | app.on('activate', () => { 60 | // On OS X it's common to re-create a window in the app when the 61 | // dock icon is clicked and there are no other windows open. 62 | if (win === null) { 63 | createWindow(); 64 | } 65 | }); 66 | 67 | } catch (e) { 68 | // Catch Error 69 | // throw e; 70 | } 71 | -------------------------------------------------------------------------------- /scripts/add-test-data.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3') 2 | const contract = require('truffle-contract') 3 | const aletheiaJson = require('../build/contracts/Aletheia.json') 4 | 5 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) 6 | 7 | const AletheiaContract = contract(aletheiaJson) 8 | AletheiaContract.setProvider(new Web3.providers.HttpProvider('http://localhost:8545')) 9 | AletheiaContract.defaults({ 10 | from: web3.eth.accounts[0], 11 | gas: "1000000" 12 | }) 13 | async function go() { 14 | 15 | // setup web3 16 | const aletheiContract = await AletheiaContract.deployed() 17 | // Submit a paper for review from user 1 18 | const fileHashBytes1 = '0xbfccda787baba32b59c78450ac3d20b633360b43992c77289f9ed46d843561e1' //encoded ipfs address 19 | const fileHashBytes2 = '0xbfccda787baba32b59c78450ac3d20b633360b43992c77289f9ed46d843561e2' //encoded ipfs address 20 | const fileHashBytes3 = '0xbfccda787baba32b59c78450ac3d20b633360b43992c77289f9ed46d843561e3' //encoded ipfs address 21 | const title = 'Test manuscript ' 22 | const authors = [web3.eth.accounts[1]] 23 | const result1 = await aletheiContract.newManuscript(fileHashBytes1, title + 1, authors) 24 | const result2 = await aletheiContract.newManuscript(fileHashBytes2, title + 2, authors) 25 | const result3 = await aletheiContract.newManuscript(fileHashBytes3, title + 3, authors) 26 | aletheiContract.communityVote(fileHashBytes1, true, {from:web3.eth.accounts[2]}) 27 | aletheiContract.communityVote(fileHashBytes1, true, {from:web3.eth.accounts[3]}) 28 | aletheiContract.communityVote(fileHashBytes2, false, {from:web3.eth.accounts[2]}) 29 | aletheiContract.communityVote(fileHashBytes2, false, {from:web3.eth.accounts[3]}) 30 | // 31 | // for (var i = 0; i < 10; i++) { 32 | // await new Promise((resolve,reject) => { 33 | // web3.currentProvider.sendAsync({ 34 | // jsonrpc: "2.0", 35 | // method: "evm_mine", 36 | // id: 12345 37 | // }, () => { 38 | // resolve() 39 | // }); 40 | // }) 41 | // } 42 | 43 | } 44 | 45 | go().then((a)=>{ 46 | console.log('done') 47 | }).catch((e) => { 48 | console.error(e) 49 | }) 50 | 51 | 52 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import electron from './node_modules/electron'; 2 | import url from './node_modules/url'; 3 | import path from './node_modules/path'; 4 | import config from './node_modules/config'; 5 | 6 | // Module to control application life. 7 | const app = electron.app 8 | // Module to create native browser window. 9 | const BrowserWindow = electron.BrowserWindow 10 | const {dialog} = require('electron') 11 | 12 | // Keep a global reference of the window object, if you don't, the window will 13 | // be closed automatically when the JavaScript object is garbage collected. 14 | let mainWindow 15 | 16 | function createWindow () { 17 | // Create the browser window. 18 | mainWindow = new BrowserWindow({width: 800, height: 600}) 19 | 20 | // and load the index.html of the app. 21 | mainWindow.loadURL(url.format({ 22 | pathname: path.join(__dirname, '/app/submit-paper/submit-paper.html'), 23 | protocol: 'file:', 24 | slashes: true 25 | })) 26 | 27 | // Open the DevTools. 28 | mainWindow.webContents.openDevTools() 29 | 30 | // Emitted when the window is closed. 31 | mainWindow.on('closed', function () { 32 | // Dereference the window object, usually you would store windows 33 | // in an array if your app supports multi windows, this is the time 34 | // when you should delete the corresponding element. 35 | mainWindow = null 36 | }) 37 | } 38 | 39 | // This method will be called when Electron has finished 40 | // initialization and is ready to create browser windows. 41 | // Some APIs can only be used after this event occurs. 42 | app.on('ready', createWindow) 43 | 44 | // Quit when all windows are closed. 45 | app.on('window-all-closed', function () { 46 | // On OS X it is common for applications and their menu bar 47 | // to stay active until the user quits explicitly with Cmd + Q 48 | if (process.platform !== 'darwin') { 49 | app.quit() 50 | } 51 | }) 52 | 53 | app.on('activate', function () { 54 | // On OS X it's common to re-create a window in the app when the 55 | // dock icon is clicked and there are no other windows open. 56 | if (mainWindow === null) { 57 | createWindow() 58 | } 59 | }) 60 | 61 | // In this file you can include the rest of your app's specific main process 62 | // code. You can also put them in separate files and require them here. 63 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import 'app/app.component.scss'; 3 | @import 'assets/font-awesome-4.7.0/css/font-awesome.min.css'; 4 | 5 | @import "theme/variables-and-mixins"; 6 | 7 | /* Import bits of bootstrap that we use */ 8 | @import "../node_modules/bootstrap/scss/root"; 9 | @import "../node_modules/bootstrap/scss/reboot"; 10 | @import "../node_modules/bootstrap/scss/type"; 11 | @import "../node_modules/bootstrap/scss/images"; 12 | @import "../node_modules/bootstrap/scss/code"; 13 | @import "../node_modules/bootstrap/scss/grid"; 14 | @import "../node_modules/bootstrap/scss/tables"; 15 | @import "../node_modules/bootstrap/scss/forms"; 16 | @import "../node_modules/bootstrap/scss/buttons"; 17 | @import "../node_modules/bootstrap/scss/transitions"; 18 | @import "../node_modules/bootstrap/scss/dropdown"; 19 | @import "../node_modules/bootstrap/scss/button-group"; 20 | @import "../node_modules/bootstrap/scss/input-group"; 21 | //@import "../node_modules/bootstrap/scss/custom-forms"; 22 | //@import "../node_modules/bootstrap/scss/nav"; 23 | //@import "../node_modules/bootstrap/scss/navbar"; 24 | //@import "../node_modules/bootstrap/scss/card"; 25 | @import "../node_modules/bootstrap/scss/breadcrumb"; 26 | //@import "../node_modules/bootstrap/scss/pagination"; 27 | //@import "../node_modules/bootstrap/scss/badge"; 28 | //@import "../node_modules/bootstrap/scss/jumbotron"; 29 | @import "../node_modules/bootstrap/scss/alert"; 30 | @import "../node_modules/bootstrap/scss/progress"; 31 | //@import "../node_modules/bootstrap/scss/media"; 32 | //@import "../node_modules/bootstrap/scss/list-group"; 33 | @import "../node_modules/bootstrap/scss/close"; 34 | @import "../node_modules/bootstrap/scss/modal"; 35 | //@import "../node_modules/bootstrap/scss/tooltip"; 36 | //@import "../node_modules/bootstrap/scss/popover"; 37 | //@import "../node_modules/bootstrap/scss/carousel"; 38 | @import "../node_modules/bootstrap/scss/utilities"; 39 | //@import "../node_modules/bootstrap/scss/print"; 40 | 41 | /* Extend bootstrap with our own styles as necessary */ 42 | @import "theme/type"; 43 | @import "theme/notifications"; 44 | @import "theme/modal"; 45 | 46 | html, body { 47 | margin: 0; 48 | padding: 0; 49 | height: 100vh; 50 | color: #EEEEEE; 51 | background-color: #32B67A; 52 | font-family: $font-family-base; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /test/helpers/expectThrow.js: -------------------------------------------------------------------------------- 1 | // taken & modified from open-zepplin project 2 | // https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js 3 | // This part is under MIT License: 4 | // https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/LICENSE 5 | 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2016 Smart Contract Solutions, Inc. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining 11 | // a copy of this software and associated documentation files (the 12 | // "Software"), to deal in the Software without restriction, including 13 | // without limitation the rights to use, copy, modify, merge, publish, 14 | // distribute, sublicense, and/or sell copies of the Software, and to 15 | // permit persons to whom the Software is furnished to do so, subject to 16 | // the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included 19 | // in all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 22 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | 30 | 31 | 32 | module.exports = async promise => { 33 | try { 34 | await promise; 35 | } catch (error) { 36 | // TODO: Check jump destination to destinguish between a throw 37 | // and an actual invalid jump. 38 | const invalidOpcode = error.message.search('invalid opcode') >= 0; 39 | // TODO: When we contract A calls contract B, and B throws, instead 40 | // of an 'invalid jump', we get an 'out of gas' error. How do 41 | // we distinguish this from an actual out of gas event? (The 42 | // testrpc log actually show an 'invalid jump' event.) 43 | const outOfGas = error.message.search('out of gas') >= 0; 44 | assert( 45 | invalidOpcode || outOfGas, 46 | "Expected throw, got '" + error + "' instead", 47 | ); 48 | return; 49 | } 50 | assert.fail('Expected throw not received'); 51 | }; 52 | -------------------------------------------------------------------------------- /test/helpers/expectRevert.js: -------------------------------------------------------------------------------- 1 | // taken & modified from open-zepplin project 2 | // https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js 3 | // This part is under MIT License: 4 | // https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/LICENSE 5 | 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2016 Smart Contract Solutions, Inc. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining 11 | // a copy of this software and associated documentation files (the 12 | // "Software"), to deal in the Software without restriction, including 13 | // without limitation the rights to use, copy, modify, merge, publish, 14 | // distribute, sublicense, and/or sell copies of the Software, and to 15 | // permit persons to whom the Software is furnished to do so, subject to 16 | // the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included 19 | // in all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 22 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | 30 | 31 | 32 | module.exports = async promise => { 33 | try { 34 | await promise; 35 | } catch (error) { 36 | // TODO: Check jump destination to destinguish between a throw 37 | // and an actual invalid jump. 38 | const revert = error.message.search('revert') >= 0; 39 | // TODO: When we contract A calls contract B, and B throws, instead 40 | // of an 'invalid jump', we get an 'out of gas' error. How do 41 | // we distinguish this from an actual out of gas event? (The 42 | // testrpc log actually show an 'invalid jump' event.) 43 | const outOfGas = error.message.search('out of gas') >= 0; 44 | assert( 45 | revert || outOfGas, 46 | "Expected transaction revert, got '" + error + "' instead", 47 | ); 48 | return; 49 | } 50 | assert.fail('Expected transaction revert not received'); 51 | }; 52 | -------------------------------------------------------------------------------- /src/app/components/submit-paper/insufficient-balance-modal/insufficient-balance-modal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing' 2 | 3 | import { InsufficientBalanceModalComponent } from './insufficient-balance-modal.component' 4 | import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap' 5 | import {FormsModule, ReactiveFormsModule} from '@angular/forms' 6 | import {Config} from '../../../../../config/Config' 7 | import {NotificationsService} from 'angular2-notifications' 8 | import {ErrorHandlerService, MockErrorHandlerService} from '../../../providers/error-handler/error-handler.service' 9 | import {MockWeb3AccountService, Web3AccountService} from '../../../providers/web3/web3-account/web3-account.service' 10 | import {MockWeb3ClientService, Web3ClientService} from '../../../providers/web3/web3-client/web3-client.service' 11 | import {HttpClientModule} from '@angular/common/http' 12 | 13 | describe('InsufficientBalanceModalComponent', () => { 14 | let component: InsufficientBalanceModalComponent 15 | let fixture: ComponentFixture 16 | let compiled: any 17 | 18 | const mockErrorHandlerService = new MockErrorHandlerService() 19 | const mockWeb3AccountService = new MockWeb3AccountService() 20 | const mockWeb3ClientService = new MockWeb3ClientService() 21 | 22 | beforeEach(async(() => { 23 | TestBed.configureTestingModule({ 24 | declarations: [ InsufficientBalanceModalComponent ], 25 | providers: [ 26 | NgbActiveModal, 27 | Config, 28 | { provide: NotificationsService, useValue: new NotificationsService({})}, 29 | { provide: ErrorHandlerService, useValue: mockErrorHandlerService }, 30 | { provide: Web3AccountService, useValue: mockWeb3AccountService }, 31 | { provide: Web3ClientService, useValue: mockWeb3ClientService } 32 | ], 33 | imports: [ 34 | HttpClientModule, 35 | ReactiveFormsModule, 36 | FormsModule 37 | ] 38 | }) 39 | .compileComponents() 40 | })) 41 | 42 | beforeEach(() => { 43 | fixture = TestBed.createComponent(InsufficientBalanceModalComponent) 44 | component = fixture.componentInstance 45 | compiled = fixture.debugElement.nativeElement 46 | fixture.detectChanges() 47 | }) 48 | 49 | it('should load the captcha image', () => { 50 | expect(compiled.querySelector('#captcha-img').src).toContain('captcha') 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3-monitor/web3-monitor.service.ts: -------------------------------------------------------------------------------- 1 | import {Inject, Injectable} from '@angular/core' 2 | import {POLL_INTERVAL_MS} from '../../../Injection-tokens' 3 | import {Web3Token} from '../web3/web3.token' 4 | import {Web3NetworkStatus} from './web3-network-status' 5 | import {BehaviorSubject} from 'rxjs/BehaviorSubject' 6 | import {Web3AccountService} from '../web3-account/web3-account.service' 7 | 8 | export class MockWeb3MonitorService { 9 | public networkStatus: BehaviorSubject = new BehaviorSubject(new Web3NetworkStatus(null, 0, '', 0)) 10 | start () {} 11 | } 12 | 13 | @Injectable() 14 | export class Web3MonitorService { 15 | poll: any 16 | public networkStatus: BehaviorSubject 17 | 18 | constructor(@Inject(POLL_INTERVAL_MS) private pollInterval: string, 19 | private accountService: Web3AccountService, 20 | @Inject(Web3Token) private web3: any) { 21 | this.networkStatus = new BehaviorSubject(new Web3NetworkStatus( 22 | null, 23 | 0, 24 | '', 25 | 0 26 | )) 27 | } 28 | 29 | start() { 30 | this.poll = setInterval(this.onPoll.bind(this), this.pollInterval) 31 | this.checkConnection() 32 | } 33 | 34 | stop() { 35 | clearInterval(this.poll) 36 | } 37 | 38 | isConnected() { 39 | return this.web3.isConnected() 40 | } 41 | 42 | private onPoll() { 43 | this.checkConnection() 44 | } 45 | 46 | private checkConnection() { 47 | this.web3.net.getPeerCount((err, numPeers) => { 48 | if (err) { 49 | console.error(err, err.stack) 50 | this.networkStatus.next(new Web3NetworkStatus(err, 0, '', 0)) 51 | return 52 | } else { 53 | const account = this.accountService.getAccount() 54 | if (!account) { 55 | const error = new Error('Unable to find blockchain address') 56 | console.error(error, error.stack) 57 | this.networkStatus.next(new Web3NetworkStatus(error, 0, '', 0)) 58 | return 59 | } 60 | this.web3.eth.getBalance(account, (error, weiBalance) => { 61 | const ethBalance = this.web3.fromWei(weiBalance, 'ether') 62 | if (error) { 63 | console.error(error, error.stack) 64 | this.networkStatus.next(new Web3NetworkStatus(error, 0, '', 0)) 65 | return 66 | } else { 67 | this.networkStatus.next(new Web3NetworkStatus( 68 | null, 69 | numPeers, 70 | account, 71 | ethBalance 72 | )) 73 | } 74 | }) 75 | } 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/Reputation.spec.js: -------------------------------------------------------------------------------- 1 | const expectRevert = require('../test/helpers/expectRevert') 2 | const expectThrow = require('../test/helpers/expectThrow') 3 | 4 | var Reputation = artifacts.require('../contracts/Reputation.sol') 5 | //var Aletheia = artifacts.require('../contracts/Aletheia.sol') 6 | 7 | contract('Reputation', function(accounts) { 8 | var instance; 9 | var reputation1, reputation2, reputation3, reputation4, reputation5, reputation6; 10 | var manuscript1; 11 | 12 | it('add reputation to account', async function() { 13 | instance = await Reputation.deployed(); 14 | 15 | // give access to accounts[0] to change reputation 16 | await instance.grantAccess(accounts[0]); 17 | 18 | // check inital reputation 19 | reputation1 = await instance.reputationOf(accounts[0]); 20 | assert.equal(reputation1, 0, "inital reputation is not 0"); 21 | 22 | // add reputation 23 | await instance.addReputation(accounts[0], 100, {from: accounts[0]}); 24 | 25 | // check final reputation 26 | reputation2 = await instance.reputationOf(accounts[0]); 27 | assert.equal(reputation2, 100, "new reputation is not 100"); 28 | }) 29 | 30 | it('remove reputation from account', async function() { 31 | 32 | // check inital reputation 33 | reputation3 = await instance.reputationOf(accounts[0]); 34 | assert.equal(reputation3, 100, "inital reputation is not 100"); 35 | 36 | // substract reputation 37 | await instance.removeReputation(accounts[0], 50); 38 | 39 | // check final reputation 40 | reputation4 = await instance.reputationOf(accounts[0]); 41 | assert.equal(reputation4, 50, "new reputation is not 50"); 42 | 43 | }) 44 | 45 | it('check that negative reputation is impossible', async function() { 46 | 47 | // check inital reputation 48 | reputation5 = await instance.reputationOf(accounts[0]); 49 | assert.equal(reputation5, 50, "inital reputation is not 50"); 50 | 51 | // try to sustract more reputation then there is for the account 52 | await expectThrow(instance.removeReputation(accounts[0], 100)); 53 | 54 | // check final reputation 55 | reputation6 = await instance.reputationOf(accounts[0]); 56 | assert.equal(reputation6, 50, "reputation is not 50"); 57 | 58 | }) 59 | 60 | it('checks ownership restrictions', async function() { 61 | 62 | // check for revert transaction of addReputation() & removeReputation() 63 | // when msg.sender is not owner 64 | await expectRevert(instance.addReputation(accounts[0], 100, {from: accounts[1]})); 65 | await expectRevert(instance.removeReputation(accounts[0], 100, {from: accounts[1]})); 66 | }) 67 | 68 | 69 | }) 70 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/set'; 35 | 36 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 37 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 38 | 39 | /** IE10 and IE11 requires the following to support `@angular/animation`. */ 40 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 41 | 42 | 43 | /** Evergreen browsers require these. **/ 44 | import 'core-js/es6/reflect'; 45 | import 'core-js/es7/reflect'; 46 | 47 | 48 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * Zone JS is required by Angular itself. 55 | */ 56 | import 'zone.js/dist/zone-mix'; // Included with Angular CLI. 57 | 58 | 59 | /*************************************************************************************************** 60 | * APPLICATION IMPORTS 61 | */ 62 | 63 | /** 64 | * Date, currency, decimal and percent pipes. 65 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 66 | */ 67 | // import 'intl'; // Run `npm install --save intl`. 68 | -------------------------------------------------------------------------------- /src/app/components/submit-paper/insufficient-balance-modal/insufficient-balance-modal.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, OnInit} from '@angular/core' 2 | import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap' 3 | import {HttpClient} from '@angular/common/http' 4 | import {Config} from '../../../../../config/Config' 5 | import {NotificationsService} from 'angular2-notifications' 6 | import {ErrorHandlerService} from '../../../providers/error-handler/error-handler.service' 7 | import {Web3AccountService} from '../../../providers/web3/web3-account/web3-account.service' 8 | import {Web3ClientService} from '../../../providers/web3/web3-client/web3-client.service' 9 | 10 | @Component({ 11 | selector: 'insufficient-balance-modal', 12 | templateUrl: './insufficient-balance-modal.component.html', 13 | styleUrls: ['./insufficient-balance-modal.component.scss'] 14 | }) 15 | export class InsufficientBalanceModalComponent implements OnInit { 16 | captchaAnswer: string 17 | awaitingFaucetResult: boolean 18 | captchaUrl: string 19 | 20 | constructor( 21 | public activeModal: NgbActiveModal, 22 | private http: HttpClient, 23 | private config: Config, 24 | private notificationsService: NotificationsService, 25 | private errorHandler: ErrorHandlerService, 26 | private accountService: Web3AccountService, 27 | private web3Client: Web3ClientService 28 | ) { 29 | } 30 | 31 | ngOnInit() { 32 | this.captchaUrl = this.generateCaptchaUrl() 33 | } 34 | 35 | onSubmitAnswer() { 36 | const requestArgs = { 37 | receiver: this.accountService.getAccount(), 38 | captcha: this.captchaAnswer 39 | } 40 | this.awaitingFaucetResult = true 41 | this.http.post(this.config.faucetUrl, requestArgs) 42 | .toPromise() 43 | .then((result: any) => { 44 | if (result.error || !result.success) { 45 | this.captchaUrl = this.generateCaptchaUrl() 46 | this.captchaAnswer = '' 47 | this.awaitingFaucetResult = false 48 | return this.errorHandler.handleError(result.error, result.error.message) 49 | } 50 | return this.web3Client.awaitTransaction(result.success.txHash) 51 | .then(() => { 52 | this.awaitingFaucetResult = false 53 | this.notificationsService.success('Success', 'You have been granted Aletheia Goodwill') 54 | this.activeModal.close() 55 | }) 56 | }) 57 | .catch((err) => { 58 | this.awaitingFaucetResult = false 59 | this.captchaUrl = this.generateCaptchaUrl() 60 | this.errorHandler.handleError(err, 'Unable to recieve Aletheia Goodwill') 61 | }) 62 | } 63 | 64 | generateCaptchaUrl(): string { 65 | // include the time to trigger refresh 66 | return `${this.config.faucetUrl}/captcha.svg?${new Date().getTime()}` 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/app/components/submit-paper/submit-paper.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing' 2 | 3 | import { SubmitPaperComponent } from './submit-paper.component' 4 | import {Web3NetworkStatus} from '../../providers/web3/web3-monitor/web3-network-status' 5 | import {MockWeb3MonitorService, Web3MonitorService} from '../../providers/web3/web3-monitor/web3-monitor.service' 6 | import {NgbModal, NgbModule} from '@ng-bootstrap/ng-bootstrap' 7 | import {InsufficientBalanceModalComponent} from './insufficient-balance-modal/insufficient-balance-modal.component' 8 | import {SubmitPaperModalComponent} from './submit-paper-modal/submit-paper-modal.component' 9 | 10 | class MockNgbModal { 11 | public component = { 12 | componentInstance: { 13 | address: '' 14 | } 15 | }; 16 | public open() { 17 | return this.component 18 | } 19 | } 20 | 21 | describe('SubmitPaperComponent', () => { 22 | let component: SubmitPaperComponent 23 | let fixture: ComponentFixture 24 | let compiled: any 25 | const mockWeb3Monitor = new MockWeb3MonitorService() 26 | const mockNgbModal = new MockNgbModal() 27 | 28 | beforeEach(async(() => { 29 | TestBed.configureTestingModule({ 30 | imports: [NgbModule.forRoot()], 31 | declarations: [ SubmitPaperComponent ], 32 | providers: [ 33 | {provide: Web3MonitorService, useValue: mockWeb3Monitor}, 34 | {provide: NgbModal, useValue: mockNgbModal } 35 | ] 36 | 37 | }) 38 | .compileComponents() 39 | })) 40 | 41 | beforeEach(() => { 42 | fixture = TestBed.createComponent(SubmitPaperComponent) 43 | component = fixture.componentInstance 44 | compiled = fixture.debugElement.nativeElement; 45 | fixture.detectChanges() 46 | }) 47 | 48 | it('should create', () => { 49 | expect(component).toBeTruthy() 50 | }) 51 | 52 | describe('Clicking on button when balance is zero', () => { 53 | beforeEach(() => { 54 | spyOn(mockNgbModal, 'open') 55 | mockWeb3Monitor.networkStatus.next(new Web3NetworkStatus(null, 0, '0xTESTADDRESS', 0)) 56 | compiled.querySelector('.submit-paper-button').click() 57 | fixture.detectChanges() 58 | }) 59 | it('should show a modal to top up account', () => { 60 | expect(mockNgbModal.open).toHaveBeenCalledWith(InsufficientBalanceModalComponent) 61 | }) 62 | }) 63 | describe('Clicking on button when balance is not zero', () => { 64 | beforeEach(() => { 65 | spyOn(mockNgbModal, 'open') 66 | mockWeb3Monitor.networkStatus.next(new Web3NetworkStatus(null, 0, '0xTESTADDRESS', 1000000)) 67 | compiled.querySelector('.submit-paper-button').click() 68 | fixture.detectChanges() 69 | }) 70 | it('should show a modal to upload a file', () => { 71 | expect(mockNgbModal.open).toHaveBeenCalledWith(SubmitPaperModalComponent) 72 | }) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /test/Accessible.spec.js: -------------------------------------------------------------------------------- 1 | const expectRevert = require('../test/helpers/expectRevert') 2 | const expectThrow = require('../test/helpers/expectThrow') 3 | 4 | console.log('*********', Object.keys(contract)) 5 | 6 | var Accessible = artifacts.require('../contracts/Accessible.sol') 7 | 8 | contract('Accessible', function(accounts) { 9 | var instance; 10 | var numOfAccess; 11 | 12 | it('initalize accessible contract', async function() { 13 | 14 | instance = await Accessible.deployed({from: accounts[0]}); 15 | 16 | // check inital number of accounts with access 17 | numOfAccess = await instance.numberOfAccounts(); 18 | assert.equal(numOfAccess, 0, "inital number of allowed accounts is not 0"); 19 | }) 20 | 21 | it('give access to accounts for contract', async function() { 22 | 23 | // give access to account and check success 24 | await instance.grantAccess(accounts[1]) 25 | var access2 = await instance.allowedAccounts(accounts[1]); 26 | assert.equal(access2, true, "new allowed account is not accounts[1]"); 27 | 28 | // give additional account access as accounts[1] and check success 29 | await instance.grantAccess(accounts[2], {from: accounts[1]}) 30 | var access3 = await instance.allowedAccounts(accounts[2]); 31 | assert.equal(access3, true, "new allowed account is not accounts[2]"); 32 | 33 | // check final number of accounts with access 34 | numOfAccess = await instance.numberOfAccounts(); 35 | assert.equal(numOfAccess, 2, "final number of allowed accounts is not 3"); 36 | 37 | // try to add account which has already access 38 | await expectRevert(instance.grantAccess(accounts[2], {from: accounts[1]})) 39 | 40 | // try to add account with address 0 41 | await expectRevert(instance.grantAccess(0, {from: accounts[1]})) 42 | }) 43 | 44 | it('remove access from contract', async function() { 45 | 46 | // remove access right and check success 47 | await instance.removeAccess(accounts[1], {from: accounts[2]}) 48 | var access1 = await instance.allowedAccounts(accounts[0]); 49 | assert.equal(access1, false, "accounts[0] has still access"); 50 | 51 | // check final number of accounts with access 52 | numOfAccess = await instance.numberOfAccounts(); 53 | assert.equal(numOfAccess, 1, "final number of allowed accounts is not 1"); 54 | 55 | // try to remove account which is has no access 56 | await expectRevert(instance.removeAccess(accounts[4], {from: accounts[1]})) 57 | }) 58 | 59 | it('checks accessship restrictions', async function() { 60 | 61 | // check for revert transaction of grantaccessship() & removeaccessship() 62 | // when msg.sender has no access 63 | await expectRevert(instance.grantAccess(accounts[0], {from: accounts[4]})); 64 | await instance.grantAccess(accounts[3], {from: accounts[0]}); 65 | await expectRevert(instance.removeAccess(accounts[3], {from: accounts[4]})); 66 | }) 67 | 68 | }) 69 | -------------------------------------------------------------------------------- /src/app/providers/contracts/contract-loader.service.ts: -------------------------------------------------------------------------------- 1 | import contract from 'truffle-contract' 2 | import {Inject, Injectable} from '@angular/core' 3 | import {Config} from '../../../../config/Config' 4 | import * as AletheiaJson from '../../../../build/contracts/Aletheia.json' 5 | import * as ManuscriptIndexJson from '../../../../build/contracts/ManuscriptIndex.json' 6 | import * as MinimalManuscriptJson from '../../../../build/contracts/MinimalManuscript.json' 7 | import * as CommunityVotesJson from '../../../../build/contracts/CommunityVotes.json' 8 | import {Web3AccountService} from '../web3/web3-account/web3-account.service' 9 | import {Web3Provider} from '../web3/web3-provider/web3-provider.token' 10 | import {Web3NetworkIdPromise} from '../web3/web3-network-id/web3-network-id.token' 11 | 12 | export class MockContractLoaderService { 13 | public loadAletheia() {} 14 | public minimalManuscriptAt(manuscriptAddress) {} 15 | } 16 | 17 | @Injectable() 18 | export class ContractLoaderService { 19 | 20 | constructor(private config: Config, 21 | private web3Account: Web3AccountService, 22 | @Inject(Web3Provider) private web3Provider: any, 23 | @Inject(Web3NetworkIdPromise) private networkIdPromise: Promise) { 24 | } 25 | 26 | public loadAletheia(): Promise { 27 | return this.loadContract(AletheiaJson) 28 | } 29 | 30 | public loadManuscriptIndex(): Promise { 31 | return this.loadContract(ManuscriptIndexJson) 32 | } 33 | 34 | public loadCommunityVotes(): Promise { 35 | return this.loadContract(CommunityVotesJson) 36 | } 37 | 38 | public minimalManuscriptAt(manuscriptAddress): any { 39 | return this.loadContractAtAddress(MinimalManuscriptJson, manuscriptAddress) 40 | } 41 | 42 | private loadContract(ContractJson: any) { 43 | return this.networkIdPromise.then((networkId) => { 44 | const contractInstance = contract(ContractJson) 45 | const networkInfo = ( ContractJson).networks[networkId] 46 | if (!networkInfo) { 47 | const errorMessage = `Unable to find ${ContractJson.contractName} contract on blockchain with networkId: ${networkId} 48 | It's probably because 'truffle migrate' has not been run since restarting the geth node. 49 | Otherwise the build output might be out of date and you should pull the latest version 50 | ` 51 | throw new Error(errorMessage) 52 | } 53 | contractInstance.setProvider(this.web3Provider) 54 | contractInstance.defaults({ 55 | from: this.web3Account.getAccount(), 56 | gas: this.config.web3.defaultGas 57 | }) 58 | return contractInstance.at(networkInfo.address) 59 | }) 60 | } 61 | 62 | private loadContractAtAddress(ContractJson, contractAddress): Promise { 63 | const manuscript = contract(ContractJson) 64 | manuscript.setProvider(this.web3Provider) 65 | manuscript.defaults({ 66 | from: this.web3Account.getAccount(), 67 | gas: this.config.web3.defaultGas 68 | }) 69 | return manuscript.at(contractAddress) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /contracts/CommunityVotes.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./Accessible.sol"; 4 | 5 | 6 | contract CommunityVotes is Accessible { 7 | 8 | // voting votingDuration is set during contract creation 9 | // should votingDuration be changeable later on? 10 | uint public votingDuration; 11 | event error(uint indexed StatusCode); 12 | 13 | struct voter { 14 | bool voted; 15 | bool vote; 16 | } 17 | 18 | struct voting { 19 | uint startingBlock; 20 | address[] voterList; 21 | mapping(address => voter) voters; 22 | } 23 | 24 | mapping(bytes32 => voting) public votingList; 25 | 26 | function CommunityVotes(uint _votingDuration) public { 27 | votingDuration = _votingDuration; 28 | } 29 | 30 | function createVoting(bytes32 ipfsHash) public onlyAllowedAccount { 31 | require(votingList[ipfsHash].startingBlock == 0); 32 | votingList[ipfsHash].startingBlock = block.number; 33 | } 34 | 35 | function votingActive(bytes32 ipfsHash) public constant returns(uint blocksRemaining) { 36 | // check that voting has been started 37 | require(votingList[ipfsHash].startingBlock != 0); 38 | // return remaining blocks until voting is closed 39 | // note that if the remaining block number is 1 the next transaction will 40 | // be in the block which closes the voting. Consequently, voting is 41 | // not possible anymore when the remaining block number goes down to 1 42 | if (votingList[ipfsHash].startingBlock + votingDuration > block.number) { 43 | return votingList[ipfsHash].startingBlock + votingDuration - block.number; 44 | } 45 | return 0; 46 | } 47 | 48 | function vote(bytes32 ipfsHash, address _voter, bool _vote) public onlyAllowedAccount { 49 | require(votingActive(ipfsHash) > 0); 50 | // check that voter did not vote yet 51 | require(votingList[ipfsHash].voters[_voter].voted == false); 52 | votingList[ipfsHash].voterList.push(_voter); 53 | votingList[ipfsHash].voters[_voter].voted = true; 54 | votingList[ipfsHash].voters[_voter].vote = _vote; 55 | } 56 | 57 | function getVote(bytes32 ipfsHash, address _voter) public constant returns(bool, bool){ 58 | return ( 59 | votingList[ipfsHash].voters[_voter].voted, 60 | votingList[ipfsHash].voters[_voter].vote 61 | ); 62 | } 63 | 64 | 65 | function getVoting(bytes32 ipfsHash) public constant returns(uint blocksRemaining, uint numAcceptVotes, address[] voterList){ 66 | uint numAccepted = 0; 67 | // check how many votes accepted the manuscript 68 | for (uint i; i < votingList[ipfsHash].voterList.length; i++) { 69 | address _voter = votingList[ipfsHash].voterList[i]; 70 | if (votingList[ipfsHash].voters[_voter].vote == true) { 71 | numAccepted++; 72 | } 73 | } 74 | return ( 75 | votingActive(ipfsHash), 76 | numAccepted, 77 | votingList[ipfsHash].voterList 78 | ); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "callable-types": true, 7 | "class-name": true, 8 | "comment-format": [ 9 | true, 10 | "check-space" 11 | ], 12 | "curly": true, 13 | "eofline": true, 14 | "forin": true, 15 | "import-blacklist": [true, "rxjs"], 16 | "import-spacing": true, 17 | "indent": [ 18 | true, 19 | "spaces" 20 | ], 21 | "interface-over-type-literal": true, 22 | "label-position": true, 23 | "max-line-length": [ 24 | true, 25 | 140 26 | ], 27 | "member-access": false, 28 | "member-ordering": [ 29 | true, 30 | "static-before-instance", 31 | "variables-before-functions" 32 | ], 33 | "no-arg": true, 34 | "no-bitwise": true, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-construct": true, 44 | "no-debugger": true, 45 | "no-duplicate-variable": true, 46 | "no-empty": false, 47 | "no-empty-interface": true, 48 | "no-eval": true, 49 | "no-inferrable-types": [true, "ignore-params"], 50 | "no-shadowed-variable": true, 51 | "no-string-literal": false, 52 | "no-string-throw": true, 53 | "no-switch-case-fall-through": true, 54 | "no-trailing-whitespace": true, 55 | "no-unused-expression": true, 56 | "no-use-before-declare": true, 57 | "no-var-keyword": true, 58 | "object-literal-sort-keys": false, 59 | "one-line": [ 60 | true, 61 | "check-open-brace", 62 | "check-catch", 63 | "check-else", 64 | "check-whitespace" 65 | ], 66 | "prefer-const": true, 67 | "quotemark": [ 68 | true, 69 | "single" 70 | ], 71 | "radix": true, 72 | "semicolon": [ 73 | "always" 74 | ], 75 | "triple-equals": [ 76 | true, 77 | "allow-null-check" 78 | ], 79 | "typedef-whitespace": [ 80 | true, 81 | { 82 | "call-signature": "nospace", 83 | "index-signature": "nospace", 84 | "parameter": "nospace", 85 | "property-declaration": "nospace", 86 | "variable-declaration": "nospace" 87 | } 88 | ], 89 | "typeof-compare": true, 90 | "unified-signatures": true, 91 | "variable-name": false, 92 | "whitespace": [ 93 | true, 94 | "check-branch", 95 | "check-decl", 96 | "check-operator", 97 | "check-separator", 98 | "check-type" 99 | ], 100 | 101 | "directive-selector": [true, "attribute", "app", "camelCase"], 102 | "component-selector": [true, "element", "app", "kebab-case"], 103 | "use-input-property-decorator": true, 104 | "use-output-property-decorator": true, 105 | "use-host-property-decorator": true, 106 | "no-input-rename": true, 107 | "no-output-rename": true, 108 | "use-life-cycle-interface": true, 109 | "use-pipe-transform-interface": true, 110 | "component-class-suffix": true, 111 | "directive-class-suffix": true, 112 | "no-access-missing-member": true, 113 | "templates-use-public": true, 114 | "invoke-injectable": true 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /contracts/Aletheia.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.19; 2 | 3 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "./Reputation.sol"; 5 | import "./ManuscriptIndex.sol"; 6 | import "./CommunityVotes.sol"; 7 | import "./Manuscript.sol"; 8 | import "./MinimalManuscript.sol"; 9 | import "./ManuscriptFactory.sol"; 10 | 11 | 12 | contract Aletheia is Ownable, ManuscriptFactory { 13 | 14 | Reputation public reputation; 15 | ManuscriptIndex public manuscriptIndex; 16 | CommunityVotes public communityVotes; 17 | mapping(address => uint256) public balanceOf; 18 | 19 | function Aletheia( 20 | address reputationAddress, 21 | address manuscriptIndexAddress, 22 | address votesAddress, 23 | address libraryManuscriptMaster) 24 | ManuscriptFactory(libraryManuscriptMaster) 25 | public 26 | { 27 | reputation = Reputation(reputationAddress); 28 | manuscriptIndex = ManuscriptIndex(manuscriptIndexAddress); 29 | communityVotes = CommunityVotes(votesAddress); 30 | } 31 | 32 | function remove() public onlyOwner payable { 33 | selfdestruct(msg.sender); 34 | } 35 | 36 | // deploy new manuscript (contract factory) 37 | function newManuscript(bytes32 _hash, string title, address[] authors) public 38 | returns(address _newManuscriptAddress) 39 | { 40 | require(manuscriptIndex.manuscriptAddress(_hash) == 0x00); 41 | 42 | address mAddress = createClone(libraryAddress); 43 | MinimalManuscript m = MinimalManuscript(mAddress); 44 | m.init(_hash, title); 45 | 46 | for (uint i = 0; i < authors.length; i++) { 47 | m.addAuthor(authors[i]); 48 | } 49 | m.transferOwnership(msg.sender); 50 | emit ManuscriptCreated(mAddress, libraryAddress); 51 | communityVotes.createVoting(_hash); 52 | manuscriptIndex.add(_hash, mAddress); 53 | 54 | // ToDo: If authors are already set with newManuscript(), and 55 | // voting is started automatically, then also citations should be set. 56 | // Moreover, the manuscript should become immutable in the sense that 57 | // hash, title, author & citations are fixed and cannot be changed anymore 58 | // => corresponding functions for addind authors and citations can be 59 | // removed. 60 | 61 | // ToDo: Update of citation count should be performed 62 | // after review of paper 63 | // ToDo: How to avoid to receive rep from one paper 64 | // several times? 65 | /*for (uint paperIdx = 0; paperIdx < paper.citationCount(); paperIdx++) { 66 | balanceOf[paper.citation(paperIdx)] += 10; 67 | } */ 68 | return mAddress; 69 | } 70 | 71 | function communityVote(bytes32 _hash, bool _vote) public { 72 | // add check if msg.sender is registered member of Aletheia 73 | // member registration still to come... 74 | // check that authors cannot vote for their own manuscript 75 | Manuscript paper = Manuscript(manuscriptIndex.manuscriptAddress(_hash)); 76 | require(!paper.isOwner(msg.sender)); 77 | for (uint i = 0; i < paper.authorCount(); i++) { 78 | require(paper.author(i) != msg.sender); 79 | } 80 | communityVotes.vote(_hash, msg.sender, _vote); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/app/components/list-papers/list-papers.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing' 2 | 3 | import { ListPapersComponent } from './list-papers.component' 4 | import {MockWeb3ClientService, Web3ClientService} from '../../providers/web3/web3-client/web3-client.service' 5 | import {ErrorHandlerService, MockErrorHandlerService} from '../../providers/error-handler/error-handler.service' 6 | import {IpfsClientService, MockIpfsClientService} from '../../providers/ipfs/ipfs-client/ipfs-client.service' 7 | import {ElectronService, MockElectronService} from '../../providers/electron.service' 8 | import {MockWeb3MonitorService, Web3MonitorService} from '../../providers/web3/web3-monitor/web3-monitor.service' 9 | import {NotificationsService} from 'angular2-notifications' 10 | 11 | describe('ListPapersComponent', () => { 12 | let component: ListPapersComponent 13 | let compiled: any 14 | let fixture: ComponentFixture 15 | let mockWeb3Client: MockWeb3ClientService = new MockWeb3ClientService() 16 | let mockErrorHandler: MockErrorHandlerService = new MockErrorHandlerService() 17 | let mockIpfsClientService = new MockIpfsClientService() 18 | let mockElectronService = new MockElectronService() 19 | beforeEach(async(() => { 20 | TestBed.configureTestingModule({ 21 | declarations: [ ListPapersComponent ], 22 | providers: [ 23 | {provide: ElectronService, useValue: mockElectronService}, 24 | {provide: IpfsClientService, useValue: mockIpfsClientService}, 25 | {provide: Web3ClientService, useValue: mockWeb3Client}, 26 | {provide: ErrorHandlerService, useValue: mockErrorHandler}, 27 | {provide: Web3MonitorService, useValue: new MockWeb3MonitorService()}, 28 | {provide: NotificationsService, useValue: new NotificationsService({})} 29 | ] 30 | }) 31 | .compileComponents() 32 | 33 | fixture = TestBed.createComponent(ListPapersComponent) 34 | component = fixture.componentInstance 35 | compiled = fixture.debugElement.nativeElement 36 | })) 37 | 38 | describe('if no manuscripts are found', () => { 39 | beforeEach(fakeAsync(() => { 40 | spyOn(mockWeb3Client, 'getAllManuscripts').and.returnValue(Promise.reject(new Error("test_error"))) 41 | spyOn(mockErrorHandler, 'handleError') 42 | component.ngOnInit() 43 | tick() 44 | fixture.detectChanges() 45 | })) 46 | it('should show an empty state and error', () => { 47 | expect(compiled.querySelector('.no-manuscripts-message')).not.toBe(null) 48 | expect(mockErrorHandler.handleError).toHaveBeenCalled() 49 | }) 50 | }) 51 | 52 | describe('if manuscripts are found', () => { 53 | beforeEach(fakeAsync(() => { 54 | spyOn(mockWeb3Client, 'getAllManuscripts').and.returnValue(Promise.resolve([ 55 | { 56 | dataAddress: '0x12345678', 57 | contractAddress: '0x1234567891011', 58 | title: 'testing methods for angular apps' 59 | } 60 | ])) 61 | spyOn(mockErrorHandler, 'handleError') 62 | component.ngOnInit() 63 | tick() 64 | fixture.detectChanges() 65 | })) 66 | it('should show the papers', () => { 67 | expect(compiled.querySelector('.manuscript-item').innerText).toContain('testing methods for angular apps') 68 | expect(compiled.querySelector('.no-manuscripts-message')).toBe(null) 69 | expect(mockErrorHandler.handleError).not.toHaveBeenCalled() 70 | }) 71 | }) 72 | }) 73 | -------------------------------------------------------------------------------- /src/app/components/list-papers/list-papers.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {Web3ClientService} from '../../providers/web3/web3-client/web3-client.service' 3 | import {ErrorHandlerService} from '../../providers/error-handler/error-handler.service' 4 | import {ManuscriptViewModel} from './manuscript-view-model' 5 | import {IpfsClientService} from '../../providers/ipfs/ipfs-client/ipfs-client.service' 6 | import {ElectronService} from '../../providers/electron.service' 7 | import {FileHelper} from '../../providers/file-helper/file-helper' 8 | import {NotificationsService} from 'angular2-notifications' 9 | import {Web3MonitorService} from '../../providers/web3/web3-monitor/web3-monitor.service' 10 | import {ManuscriptVotingStatus} from './manuscript-voting-status' 11 | @Component({ 12 | selector: 'app-list-papers', 13 | templateUrl: './list-papers.component.html', 14 | styleUrls: ['./list-papers.component.scss'] 15 | }) 16 | export class ListPapersComponent implements OnInit { 17 | manuscripts: ManuscriptViewModel[] = [] 18 | downloading = false 19 | manuscriptVotingStatus = ManuscriptVotingStatus 20 | 21 | constructor(private web3Client: Web3ClientService, 22 | private errorHandler: ErrorHandlerService, 23 | private web3Monitor: Web3MonitorService, 24 | private ipfsClient: IpfsClientService, 25 | private electronService: ElectronService, 26 | private notificationService: NotificationsService, 27 | ) { 28 | } 29 | 30 | async voteOnManuscript(manuscript, vote) { 31 | // check if voter is the one who created the manuscript 32 | if (manuscript.authors.indexOf(this.web3Monitor.networkStatus.getValue().address) != -1){ 33 | this.errorHandler.handleError(new Error('You cannot vote on your own paper')) 34 | return 35 | } 36 | 37 | // check if voter has voted already 38 | 39 | try { 40 | const result = await this.web3Client.voteOnManuscript(manuscript.dataAddress, vote) 41 | const votingActive = await this.web3Client.votingActive(manuscript.dataAddress) 42 | if(result) { 43 | console.log('vote cast', result) 44 | this.notificationService.success('Your vote was recorded', `Voting on the paper is still open for ${votingActive} more blocks`) 45 | } 46 | } catch (e) { 47 | this.errorHandler.handleError(e, 'There was an error casting your vote, currently your vote cannot be changed one it has been cast') 48 | } 49 | } 50 | 51 | async downloadManuscript(_hash, title) { 52 | this.downloading = true 53 | try { 54 | const stream = await this.ipfsClient.getStream(_hash) 55 | 56 | if (!this.electronService.isElectron()) { 57 | return this.errorHandler.handleError(new Error('File open dialog is only available in electron')) 58 | } 59 | const defaultFileName = FileHelper.toAbsoluteDownloadFilePath(title, '.pdf') 60 | 61 | const filePath = this.electronService.dialog.showSaveDialog({ 62 | defaultPath: defaultFileName 63 | }) 64 | if (filePath) { 65 | FileHelper.writeFileStream(stream, filePath) 66 | } 67 | } catch (e) { 68 | this.errorHandler.handleError(e, 'Error downloading paper') 69 | } 70 | } 71 | 72 | 73 | async ngOnInit() { 74 | try { 75 | this.manuscripts = await this.web3Client.getAllManuscripts() 76 | } catch (error) { 77 | this.manuscripts = [] 78 | this.errorHandler.handleError(error, 'Unable to load manuscripts') 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /test/ManuscriptFactory.spec.js: -------------------------------------------------------------------------------- 1 | const EncodingHelper = require('../test/helpers/test-encoding-helper') 2 | const expectRevert = require('../test/helpers/expectRevert') 3 | 4 | 5 | console.log('*********', Object.keys(contract)) 6 | 7 | var MinimalManuscript = artifacts.require('../contracts/MinimalManuscript.sol') 8 | var ManuscriptFactory = artifacts.require('../contracts/ManuscriptFactory.sol') 9 | 10 | contract('ManuscriptFactory', function (accounts) { 11 | var global; 12 | var factory; 13 | // manuscript variables 14 | var manuscript = []; 15 | // manuscript address variables 16 | var addressManuscript = []; 17 | var instanceMaFactory; 18 | var bytesOfAddress = [], ipfsAddress = []; 19 | ipfsAddress[0] = 'QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH'; 20 | ipfsAddress[1] = 'QmdavdTfHgbbLCp5DUkZDYGoHwgDuQ2Zf2vpMYnJ6i71n2'; 21 | ipfsAddress[2] = 'QmWk4iniHmBZwpjwSFNmP7im4uEgVBgSFiPu2pU168353k'; 22 | ipfsAddress[3] = 'QmdxbPmxWus45myDDCXMfvyBnn5mLf2QsocKeVnDgxj2QR'; 23 | ipfsAddress[4] = 'QmRPCdKARctQAoooPfaxZWjSMPuzSEvzL44k3e7PPKtVib'; 24 | ipfsAddress[5] = 'QmT8f6vZRdorpsWzyiSighyR7XzyeATqwqtQwwv38KvgHn'; 25 | for(var cnt=0; cnt tx.receipt.gasUsed); 34 | }, 35 | createManuscript: function (value1, value2, option) { 36 | return _factory.createManuscript(value1, value2, option) 37 | .then(tx => { 38 | return MinimalManuscript.at(tx.logs[0].args.newManuscriptAddress); 39 | }) 40 | } 41 | } 42 | }; 43 | 44 | before(async function () { 45 | global = await MinimalManuscript.new(); 46 | await initFactory(ManuscriptFactory); 47 | }) 48 | 49 | it('check low gas consumption', async function() { 50 | 51 | var cost = await factory.cloneCost(); 52 | console.log(" Clone cost: " + cost); 53 | assert(cost < 70000,"clone cost is not below 70000") 54 | }) 55 | 56 | it('create new cheap manuscript ', async function() { 57 | 58 | manuscript[0] = await factory.createManuscript(bytesOfAddress[0], 'title'); 59 | assert.equal(await manuscript[0]._dataAddress(), bytesOfAddress[0], "manuscript address is not correct" ); 60 | assert.equal(await global._dataAddress(), 0x1, "address of master contract is not 0x1") 61 | }) 62 | 63 | it('check ownership of new manuscripts', async function() { 64 | 65 | manuscript[1] = await factory.createManuscript(bytesOfAddress[0], 'title', {from: accounts[1]}); 66 | assert.equal(await manuscript[1].owner(), accounts[1], "owner is not correct" ); 67 | }) 68 | 69 | it('check for false input to ManuscriptFactory', async function() { 70 | 71 | await expectRevert(factory.createManuscript(0, 'title')) 72 | }) 73 | 74 | it('check revert of init master contract & other existing contract', async function() { 75 | 76 | await expectRevert(global.init(2, 'some title')) 77 | await expectRevert(manuscript[0].init(2, 'other title')) 78 | }) 79 | 80 | it('check that directly created manuscripts are dead', async function() { 81 | 82 | manuscript[2] = await MinimalManuscript.new({from: accounts[1]}); 83 | assert.equal(await manuscript[2].owner(), accounts[1], 'owner not correct'); 84 | await expectRevert(manuscript[2].init(bytesOfAddress[2], accounts[3])); 85 | }) 86 | 87 | }) 88 | -------------------------------------------------------------------------------- /src/app/components/submit-paper/submit-paper-modal/submit-paper-modal.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, ViewChild} from '@angular/core' 2 | import {ElectronService} from '../../../providers/electron.service' 3 | import {ErrorHandlerService} from '../../../providers/error-handler/error-handler.service' 4 | import {Web3ClientService} from '../../../providers/web3/web3-client/web3-client.service' 5 | import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap' 6 | import {IpfsClientService} from '../../../providers/ipfs/ipfs-client/ipfs-client.service' 7 | import {NotificationsService} from 'angular2-notifications' 8 | 9 | @Component({ 10 | selector: 'app-submit-paper-modal', 11 | templateUrl: './submit-paper-modal.component.html', 12 | styleUrls: ['./submit-paper-modal.component.scss'] 13 | }) 14 | export class SubmitPaperModalComponent implements OnInit { 15 | fileName = '' 16 | filePath = '' 17 | title = ''; 18 | isAuthor = false 19 | uploadingPaper = false 20 | @ViewChild('paperForm') paperForm 21 | constructor( 22 | private web3Client: Web3ClientService, 23 | private activeModal: NgbActiveModal, 24 | private electronService: ElectronService, 25 | private errorHandler: ErrorHandlerService, 26 | private ipfsClient: IpfsClientService, 27 | private notificationService: NotificationsService 28 | ) { 29 | } 30 | 31 | ngOnInit() { 32 | } 33 | 34 | shareFileButtonClick() { 35 | if (!this.electronService.isElectron()) { 36 | return this.errorHandler.handleError(new Error('File open dialog is only available in electron')) 37 | } 38 | 39 | const filePath = this.electronService.dialog.showOpenDialog({properties: ['openFile']}) 40 | 41 | if (typeof filePath !== 'object') { 42 | // assume user cancelled the dialog, no action required. 43 | return 44 | } 45 | if (!filePath[0]) { 46 | return this.errorHandler.handleWarning(new Error(`No file was selected`)) 47 | } 48 | const fileName = filePath[0].match(/[^/]+$/) 49 | if (!fileName) { 50 | // user did something strange in the dialog 51 | return this.errorHandler.handleError(new Error(`Unable to upload file or folder: ${filePath}`)) 52 | } 53 | if (!/\.pdf$/.test(fileName[0])) { 54 | return this.errorHandler.handleError(new Error(`Currently only pdf files can be uploaded`)) 55 | } 56 | this.fileName = fileName[0] 57 | this.filePath = filePath[0] 58 | } 59 | 60 | onSubmitPaper() { 61 | if (this.paperForm.invalid) { 62 | return 63 | } 64 | //todo: It is quite common that this will fail because the paper has already been submitted, that case should be handled separately 65 | this.ipfsClient.addFileFromPath(this.fileName, this.filePath) 66 | .then((ipfsFileRef) => { 67 | if (typeof ipfsFileRef !== 'object' || !ipfsFileRef[0] || !ipfsFileRef[0].hash) { 68 | console.log('ipfsFileRef: ', ipfsFileRef); 69 | throw new Error('ipfsFileRef[0].hash was null') 70 | } 71 | this.uploadingPaper = true 72 | return this.web3Client.submitManuscript(ipfsFileRef[0].hash, this.title, this.isAuthor) 73 | }).then((minimalManuscriptContract) => { 74 | 75 | if (!minimalManuscriptContract.address) { 76 | console.log('minimalManuscriptContract: ', minimalManuscriptContract) 77 | throw new Error('The manuscript contract returned had no address') 78 | } 79 | this.uploadingPaper = false 80 | this.notificationService.success( 81 | 'Paper submitted successfully:', 82 | 'The paper will be voted on before being stored permanently on the Aletheia network' 83 | ) 84 | this.activeModal.close() 85 | 86 | }).catch((err) => { 87 | this.uploadingPaper = false 88 | this.errorHandler.handleError(err, `Unable to share file: ${this.fileName}`) 89 | }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## I confirm: 2 | 3 | - [ ] I've read the [contributing guidelines](https://github.com/aletheia-foundation/aletheia-admin/blob/master/CONTRIBUTING.md) and the [code of conduct](https://github.com/aletheia-foundation/aletheia-admin/blob/master/CODE-OF-CONDUCT.md) 4 | - [ ] I've checked that this issue applies to this repo 5 | - [ ] I've checked the existing issues, no one else has reported this 6 | - [ ] I've got a clear and descriptive title for the issue 7 | 8 | --------------------------- 9 | 10 | ## Report bugs - delete this section if not reporting a bug 11 | 12 | To make your bug report as useful to others as possible, please consider the below and try to answer as many of them as possible: 13 | 14 | **Explain the problem and include additional details to help maintainers reproduce the problem:** 15 | 16 | * Describe the exact steps which reproduce the problem in as many details as possible. 17 | * Describe the behaviour you observed after following the steps and point out what exactly is the problem with that behaviour. 18 | * Explain what behaviour you expected to see instead and why. 19 | * Include screenshots where possible. 20 | * If you're reporting that Alethia crashed, include a crash report with a stack trace from the operating system if possible. Include the crash report in the issue in a code block, a file attachment, or put it in a gist and provide link to that gist. 21 | * If the problem is related to performance, include a CPU profile capture and a screenshot with your report if possible. 22 | * If the problem wasn't triggered by a specific action, describe what you were doing before the problem happened and share more information using the guidelines below. 23 | 24 | ### My response: 25 | 26 | **Provide more context by answering these questions:** 27 | 28 | * Can you reproduce the problem in safe mode? 29 | * Did the problem start happening recently (e.g. after updating to a new version of Aletheia) or was this always a problem? 30 | * If the problem started happening recently, can you reproduce the problem in an older version of Aletheia? What's the most recent version in which the problem doesn't happen? 31 | * Can you reliably reproduce the issue? If not, provide details about how often the problem happens and under which conditions it normally happens. 32 | 33 | ### My reponse: 34 | 35 | **Include details about your configuration and environment:** 36 | 37 | * Which version of Aletheia are you using? 38 | * What's the name and version of the OS you're using? 39 | * Are you running Aletheia in a virtual machine? If so, which VM software are you using and which operating systems and versions are used for the host and the guest? 40 | * Are you using Aletheia with multiple monitors? If so, can you reproduce the problem when you use a single monitor? 41 | * Which keyboard layout are you using? Are you using a US layout or some other layout? 42 | 43 | ### My response: 44 | 45 | --------------------------- 46 | 47 | ## Suggest an enhancement - delete this section if not suggesting an enhancement 48 | 49 | To make your enhancement suggestion as useful to others as possible, please consider the below and try to answer as many of them as possible: 50 | 51 | * Provide a step-by-step description of the suggested enhancement in as many details as possible. 52 | * Provide specific examples to demonstrate the steps. Include copy/pasteable snippets which you use in those examples, as Markdown code blocks if possible. 53 | * Describe the current behaviour and explain which behaviour you expected to see instead and why. 54 | * Include screenshots which help you demonstrate the steps or point out the part of Aletheia which the suggestion is related to. 55 | * Explain why this enhancement would be useful to most Aletheia users. 56 | * List some other applications where this enhancement exists. 57 | * Specify which version of Aletheia you're using. 58 | * Specify the name and version of the OS you're using. 59 | 60 | ### My response: 61 | 62 | -------------------------------------------------------------------------------- /contracts/CheapManuscript.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "./MinimalManuscript.sol"; 5 | 6 | 7 | /** @title Minimal manuscript. */ 8 | contract CheapManuscript { 9 | /* bytes32 public _dataAddress; 10 | address[] public authors; 11 | address[] public citations; 12 | mapping(address => bool) public signedByAuthor; 13 | 14 | function MinimalManuscript(bytes32 _da) public { 15 | require(_da != 0x00); 16 | //owner = msg.sender; 17 | _dataAddress = _da; 18 | } */ 19 | 20 | /* function isOwner(address account) public constant returns(bool) { 21 | return owner == account; 22 | } 23 | 24 | function authorSigned(address _author) public constant returns(bool) { 25 | return signedByAuthor[_author]; 26 | } 27 | 28 | function dataAddress() public constant returns(bytes32 _da) { 29 | return _dataAddress; 30 | } 31 | 32 | function addAuthor(address newAuthor) public onlyOwner { 33 | for (uint i = 0; i < authors.length; i++) { 34 | // ToDo: should function throw if author is already registered? 35 | if (authors[i] == newAuthor) { return; } 36 | } 37 | authors.push(newAuthor); 38 | } 39 | 40 | function citePaper(address citee) public onlyOwner { 41 | // ToDo: make self citation of this paper impossible. 42 | for (uint i = 0; i < citations.length; i++) { 43 | //ToDo: should function throw if paper is already registered? 44 | if (citations[i] == citee) { return; } 45 | } 46 | 47 | citations.push(citee); 48 | // If this manuscript is already registered with Aletheia, the new 49 | // citation hasn't changed the balanceOf the cited paper. We could 50 | // 'fix' this by calling back to Aletheia, but it might be preferable 51 | // to add new citations and then reregister the manuscript with Aletheia. 52 | // If we recalculate the balanceOf each paper with every citation, 53 | // we waste a bunch of gas. Fewer transactions = better. 54 | } 55 | 56 | function removeCitation(address citee) public onlyOwner { 57 | uint i = findItem(citations, citee); 58 | removeItemByIndex(citations, i); 59 | } 60 | 61 | function removeAuthor(address author) public onlyOwner { 62 | uint i = findItem(authors, author); 63 | removeItemByIndex(authors, i); 64 | } 65 | 66 | function signAuthorship() public { 67 | findItem(authors, msg.sender); 68 | signedByAuthor[msg.sender] = true; 69 | } 70 | 71 | function citationCount() public constant returns (uint _citationCount) { 72 | return citations.length; 73 | } 74 | 75 | function authorCount() public constant returns (uint _authorCount) { 76 | return authors.length; 77 | } 78 | 79 | function author(uint authorIdx) public constant returns (address authorList) { 80 | return authors[authorIdx]; 81 | } 82 | 83 | function citation(uint paperIdx) public constant returns (address citationList) { 84 | return citations[paperIdx]; 85 | } 86 | 87 | function removeItemByIndex(address[] storage someList, uint i) internal { 88 | require(i < someList.length); 89 | while (i < someList.length-1) { 90 | someList[i] = someList[i+1]; 91 | i++; 92 | } 93 | someList.length--; 94 | } 95 | 96 | function findItem(address[] someList, address item) internal pure 97 | returns(uint itemIndex) 98 | { 99 | // returns index of item in someList, if item is not in someList the 100 | // transaction is reverted 101 | for (uint i = 0; i < someList.length; i++) { 102 | if (someList[i] == item) { return i;} 103 | } 104 | revert(); 105 | } */ 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/app/components/network-status/network-status.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NetworkStatusComponent } from './network-status.component'; 4 | import {MockWeb3MonitorService, Web3MonitorService} from '../../providers/web3/web3-monitor/web3-monitor.service' 5 | import {Web3NetworkStatus} from '../../providers/web3/web3-monitor/web3-network-status' 6 | 7 | describe('NetworkStatusComponent', () => { 8 | let component: NetworkStatusComponent 9 | let fixture: ComponentFixture 10 | let compiled: any 11 | const mockWeb3Monitor = new MockWeb3MonitorService() 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | providers: [ 16 | {provide: Web3MonitorService, useValue: mockWeb3Monitor} 17 | ], 18 | declarations: [ NetworkStatusComponent ] 19 | }) 20 | .compileComponents(); 21 | })); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(NetworkStatusComponent); 25 | component = fixture.componentInstance; 26 | compiled = fixture.debugElement.nativeElement; 27 | fixture.detectChanges(); 28 | }); 29 | 30 | it('should create', () => { 31 | expect(component).toBeTruthy(); 32 | }); 33 | 34 | describe('when 10 peers are connected', () => { 35 | beforeEach(() => { 36 | mockWeb3Monitor.networkStatus.next(new Web3NetworkStatus(null, 10, '0x12345', 200)) 37 | fixture.detectChanges() 38 | }) 39 | it('should show the network status as connected', () => { 40 | expect(compiled.querySelector('.connected-status')).not.toBe(null) 41 | expect(compiled.querySelector('.peers-connected').innerText).toContain('10') 42 | expect(compiled.querySelector('.balance').innerText).toContain('200') 43 | }) 44 | describe('when the balance is changed', () => { 45 | beforeEach(() => { 46 | mockWeb3Monitor.networkStatus.next(new Web3NetworkStatus(null, 10, '0x12345', 500)) 47 | fixture.detectChanges() 48 | }) 49 | it('should show the new balance', () =>{ 50 | expect(compiled.querySelector('.balance').innerText).toContain('500') 51 | }) 52 | }) 53 | }) 54 | 55 | describe('when 0 peers are connected', () => { 56 | beforeEach(() => { 57 | mockWeb3Monitor.networkStatus.next(new Web3NetworkStatus(null, 0, '0x12345', 300)) 58 | fixture.detectChanges() 59 | }) 60 | it('should show the network status as warning', () => { 61 | expect(compiled.querySelector('.connected-status')).toBe(null) 62 | expect(compiled.querySelector('.warning-status')).not.toBe(null) 63 | expect(compiled.querySelector('.peers-connected').innerText).toContain('0') 64 | expect(compiled.querySelector('.balance').innerText).toContain('300') 65 | }) 66 | }) 67 | 68 | describe('when the local node cannot be reached', () => { 69 | beforeEach(() => { 70 | mockWeb3Monitor.networkStatus.next(new Web3NetworkStatus(new Error('Unable to find blockchain address'), 0, '', 0)) 71 | fixture.detectChanges() 72 | }) 73 | it('should show the network status as error', () => { 74 | expect(compiled.querySelector('.error-status')).not.toBe(null) 75 | expect(compiled.querySelector('.peers-connected')).toBe(null) 76 | expect(compiled.querySelector('.profile-link')).toBe(null) 77 | expect(compiled.querySelector('.balance')).toBe(null) 78 | }) 79 | describe('when the node is restarted', () => { 80 | beforeEach(()=>{ 81 | mockWeb3Monitor.networkStatus.next(new Web3NetworkStatus(null, 10, '0x12345', 200)) 82 | fixture.detectChanges() 83 | }) 84 | it('should show the network status as connected', () =>{ 85 | expect(compiled.querySelector('.connected-status')).not.toBe(null) 86 | expect(compiled.querySelector('.peers-connected').innerText).toContain('10') 87 | expect(compiled.querySelector('.balance').innerText).toContain('200') 88 | }) 89 | }) 90 | }) 91 | 92 | }); 93 | -------------------------------------------------------------------------------- /contracts/MinimalManuscript.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "./Manuscript.sol"; 5 | 6 | 7 | /** @title Minimal manuscript. */ 8 | contract MinimalManuscript is Ownable, Manuscript { 9 | bytes32 public _dataAddress; 10 | address[] public _authors; 11 | address[] public citations; 12 | string public title; 13 | 14 | mapping(address => bool) public signedByAuthor; 15 | 16 | function MinimalManuscript() public { 17 | // master contract and all directly created manuscripts have 18 | // _dataAddress = 0x1 -> no init possible 19 | _dataAddress = 0x1; 20 | title = "master"; 21 | } 22 | 23 | function init(bytes32 _da, string _title) external { 24 | require(_dataAddress == 0x00); // ensure not init'd already. 25 | require(_da != 0x00); 26 | require(bytes(_title).length > 0); 27 | 28 | _dataAddress = _da; 29 | owner = msg.sender; 30 | title = _title; 31 | } 32 | 33 | function isOwner(address account) external constant returns(bool) { 34 | return owner == account; 35 | } 36 | 37 | function authorSigned(address _author) external constant returns(bool) { 38 | return signedByAuthor[_author]; 39 | } 40 | 41 | function dataAddress() external constant returns(bytes32 _da) { 42 | return _dataAddress; 43 | } 44 | 45 | function title() external constant returns(string _t) { 46 | return title; 47 | } 48 | 49 | function authors() public constant returns(address[] _authos) { 50 | return _authors; 51 | } 52 | 53 | function addAuthor(address newAuthor) external onlyOwner { 54 | for (uint i = 0; i < _authors.length; i++) { 55 | // ToDo: should function throw if author is already registered? 56 | if (_authors[i] == newAuthor) { return; } 57 | } 58 | _authors.push(newAuthor); 59 | } 60 | 61 | function citePaper(address citee) external onlyOwner { 62 | // ToDo: make self citation of this paper impossible. 63 | for (uint i = 0; i < citations.length; i++) { 64 | //ToDo: should function throw if paper is already registered? 65 | if (citations[i] == citee) { return; } 66 | } 67 | 68 | citations.push(citee); 69 | // If this manuscript is already registered with Aletheia, the new 70 | // citation hasn't changed the balanceOf the cited paper. We could 71 | // 'fix' this by calling back to Aletheia, but it might be preferable 72 | // to add new citations and then reregister the manuscript with Aletheia. 73 | // If we recalculate the balanceOf each paper with every citation, 74 | // we waste a bunch of gas. Fewer transactions = better. 75 | } 76 | 77 | function removeCitation(address citee) external onlyOwner { 78 | uint i = findItem(citations, citee); 79 | removeItemByIndex(citations, i); 80 | } 81 | 82 | function removeAuthor(address author) external onlyOwner { 83 | uint i = findItem(_authors, author); 84 | removeItemByIndex(_authors, i); 85 | } 86 | 87 | function signAuthorship() external { 88 | findItem(_authors, msg.sender); 89 | signedByAuthor[msg.sender] = true; 90 | } 91 | 92 | function citationCount() external constant returns (uint _citationCount) { 93 | return citations.length; 94 | } 95 | 96 | function authorCount() external constant returns (uint _authorCount) { 97 | return _authors.length; 98 | } 99 | 100 | function author(uint authorIdx) external constant returns (address authorList) { 101 | return _authors[authorIdx]; 102 | } 103 | 104 | function citation(uint paperIdx) external constant returns (address citationList) { 105 | return citations[paperIdx]; 106 | } 107 | 108 | function removeItemByIndex(address[] storage someList, uint i) internal { 109 | require(i < someList.length); 110 | while (i < someList.length-1) { 111 | someList[i] = someList[i+1]; 112 | i++; 113 | } 114 | someList.length--; 115 | } 116 | 117 | function findItem(address[] someList, address item) internal pure 118 | returns(uint itemIndex) 119 | { 120 | // returns index of item in someList, if item is not in someList the 121 | // transaction is reverted 122 | for (uint i = 0; i < someList.length; i++) { 123 | if (someList[i] == item) { return i;} 124 | } 125 | revert(); 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /scripts/mine.js: -------------------------------------------------------------------------------- 1 | // Adapted from Iuri Matias' Embark framework 2 | // https://github.com/iurimatias/embark-framework 3 | // Modified by ryepdx to mine at regular intervals. 4 | (function () { 5 | var main = function () { 6 | /* TODO: Find a way to load mining config from YML. 7 | if (!loadScript("config.js")) { 8 | console.log("== config.js not found"); 9 | } 10 | if (typeof(config) === "undefined") { 11 | config = {}; 12 | console.log("== config is undefined, proceeding with defaults"); 13 | } 14 | In the meantime, just set an empty config object. 15 | */ 16 | config = {} 17 | 18 | defaults = { 19 | interval_ms: 10000, 20 | initial_ether: 15000000000000000000, 21 | mine_pending_txns: true, 22 | mine_periodically: false, 23 | mine_normally: false, 24 | threads: 1 25 | } 26 | 27 | for (var key in defaults) { 28 | if (config[key] === undefined) { 29 | config[key] = defaults[key] 30 | } 31 | } 32 | 33 | var miner_obj = (admin.miner === undefined) ? miner : admin.miner 34 | 35 | if (config.mine_normally) { 36 | // miner_obj.start(config.threads); 37 | miner_obj.start() 38 | return 39 | } 40 | 41 | // TODO: check why it's no longer accepting this param 42 | // miner_obj.stop(config.threads); 43 | miner_obj.stop() 44 | 45 | fundAccount(config, miner_obj, function () { 46 | if (config.mine_periodically) start_periodic_mining(config, miner_obj) 47 | if (config.mine_pending_txns) start_transaction_mining(config, miner_obj) 48 | }) 49 | } 50 | 51 | var fundAccount = function (config, miner_obj, cb) { 52 | var accountFunded = function () { 53 | return (eth.getBalance(eth.coinbase) >= config.initial_ether) 54 | } 55 | 56 | if (accountFunded()) { 57 | return cb() 58 | } 59 | 60 | console.log('== Funding account') 61 | miner_obj.start() 62 | 63 | var blockWatcher = web3.eth.filter('latest').watch(function () { 64 | if (accountFunded()) { 65 | console.log('== Account funded') 66 | 67 | blockWatcher.stopWatching() 68 | // miner_obj.stop(config.threads); 69 | miner_obj.stop() 70 | cb() 71 | } 72 | }) 73 | } 74 | 75 | var pendingTransactions = function () { 76 | if (web3.eth.pendingTransactions === undefined || web3.eth.pendingTransactions === null) { 77 | return txpool.status.pending || txpool.status.queued 78 | } else if (typeof web3.eth.pendingTransactions === 'function') { 79 | return web3.eth.pendingTransactions().length > 0 80 | } else { 81 | return web3.eth.pendingTransactions.length > 0 || web3.eth.getBlock('pending').transactions.length > 0 82 | } 83 | } 84 | 85 | var start_periodic_mining = function (config, miner_obj) { 86 | var last_mined_ms = Date.now() 87 | var timeout_set = false 88 | 89 | // miner_obj.start(config.threads); 90 | miner_obj.start() 91 | web3.eth.filter('latest').watch(function () { 92 | if ((config.mine_pending_txns && pendingTransactions()) || timeout_set) { 93 | return 94 | } 95 | 96 | timeout_set = true 97 | 98 | var now = Date.now() 99 | var ms_since_block = now - last_mined_ms 100 | last_mined_ms = now 101 | 102 | var next_block_in_ms 103 | 104 | if (ms_since_block > config.interval_ms) { 105 | next_block_in_ms = 0 106 | } else { 107 | next_block_in_ms = (config.interval_ms - ms_since_block) 108 | } 109 | 110 | // miner_obj.stop(config.threads); 111 | miner_obj.stop() 112 | console.log('== Looking for next block in ' + next_block_in_ms + 'ms') 113 | 114 | setTimeout(function () { 115 | console.log('== Looking for next block') 116 | timeout_set = false 117 | // miner_obj.start(config.threads); 118 | miner_obj.start() 119 | }, next_block_in_ms) 120 | }) 121 | } 122 | 123 | var start_transaction_mining = function (config, miner_obj) { 124 | web3.eth.filter('pending').watch(function () { 125 | if (miner_obj.hashrate > 0) return 126 | 127 | console.log('== Pending transactions! Looking for next block...') 128 | // miner_obj.start(config.threads); 129 | miner_obj.start() 130 | }) 131 | 132 | if (config.mine_periodically) return 133 | 134 | web3.eth.filter('latest').watch(function () { 135 | if (!pendingTransactions()) { 136 | console.log('== No transactions left. Stopping miner...') 137 | // miner_obj.stop(config.threads); 138 | miner_obj.stop() 139 | } 140 | }) 141 | } 142 | 143 | main() 144 | })() 145 | -------------------------------------------------------------------------------- /test/Aletheia.spec.js: -------------------------------------------------------------------------------- 1 | const expectRevert = require('../test/helpers/expectRevert') 2 | 3 | console.log('*********', Object.keys(contract)) 4 | 5 | var Aletheia = artifacts.require('../contracts/Aletheia.sol') 6 | var MinimalManuscript = artifacts.require('../contracts/MinimalManuscript.sol') 7 | var Reputation = artifacts.require('../contracts/Reputation.sol') 8 | var ManuscriptIndex = artifacts.require('../contracts/ManuscriptIndex.sol') 9 | var CommunityVotes = artifacts.require('../contracts/CommunityVotes.sol') 10 | 11 | contract('Aletheia', function(accounts) { 12 | var instance, instanceRep, instanceVotes, instanceManscptInd; 13 | var addressManuscript1; 14 | var manuscript1; 15 | var bytesOfAddress = '0xbfccda787baba32b59c78450ac3d20b633360b43992c77289f9ed46d843561e6' 16 | 17 | before(async function() { 18 | instanceRep = await Reputation.deployed(); 19 | instanceManscptInd = await ManuscriptIndex.deployed(); 20 | instanceVotes = await CommunityVotes.deployed(); 21 | instance = await Aletheia.deployed(); 22 | }) 23 | 24 | it('transfer ownership of reputation contract to Aletheia', async function() { 25 | // check new owner of reputation 26 | var ownerRep = await instanceRep.allowedAccounts(instance.address); 27 | assert.equal(ownerRep, true, "new owner is not Aletheia") 28 | 29 | // check new owner of manuscriptIndex 30 | var ownerManscptInd = await instanceManscptInd.allowedAccounts(instance.address); 31 | assert.equal(ownerManscptInd, true, "new owner is not Aletheia") 32 | 33 | // check allowance for communtiyvotes of Aletheia contract 34 | var ownerVote = await instanceVotes.allowedAccounts(instance.address); 35 | assert.equal(ownerVote, true, "new owner is not Aletheia") 36 | 37 | }) 38 | 39 | describe('create new manuscripts', function() { 40 | before(async function() { 41 | // create new manuscript 1 42 | let tx = await instance.newManuscript(bytesOfAddress, 'the title', [accounts[0]], {from: accounts[0]}); 43 | console.log(' gas cost: ', tx.receipt.gasUsed) 44 | // get address of new contact by IPFS link 45 | addressManuscript1 = await instanceManscptInd.manuscriptAddress(bytesOfAddress); 46 | manuscript1 = await MinimalManuscript.at(addressManuscript1); 47 | }) 48 | 49 | it('should transfer ownership of new manuscript to the creator', async function() { 50 | // check for transfer of ownership for new manuscript 51 | let ownerManuscript1 = await manuscript1.isOwner(accounts[0]); 52 | assert.equal(ownerManuscript1, true, "new owner is not author 1"); 53 | }) 54 | 55 | it('checks ownership restrictions', async function() { 56 | 57 | // check for revert transaction of remove() when msg.sender is not owner 58 | await expectRevert(instance.remove({from: accounts[1]})); 59 | }) 60 | 61 | describe('add author to manuscript', async function() { 62 | before(async function() { 63 | await manuscript1.addAuthor(accounts[1], {from: accounts[0]}) 64 | }) 65 | 66 | it('vote for and against manuscript', async function() { 67 | 68 | // try to vote for manuscript as manuscript owner 69 | await expectRevert(instance.communityVote(bytesOfAddress, true, {from: accounts[0]})); 70 | 71 | // try to vote for manuscript as manuscript author 72 | await expectRevert(instance.communityVote(bytesOfAddress, true, {from: accounts[1]})); 73 | 74 | // vote for manuscript 75 | var nClosed = 0; 76 | for(var cnt=0; cnt<7; cnt++) { 77 | // vote 4 times for manuscript then against it 78 | var nForVote = 4; 79 | if (cnt < nForVote) {var vote = true} else {var vote = false} 80 | // check that voting is still open 81 | 82 | if (await instanceVotes.votingActive(bytesOfAddress) > 0 ) { 83 | // vote 84 | await instance.communityVote(bytesOfAddress, vote, {from: accounts[cnt+2]}); 85 | } 86 | else { 87 | // save cnt when voting was closed 88 | if (nClosed == 0) {nClosed = cnt}; 89 | // try to vote 90 | await expectRevert(instance.communityVote(bytesOfAddress, vote, {from: accounts[cnt+2]})); 91 | } 92 | } 93 | 94 | // check that voting is closed 95 | assert.equal(await instanceVotes.votingActive(bytesOfAddress), 0 , 96 | "voting is not closed"); 97 | 98 | // check that manuscript was accepted 99 | var nVoters = await instanceVotes.getVoting(bytesOfAddress); 100 | assert(nVoters[1] > nVoters[2].length-nVoters[1], "manuscript was not accepted"); 101 | }) 102 | }) 103 | }) 104 | }) 105 | -------------------------------------------------------------------------------- /src/app/components/submit-paper/submit-paper-modal/submit-paper-modal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing' 2 | 3 | import { SubmitPaperModalComponent } from './submit-paper-modal.component' 4 | import {ElectronService, MockElectronService} from '../../../providers/electron.service' 5 | import {NotificationsService} from 'angular2-notifications' 6 | import {ErrorHandlerService, MockErrorHandlerService} from '../../../providers/error-handler/error-handler.service' 7 | import {IpfsClientService, MockIpfsClientService} from '../../../providers/ipfs/ipfs-client/ipfs-client.service' 8 | import {MockWeb3ClientService, Web3ClientService} from '../../../providers/web3/web3-client/web3-client.service' 9 | import {FormsModule, ReactiveFormsModule} from '@angular/forms' 10 | import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap' 11 | 12 | class MockNotificationService { 13 | success () {} 14 | error () {} 15 | } 16 | 17 | describe('SubmitPaperModalComponent', () => { 18 | let component: SubmitPaperModalComponent 19 | let fixture: ComponentFixture 20 | let compiled: any 21 | const mockWeb3Client = new MockWeb3ClientService() 22 | const mockElectronService = new MockElectronService() 23 | const mockIpfsClientService = new MockIpfsClientService() 24 | const mockNotificationService = new MockNotificationService() 25 | const mockErrorHandlerService = new MockErrorHandlerService() 26 | beforeEach(async(() => { 27 | TestBed.configureTestingModule({ 28 | declarations: [ SubmitPaperModalComponent ], 29 | providers: [ 30 | NgbActiveModal, 31 | {provide: ElectronService, useValue: mockElectronService}, 32 | {provide: NotificationsService, useValue: mockNotificationService}, 33 | {provide: ErrorHandlerService, useValue: mockErrorHandlerService}, 34 | {provide: IpfsClientService, useValue: mockIpfsClientService}, 35 | {provide: Web3ClientService, useValue: mockWeb3Client}, 36 | ], 37 | imports: [ 38 | ReactiveFormsModule, 39 | FormsModule 40 | ] 41 | }) 42 | .compileComponents() 43 | })) 44 | 45 | beforeEach(() => { 46 | fixture = TestBed.createComponent(SubmitPaperModalComponent) 47 | component = fixture.componentInstance 48 | compiled = fixture.debugElement.nativeElement 49 | 50 | fixture.detectChanges() 51 | }) 52 | 53 | 54 | describe('choosing a directory', () => { 55 | beforeEach(() => { 56 | spyOn(mockElectronService.dialog, 'showOpenDialog').and.returnValue(['/']) 57 | spyOn(mockErrorHandlerService, 'handleError') 58 | 59 | compiled.querySelector('.select-file-button').click() 60 | fixture.detectChanges() 61 | }) 62 | it('should show an error', () => { 63 | expect(mockErrorHandlerService.handleError).toHaveBeenCalled() 64 | }) 65 | }) 66 | 67 | describe('Choosing a file', () => { 68 | beforeEach(() => { 69 | spyOn(mockElectronService.dialog, 'showOpenDialog').and.returnValue(['/home/kevin/Documents/manuscript.pdf']) 70 | compiled.querySelector('.select-file-button').click() 71 | 72 | fixture.detectChanges() 73 | }) 74 | it('should display the filename in the ui', () => { 75 | expect(compiled.querySelector('.upload-paper-filename').innerText).toContain('manuscript.pdf') 76 | }) 77 | }) 78 | 79 | describe('Submit a manuscript: Success', () => { 80 | // fakeAsync is needed so that promises can be resolved before calling the `it` block 81 | // https://angular-2-training-book.rangle.io/handout/testing/components/async.html 82 | beforeEach(fakeAsync(() => { 83 | spyOn(mockIpfsClientService, 'addFileFromPath').and.returnValue(Promise.resolve( 84 | { 85 | 0: { 86 | hash: 'QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco' 87 | } 88 | } 89 | )) 90 | spyOn(mockWeb3Client, 'submitManuscript').and.returnValue(Promise.resolve({ 91 | address: '0x90696942e1da6cf23d9c25bfe6d5d65237468fbbbb9beb0c1cf8940358ab031c' 92 | })) 93 | spyOn(mockNotificationService, 'success') 94 | spyOn(mockErrorHandlerService, 'handleError') 95 | component.paperForm = { 96 | invalid: false 97 | } 98 | component.onSubmitPaper() 99 | // tick allows promises returned by spys to resolve before running the `it` block 100 | tick() 101 | fixture.detectChanges() 102 | })) 103 | 104 | it('should upload the file successfully', () => { 105 | expect(mockIpfsClientService.addFileFromPath).toHaveBeenCalled() 106 | expect(mockWeb3Client.submitManuscript).toHaveBeenCalled() 107 | expect(mockNotificationService.success).toHaveBeenCalled() 108 | expect(mockErrorHandlerService.handleError).not.toHaveBeenCalled() 109 | }) 110 | }) 111 | }) 112 | -------------------------------------------------------------------------------- /src/app/providers/web3/web3-client/web3-client.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, Inject} from '@angular/core' 2 | 3 | import {EncodingHelper} from '../../encoding-helper/encoding-helper' 4 | import {Web3HelperService} from '../web3-helper/web3-helper.service' 5 | import {AletheiaPromise} from '../../contracts/aletheia-promise.token' 6 | import {Web3AccountService} from '../web3-account/web3-account.service' 7 | import {ContractLoaderService} from '../../contracts/contract-loader.service' 8 | import {Web3Token} from '../web3/web3.token' 9 | import {ManuscriptViewModel} from '../../../components/list-papers/manuscript-view-model' 10 | import {ManuscriptVotingStatus} from '../../../components/list-papers/manuscript-voting-status' 11 | 12 | export class MockWeb3ClientService { 13 | submitManuscript() { 14 | } 15 | 16 | awaitTransaction() { 17 | } 18 | 19 | getAllManuscripts() { 20 | } 21 | } 22 | 23 | @Injectable() 24 | export class Web3ClientService { 25 | readonly NULL_BYTE = '0x0000000000000000000000000000000000000000000000000000000000000000' 26 | manuscriptIndex: any 27 | aletheia: any 28 | communityVotes: any 29 | 30 | constructor( 31 | @Inject(Web3HelperService) private web3Helper: Web3HelperService, 32 | @Inject(Web3AccountService) private web3Account: Web3AccountService, 33 | @Inject(Web3Token) private web3: any, 34 | private contractLoader: ContractLoaderService) { 35 | } 36 | 37 | load() { 38 | (window).web3 = this.web3 // hack for easier testing 39 | return Promise.all([ 40 | this.contractLoader.loadAletheia(), 41 | this.contractLoader.loadManuscriptIndex(), 42 | this.contractLoader.loadCommunityVotes() 43 | ] 44 | ).then((results) => { 45 | this.aletheia = results[0] 46 | this.manuscriptIndex = results[1] 47 | this.communityVotes = results[2] 48 | }) 49 | } 50 | 51 | submitManuscript(fileHash: string, title: string, isAuthor: boolean) { 52 | const fileHashBytes = EncodingHelper.ipfsAddressToHexSha256(fileHash) 53 | const from = this.web3Account.getAccount() 54 | // todo: ensure that we have created an account. 55 | const authors = isAuthor ? [from] : [] 56 | return this.aletheia.newManuscript(fileHashBytes, title, authors) 57 | .then((manuscriptReceipt) => { 58 | // This is available because the smartcontract defines address as a return value 59 | const newManuscriptAddress = manuscriptReceipt.logs[0].address 60 | return this.contractLoader.minimalManuscriptAt(newManuscriptAddress) 61 | }) 62 | } 63 | 64 | awaitTransaction(txnHash): Promise { 65 | return this.web3Helper.getTransactionReceiptMined(this.web3, txnHash) 66 | .then((result: any) => { 67 | return result.blockHash 68 | }) 69 | } 70 | 71 | async getAllManuscripts(): Promise { 72 | const allManuscripts = [] 73 | let manuscriptHash = await this.manuscriptIndex.dllIndex(0, true) 74 | while (this.NULL_BYTE !== manuscriptHash) { 75 | const votingResult = await this.communityVotes.getVoting(manuscriptHash) 76 | const address = await this.manuscriptIndex.manuscriptAddress(manuscriptHash) 77 | const manuscriptContract = this.contractLoader.minimalManuscriptAt(address) 78 | const manuscriptViewModel = await this.getManuscriptViewModel(manuscriptContract, votingResult) 79 | allManuscripts.push(manuscriptViewModel) 80 | manuscriptHash = await this.manuscriptIndex.dllIndex(manuscriptHash, true) 81 | } 82 | 83 | return allManuscripts; 84 | } 85 | 86 | async votingActive (_hexDataHash: string) { 87 | return this.communityVotes.votingActive(_hexDataHash) 88 | } 89 | 90 | async voteOnManuscript (_hexDataHash: string, vote: boolean) { 91 | return this.aletheia.communityVote(_hexDataHash, vote, {from: this.web3Account.getAccount()}); 92 | } 93 | 94 | getManuscriptVotingStatus (votingResult) : ManuscriptVotingStatus { 95 | const blocksRemaining = votingResult[0] 96 | const numAcceptVotes = votingResult[1] 97 | const voterList = votingResult[2] 98 | const numRejectVotes = voterList.length - numAcceptVotes 99 | 100 | if(blocksRemaining > 1) { 101 | return ManuscriptVotingStatus.Open 102 | } 103 | else if (numAcceptVotes > numRejectVotes) { 104 | return ManuscriptVotingStatus.Accepted 105 | } else { 106 | return ManuscriptVotingStatus.Rejected 107 | } 108 | } 109 | 110 | async getManuscriptViewModel(manuscriptContract, votingResult: ManuscriptVotingStatus): Promise { 111 | const votingStatus = this.getManuscriptVotingStatus(votingResult) 112 | 113 | return { 114 | authors: await manuscriptContract.authors(), 115 | dataAddress: await manuscriptContract.dataAddress(), 116 | contractAddress: manuscriptContract.address, 117 | votingStatus: votingStatus, 118 | blocksRemaining: Number(votingResult[0]), 119 | title: await manuscriptContract.title() 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import 'zone.js/dist/zone-mix' 2 | import 'reflect-metadata' 3 | import 'polyfills' 4 | import {BrowserModule} from '@angular/platform-browser' 5 | import {APP_INITIALIZER, NgModule} from '@angular/core' 6 | import {FormsModule} from '@angular/forms' 7 | import {NgbModule} from '@ng-bootstrap/ng-bootstrap' 8 | 9 | import {AppComponent} from './app.component' 10 | import {HomeComponent} from './components/home/home.component' 11 | import {BecomeAReviewerComponent} from './components/become-a-reviewer/become-a-reviewer.component' 12 | import {HowItWorksComponent} from './components/how-it-works/how-it-works.component' 13 | import {SubmitPaperComponent} from './components/submit-paper/submit-paper.component' 14 | import {InsufficientBalanceModalComponent} from './components/submit-paper/insufficient-balance-modal/insufficient-balance-modal.component' 15 | 16 | import {AppRoutingModule} from './app-routing.module' 17 | import {ElectronService} from './providers/electron.service' 18 | 19 | import {Web3ClientService} from './providers/web3/web3-client/web3-client.service' 20 | import {Web3HelperService} from './providers/web3/web3-helper/web3-helper.service' 21 | import {Web3Provider} from './providers/web3/web3-provider/web3-provider.token' 22 | import {web3ProviderFactory} from './providers/web3/web3-provider/web3-provider.factory' 23 | 24 | import {ContractLoaderService} from './providers/contracts/contract-loader.service' 25 | import {POLL_INTERVAL_MS, WEB3_URL} from './Injection-tokens' 26 | import {Web3Token} from './providers/web3/web3/web3.token' 27 | import {web3Factory} from './providers/web3/web3/web3.factory' 28 | import {NetworkStatusComponent} from './components/network-status/network-status.component' 29 | import {Web3MonitorService} from './providers/web3/web3-monitor/web3-monitor.service' 30 | import {SimpleNotificationsModule} from 'angular2-notifications' 31 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations' 32 | import {ErrorHandlerService} from './providers/error-handler/error-handler.service' 33 | import {IpfsClientService} from './providers/ipfs/ipfs-client/ipfs-client.service' 34 | import {Config} from '../../config/Config' 35 | import {configFactory} from './providers/config/config.factory' 36 | import {ListPapersComponent} from './components/list-papers/list-papers.component' 37 | import {Web3NetworkIdPromise} from './providers/web3/web3-network-id/web3-network-id.token' 38 | import {web3NetworkIdFactory} from './providers/web3/web3-network-id/web3-network-id.factory' 39 | import {loadWeb3Account, Web3AccountService} from './providers/web3/web3-account/web3-account.service' 40 | import {HttpClientModule} from '@angular/common/http'; 41 | import { SubmitPaperModalComponent } from './components/submit-paper/submit-paper-modal/submit-paper-modal.component' 42 | import {RouterModule} from '@angular/router' 43 | 44 | export function loadWeb3Client(web3Client: Web3ClientService) { 45 | return () => { 46 | return web3Client.load() 47 | } 48 | } 49 | 50 | @NgModule({ 51 | declarations: [ 52 | AppComponent, 53 | HomeComponent, 54 | HowItWorksComponent, 55 | SubmitPaperComponent, 56 | BecomeAReviewerComponent, 57 | NetworkStatusComponent, 58 | InsufficientBalanceModalComponent, 59 | ListPapersComponent, 60 | SubmitPaperModalComponent 61 | ], 62 | entryComponents: [ 63 | InsufficientBalanceModalComponent, 64 | SubmitPaperModalComponent 65 | ], 66 | imports: [ 67 | BrowserModule, 68 | FormsModule, 69 | HttpClientModule, 70 | AppRoutingModule, 71 | BrowserAnimationsModule, 72 | NgbModule.forRoot(), 73 | SimpleNotificationsModule.forRoot() 74 | ], 75 | providers: [ 76 | ElectronService, 77 | {provide: Config, useFactory: configFactory}, 78 | ErrorHandlerService, 79 | IpfsClientService, 80 | {provide: WEB3_URL, useValue: 'http://localhost:8545'}, 81 | { 82 | provide: Web3Provider, 83 | useFactory: web3ProviderFactory 84 | }, 85 | {provide: POLL_INTERVAL_MS, useValue: '5000'}, 86 | { 87 | provide: Web3Token, 88 | deps: [Web3Provider], 89 | useFactory: web3Factory 90 | }, 91 | { 92 | provide: Web3NetworkIdPromise, 93 | deps: [Web3Token], 94 | useFactory: web3NetworkIdFactory 95 | }, 96 | { 97 | provide: ContractLoaderService, 98 | useClass: ContractLoaderService 99 | }, 100 | { 101 | provide: Web3HelperService, useClass: Web3HelperService 102 | }, 103 | { 104 | provide: Web3ClientService, 105 | useClass: Web3ClientService 106 | }, 107 | { 108 | provide: APP_INITIALIZER, 109 | useFactory: loadWeb3Client, 110 | deps: [Web3ClientService], 111 | multi: true 112 | }, 113 | { 114 | provide: Web3AccountService, 115 | useClass: Web3AccountService 116 | }, 117 | { 118 | provide: APP_INITIALIZER, 119 | deps: [Web3AccountService], 120 | useFactory: loadWeb3Account, 121 | multi: true 122 | }, 123 | { 124 | provide: Web3MonitorService, 125 | useClass: Web3MonitorService 126 | } 127 | ], 128 | bootstrap: [AppComponent] 129 | }) 130 | export class AppModule { 131 | } 132 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aletheia-app", 3 | "version": "1.0.0", 4 | "description": "Decentralised science", 5 | "main": "main.js", 6 | "scripts": { 7 | "ethereum-local": "ganache-cli", 8 | "ipfs-local": "node ./scripts/ipfs-local.js", 9 | "ethereum-testnet": "./scripts/start-geth-testnet.sh", 10 | "ethereum-testnet:attach": "./scripts/attach-geth-testnet.sh", 11 | "ng": "ng", 12 | "lint": "ng lint", 13 | "start": "npm run truffle:migrate && npm run electron:serve", 14 | "start:web": "npm run truffle:migrate && webpack-dev-server --target web --content-base . --port 4200 --inline", 15 | "truffle:migrate": "./node_modules/.bin/truffle migrate", 16 | "seed-data": "node ./scripts/add-test-data.js", 17 | "build:electron:main": "tsc main.ts --outDir dist && copyfiles package.json dist && cd dist && npm install --prod && cd ..", 18 | "build": "webpack --display-error-details && npm run build:electron:main", 19 | "build:prod": "cross-env NODE_ENV=production npm run build", 20 | "electron:serve": "npm run build && electron ./dist --serve", 21 | "electron:test": "electron ./dist", 22 | "electron:dev": "npm run build && electron ./dist", 23 | "electron:prod": "npm run build:prod && electron ./dist", 24 | "electron:linux": "npm run build:prod && node package.js --asar --platform=linux --arch=x64", 25 | "electron:windows": "npm run build:prod && node package.js --asar --platform=win32 --arch=ia32", 26 | "electron:mac": "npm run build:prod && node package.js --asar --platform=darwin --arch=x64", 27 | "test": "karma start ./karma.conf.js --singleRun true && npm run test:truffle", 28 | "test:ci": "npm run test:js:ci && npm run test:truffle:ci", 29 | "test:js": "karma start ./karma.conf.js", 30 | "test:js:ci": "karma start ./karma.conf.js --singleRun true --browsers 'ChromeHeadless'", 31 | "test:truffle": "./node_modules/.bin/truffle test", 32 | "test:truffle:ci": "./scripts/test-truffle-ci.sh" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/electron/electron-quick-start.git" 37 | }, 38 | "keywords": [ 39 | "Aletheia", 40 | "Decentralised", 41 | "Academic", 42 | "Publishing", 43 | "Ipfs" 44 | ], 45 | "author": "aletheia-foundation", 46 | "license": "LGPL-3.0", 47 | "devDependencies": { 48 | "@angular/cli": "^1.6.3", 49 | "@angular/compiler-cli": "5.2.0", 50 | "@ng-bootstrap/ng-bootstrap": "^1.0.0-beta.9", 51 | "@ngtools/webpack": "^1.9.4", 52 | "@types/core-js": "0.9.36", 53 | "@types/jasmine": "2.8.3", 54 | "@types/node": "7.0.7", 55 | "autoprefixer": "7.2.4", 56 | "bs58": "4.0.1", 57 | "codelyzer": "4.0.2", 58 | "copyfiles": "1.2.0", 59 | "cross-env": "5.1.3", 60 | "css-loader": "0.28.8", 61 | "cssnano": "3.10.0", 62 | "electron": "1.7.10", 63 | "electron-packager": "10.1.1", 64 | "electron-reload": "1.2.2", 65 | "expect.js": "0.3.1", 66 | "exports-loader": "0.6.4", 67 | "file-loader": "1.1.6", 68 | "ganache-cli": "^6.0.3", 69 | "html-loader": "0.5.4", 70 | "istanbul-instrumenter-loader": "3.0.0", 71 | "jasmine-core": "2.8.0", 72 | "jasmine-spec-reporter": "4.2.1", 73 | "json-loader": "0.5.7", 74 | "karma": "2.0.0", 75 | "karma-chrome-launcher": "2.2.0", 76 | "karma-cli": "1.0.1", 77 | "karma-coverage-istanbul-reporter": "1.3.3", 78 | "karma-jasmine": "1.1.1", 79 | "karma-jasmine-html-reporter": "0.2.2", 80 | "karma-sourcemap-loader": "0.3.7", 81 | "karma-webpack": "^2.0.9", 82 | "less-loader": "4.0.5", 83 | "minimist": "1.2.0", 84 | "mkdirp": "0.5.1", 85 | "mocha": "4.1.0", 86 | "postcss-loader": "2.0.10", 87 | "postcss-url": "7.3.0", 88 | "raw-loader": "0.5.1", 89 | "sass-loader": "6.0.6", 90 | "script-loader": "0.7.2", 91 | "sinon": "4.1.4", 92 | "solc": "0.4.19", 93 | "source-map-loader": "0.2.3", 94 | "style-loader": "0.19.1", 95 | "stylus-loader": "3.0.1", 96 | "truffle": "4.1.8", 97 | "truffle-config": "1.0.0", 98 | "truffle-contract": "3.0.0", 99 | "truffle-expect": "0.0.3", 100 | "ts-node": "^4.1.0", 101 | "tslint": "5.9.1", 102 | "typescript": "2.6.2", 103 | "url-loader": "0.6.2", 104 | "web3-fake-provider": "0.1.0", 105 | "webdriver-manager": "12.0.6", 106 | "webpack": "^3.10.0", 107 | "webpack-dev-server": "^2.10.1" 108 | }, 109 | "dependencies": { 110 | "@angular/animations": "^5.1.3", 111 | "@angular/common": "5.2.0", 112 | "@angular/compiler": "5.2.0", 113 | "@angular/core": "5.2.0", 114 | "@angular/forms": "5.2.0", 115 | "@angular/http": "5.2.0", 116 | "@angular/platform-browser": "5.2.0", 117 | "@angular/platform-browser-dynamic": "5.2.0", 118 | "@angular/router": "5.2.0", 119 | "@ng-bootstrap/ng-bootstrap": "1.0.0-beta.8", 120 | "@types/ipfs": "git+https://github.com/zabirauf/ipfs-types.git", 121 | "angular2-notifications": "^1.0.2", 122 | "bootstrap": "4.0.0-beta.3", 123 | "core-js": "2.5.3", 124 | "enhanced-resolve": "^4.0.0-beta.4", 125 | "go-ipfs-dep": "^0.4.13", 126 | "ipfs-api": "17.2.7", 127 | "jquery": "3.2.1", 128 | "popper.js": "1.12.9", 129 | "q": "1.5.1", 130 | "request-promise-native": "1.0.5", 131 | "rxjs": "5.5.6", 132 | "truffle-contract": "3.0.0", 133 | "truffle-expect": "0.0.3", 134 | "web3": "0.18.4", 135 | "zeppelin-solidity": "1.8.0", 136 | "zone.js": "0.8.20" 137 | }, 138 | "bugs": { 139 | "url": "https://github.com/electron/electron-quick-start/issues" 140 | }, 141 | "homepage": "https://github.com/electron/electron-quick-start#readme", 142 | "directories": { 143 | "test": "test" 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /test/CommunityVotes.spec.js: -------------------------------------------------------------------------------- 1 | const EncodingHelper = require('../test/helpers/test-encoding-helper') 2 | const expectRevert = require('../test/helpers/expectRevert') 3 | const expectThrow = require('../test/helpers/expectThrow') 4 | 5 | console.log('*********', Object.keys(contract)) 6 | 7 | var CommunityVotes = artifacts.require('../contracts/CommunityVotes.sol') 8 | describe('ComunityVotes', function() { 9 | 10 | contract('CommunityVotes', function(accounts) { 11 | var instance; 12 | var reputation1, reputation2, reputation3, reputation4, reputation5, reputation6; 13 | var manuscript1; 14 | var bytesOfAddress = [], ipfsAddress = []; 15 | var nClosed; 16 | var nForVote; 17 | ipfsAddress[0] = 'QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH'; 18 | ipfsAddress[1] = 'QmdavdTfHgbbLCp5DUkZDYGoHwgDuQ2Zf2vpMYnJ6i71n2'; 19 | ipfsAddress[2] = 'QmWk4iniHmBZwpjwSFNmP7im4uEgVBgSFiPu2pU168353k'; 20 | ipfsAddress[3] = 'QmdxbPmxWus45myDDCXMfvyBnn5mLf2QsocKeVnDgxj2QR'; 21 | ipfsAddress[4] = 'QmRPCdKARctQAoooPfaxZWjSMPuzSEvzL44k3e7PPKtVib'; 22 | ipfsAddress[5] = 'QmT8f6vZRdorpsWzyiSighyR7XzyeATqwqtQwwv38KvgHn'; 23 | for(var cnt=0; cnt 1, "voting closed too soon"); 48 | 49 | // check that list of voters is empty & no votes have been counted 50 | assert.equal(voting1[1].toNumber(), 0, "voting count not zero"); 51 | assert.equal(voting1[2].length, 0, "voter list not empty"); 52 | 53 | // check that votes are active 54 | assert(await instance.votingActive(bytesOfAddress[cnt]) > 1, "voting closed" ); 55 | } 56 | 57 | // try to start same voting again 58 | await expectRevert(instance.createVoting(bytesOfAddress[0], {from: accounts[0]})); 59 | }) 60 | 61 | it('vote for and against manuscript', async function() { 62 | 63 | // check that voting is still open 64 | assert(await instance.votingActive(bytesOfAddress[0]) > 1 , "voting is closed"); 65 | // vote for manuscript 66 | 67 | nClosed = 0; 68 | for(var cnt=0; cnt<9; cnt++) { 69 | // vote 5 times for manuscript then against it 70 | nForVote = 5; 71 | if (cnt < nForVote) {var vote = true} else {var vote = false} 72 | // check that voting is still open 73 | if (await instance.votingActive(bytesOfAddress[0]) > 0 ) { 74 | // vote 75 | console.log(bytesOfAddress[0], accounts[cnt], vote) 76 | await instance.vote(bytesOfAddress[0], accounts[cnt], vote); 77 | console.log('done') 78 | } 79 | else { 80 | // save cnt when voting was closed 81 | if (nClosed == 0) {nClosed = cnt}; 82 | // try to vote 83 | await expectRevert(instance.vote(bytesOfAddress[0], accounts[cnt], vote)); 84 | } 85 | } 86 | const blah = await instance.votingActive(bytesOfAddress[0]) 87 | 88 | console.log('blah', blah) 89 | // check that voting is closed 90 | assert.equal(blah, 0 , 91 | "voting is not closed"); 92 | 93 | // try to start same voting again 94 | await expectRevert(instance.createVoting(bytesOfAddress[0], {from: accounts[0]})); 95 | console.log('cannot restart voting') 96 | 97 | // try to vote on a closed voting 98 | await expectRevert(instance.vote(bytesOfAddress[9], accounts[9], false)); 99 | console.log('cannot vote on closed voting') 100 | }) 101 | 102 | it('check voting results', async function(){ 103 | 104 | // check that manuscript was accepted 105 | var nVoters = await instance.getVoting(bytesOfAddress[0]); 106 | assert(nVoters[1] > nVoters[2].length-nVoters[1], "manuscript was not accepted"); 107 | 108 | // check number of positive votes 109 | var voting2 = await instance.getVoting(bytesOfAddress[0]); 110 | assert.equal(voting2[1].toNumber(), nForVote, "number of positive votes not correct"); 111 | 112 | // check if all voters are in voter list 113 | for(var cnt=0; cnt