Browse Source

Init

develop
Ezerous 4 years ago
commit
29bdd3dd8e
  1. 10
      .gitattributes
  2. 34
      .gitignore
  3. 8
      lerna.json
  4. 8
      package.json
  5. 50
      packages/concordia-app/package.json
  6. 15
      packages/concordia-app/patches/web3-eth+1.2.11.patch
  7. BIN
      packages/concordia-app/public/favicon.ico
  8. 39
      packages/concordia-app/public/index.html
  9. 15
      packages/concordia-app/public/manifest.json
  10. 2
      packages/concordia-app/public/robots.txt
  11. 4
      packages/concordia-app/src/assets/css/index.css
  12. 12
      packages/concordia-app/src/assets/css/loading-container.css
  13. BIN
      packages/concordia-app/src/assets/images/PageNotFound.jpg
  14. 1
      packages/concordia-app/src/assets/images/ethereum_logo.svg
  15. 20
      packages/concordia-app/src/assets/images/ipfs_logo.svg
  16. BIN
      packages/concordia-app/src/assets/images/logo.png
  17. BIN
      packages/concordia-app/src/assets/images/orbitdb_logo.png
  18. 24
      packages/concordia-app/src/components/App.jsx
  19. 72
      packages/concordia-app/src/components/AppContext.js
  20. 115
      packages/concordia-app/src/components/LoadingContainer.jsx
  21. 13
      packages/concordia-app/src/components/NotFound.jsx
  22. 29
      packages/concordia-app/src/index.js
  23. 42
      packages/concordia-app/src/options/breezeOptions.js
  24. 15
      packages/concordia-app/src/options/drizzleOptions.js
  25. 14
      packages/concordia-app/src/options/web3Options.js
  26. 23
      packages/concordia-app/src/orbit/levelUtils.js
  27. 11
      packages/concordia-app/src/orbit/orbitUtils.js
  28. 108
      packages/concordia-app/src/orbit/ΕthereumIdentityProvider.js
  29. 23
      packages/concordia-app/src/redux/sagas/orbitSaga.js
  30. 15
      packages/concordia-app/src/redux/sagas/rootSaga.js
  31. 23
      packages/concordia-app/src/redux/store.js
  32. 135
      packages/concordia-app/src/utils/serviceWorker.js
  33. 159
      packages/concordia-contracts/contracts/Forum.sol
  34. 23
      packages/concordia-contracts/contracts/Migrations.sol
  35. 13
      packages/concordia-contracts/index.js
  36. 5
      packages/concordia-contracts/migrations/1_initial_migration.js
  37. 5
      packages/concordia-contracts/migrations/2_deploy_contracts.js
  38. 22
      packages/concordia-contracts/package.json
  39. 27
      packages/concordia-contracts/truffle-config.js
  40. 17510
      yarn.lock

10
.gitattributes

@ -0,0 +1,10 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto eol=lf
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
*.ico binary
# Solidity
*.sol linguist-language=Solidity

34
.gitignore

@ -0,0 +1,34 @@
# Node
/node_modules
packages/*/node_modules
packages/concordia-contracts/build
# IDE
.DS_Store
.idea
# Build Directories
/build
/src/build
/packages/concordia-app/build
/packages/concordia-contracts/build
# Logs
/log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Docker volumes
docker/volumes
docker/reports
docker/env/concordia.env
# Misc
.env.local
.env.development.local
.env.test.local
.env.production.local
# Lerna
*.lerna_backup

8
lerna.json

@ -0,0 +1,8 @@
{
"version": "0.0.1",
"npmClient": "yarn",
"useWorkspaces": true,
"packages": [
"packages/*"
]
}

8
package.json

@ -0,0 +1,8 @@
{
"name": "apella",
"private": true,
"workspaces": {
"packages": ["packages/*"],
"nohoist": ["**/web3", "**/web3/**"]
}
}

50
packages/concordia-app/package.json

@ -0,0 +1,50 @@
{
"name": "concordia-app",
"version": "0.1.0",
"private": true,
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"postinstall": "patch-package",
"rendezvous": "star-signal --port=9090 --host=127.0.0.1"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"dependencies": {
"@ezerous/breeze": "0.1.0",
"@ezerous/drizzle": "0.2.1",
"@reduxjs/toolkit": "1.4.0",
"concordia-contracts": "0.1.0",
"level": "6.0.1",
"prop-types": "15.7.2",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-redux": "7.2.1",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",
"react-scripts": "3.4.3",
"redux-saga": "1.1.3",
"web3": "1.2.11"
},
"devDependencies": {
"libp2p-webrtc-star": "0.20.0",
"orbit-db-identity-provider": "0.3.1",
"patch-package": "6.2.2",
"postinstall-postinstall": "2.1.0"
}
}

15
packages/concordia-app/patches/web3-eth+1.2.11.patch

@ -0,0 +1,15 @@
diff --git a/node_modules/web3-eth/src/index.js b/node_modules/web3-eth/src/index.js
index 469c149..6f7f1bc 100644
--- a/node_modules/web3-eth/src/index.js
+++ b/node_modules/web3-eth/src/index.js
@@ -343,8 +343,8 @@ var Eth = function Eth() {
// add ABI
this.abi = abi;
- // add ENS
- this.ens = new ENS(this);
+ // add ENS (Removed because of https://github.com/ethereum/web3.js/issues/2665#issuecomment-687164093)
+ // this.ens = new ENS(this);
var methods = [
new Method({

BIN
packages/concordia-app/public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

39
packages/concordia-app/public/index.html

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css" />
<title>Concordia</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

15
packages/concordia-app/public/manifest.json

@ -0,0 +1,15 @@
{
"short_name": "Apella",
"name": "Apella",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

2
packages/concordia-app/public/robots.txt

@ -0,0 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *

4
packages/concordia-app/src/assets/css/index.css

@ -0,0 +1,4 @@
body {
margin: 10em;
padding: 0;
}

12
packages/concordia-app/src/assets/css/loading-container.css

@ -0,0 +1,12 @@
.loading-screen {
text-align: center;
}
.loading-img {
margin-bottom: 30px;
height: 100px;
}
ul {
list-style-position: inside;
}

BIN
packages/concordia-app/src/assets/images/PageNotFound.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

1
packages/concordia-app/src/assets/images/ethereum_logo.svg

@ -0,0 +1 @@
<svg height="64" viewBox="0 0 49.91744 49.931196" width="64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m720.6 306.4h508.7v266h-508.7z" height="100%" width="100%"/></clipPath><clipPath id="b"><path d="m720.6 0h254.4v572.4h-254.4z" height="100%" width="100%"/></clipPath><clipPath id="c"><path d="m975 0h254.4v572.4h-254.4z" height="100%" width="100%"/></clipPath><clipPath id="d"><path d="m720.6 470.3h254.4v358.4h-254.4z" height="100%" width="100%"/></clipPath><clipPath id="e"><path d="m975 470.3h254.5v358.4h-254.5z" height="100%" width="100%"/></clipPath><g fill="#010101"><path clip-path="url(#a)" d="m975 306.4-254.4 115.7 254.4 150.3 254.3-150.3z" opacity=".6" transform="matrix(.06025243658 0 0 .06025243658 -33.79041826347 .00000056684)"/><path clip-path="url(#b)" d="m720.6 422.1 254.4 150.3v-572.4z" opacity=".45" transform="matrix(.06025243658 0 0 .06025243658 -33.79041826347 .00000056684)"/><path clip-path="url(#c)" d="m975 0v572.4l254.3-150.3z" opacity=".8" transform="matrix(.06025243658 0 0 .06025243658 -33.79041826347 .00000056684)"/><path clip-path="url(#d)" d="m720.6 470.3 254.4 358.4v-208.1z" opacity=".45" transform="matrix(.06025243658 0 0 .06025243658 -33.79041826347 .00000056684)"/><path clip-path="url(#e)" d="m975 620.6v208.1l254.5-358.4z" opacity=".8" transform="matrix(.06025243658 0 0 .06025243658 -33.79041826347 .00000056684)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

20
packages/concordia-app/src/assets/images/ipfs_logo.svg

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" style="enable-background:new" xmlns="http://www.w3.org/2000/svg" xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" height="512" width="512" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 511.99999 511.99998" xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs>
<linearGradient id="b" y2="771.51" gradientUnits="userSpaceOnUse" x2="527.72" y1="771.51" x1="84.315">
<stop stop-color="#4a9ea1" offset="0"/>
</linearGradient>
<linearGradient id="a" y2="771.48" gradientUnits="userSpaceOnUse" x2="512.36" y1="771.48" x1="99.675">
<stop stop-color="#63d3d7" offset="0"/>
</linearGradient>
</defs>
<g transform="translate(-50.017 -515.51)">
<path d="m84.315 899.51 221.7 128 221.7-128v-256l-221.7-127.99-221.7 128z" fill="url(#b)"/>
<path fill="url(#a)" d="m283.13 546.35-160.74 92.806c0.32126 2.8543 0.32125 5.7352 0 8.5894l160.75 92.806c13.554-10.001 32.043-10.001 45.597 0l160.75-92.807c-0.32126-2.8543-0.32293-5.7338-0.001-8.588l-160.74-92.806c-13.554 10.001-32.044 10.001-45.599 0zm221.79 127.03-160.92 93.84c1.884 16.739-7.3611 32.751-22.799 39.489l0.18062 184.58c2.6325 1.1489 5.1267 2.5886 7.438 4.294l160.75-92.805c-1.884-16.739 7.3611-32.752 22.799-39.49v-185.61c-2.6325-1.1489-5.1281-2.5886-7.4394-4.294zm-397.81 1.0315c-2.3112 1.7054-4.8054 3.1465-7.438 4.2954v185.61c15.438 6.7378 24.683 22.75 22.799 39.489l160.74 92.806c2.3112-1.7054 4.8069-3.1465 7.4394-4.2954v-185.61c-15.438-6.7378-24.683-22.75-22.799-39.489l-160.74-92.81z"/>
</g>
<g transform="translate(0 -196.66)">
<path d="m256 708.66 221.7-128v-256l-221.7 128v256z" fill-opacity=".25098"/>
<path d="m256 708.66v-256l-221.7-128v256l221.7 128z" fill-opacity=".039216"/>
<path d="m34.298 324.66 221.7 128 221.7-128-221.7-128-221.7 128z" fill-opacity=".13018"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
packages/concordia-app/src/assets/images/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
packages/concordia-app/src/assets/images/orbitdb_logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

24
packages/concordia-app/src/components/App.jsx

@ -0,0 +1,24 @@
import React from 'react'
import { Provider } from 'react-redux'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import LoadingContainer from './LoadingContainer'
import PropTypes from 'prop-types'
import NotFound from '../components/NotFound';
const App = ({ store }) => (
<Provider store={store}>
<LoadingContainer>
<Router>
<Switch>
<Route component={NotFound} />
</Switch>
</Router>
</LoadingContainer>
</Provider>
)
App.propTypes = {
store: PropTypes.object.isRequired
}
export default App

72
packages/concordia-app/src/components/AppContext.js

@ -0,0 +1,72 @@
// Modified version of https://github.com/trufflesuite/drizzle/blob/develop/packages/react-plugin/src/DrizzleContext.js
import React from "react";
const Context = React.createContext();
class Provider extends React.Component {
state = {
drizzleState: null,
drizzleInitialized: false,
breezeState: null,
breezeInitialized: false
};
componentDidMount() {
const { drizzle, breeze } = this.props;
// subscribe to changes in the store, keep state up-to-date
this.unsubscribe = drizzle.store.subscribe(() => {
const drizzleState = drizzle.store.getState();
const breezeState = breeze.store.getState();
if (drizzleState.drizzleStatus.initialized) {
this.setState({
drizzleState,
drizzleInitialized: true
});
}
if (breezeState.breezeStatus.initialized) {
this.setState({
breezeState: breezeState,
breezeInitialized: true
});
}
});
this.unsubscribe = breeze.store.subscribe(() => {
const breezeState = breeze.store.getState();
if (breezeState.breezeStatus.initialized) {
this.setState({
breezeState: breezeState,
breezeInitialized: true
});
}
});
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<Context.Provider
value={{
drizzle: this.props.drizzle,
drizzleState: this.state.drizzleState,
drizzleInitialized: this.state.drizzleInitialized,
breeze: this.props.breeze,
breezeState: this.state.breezeState,
breezeInitialized: this.state.breezeInitialized
}}
>
{this.props.children}
</Context.Provider>
);
}
}
export default {
Context: Context,
Consumer: Context.Consumer,
Provider
};

115
packages/concordia-app/src/components/LoadingContainer.jsx

@ -0,0 +1,115 @@
import React, { Children, Component } from 'react';
import { connect } from 'react-redux';
import '../assets/css/loading-container.css';
import ethereum_logo from '../assets/images/ethereum_logo.svg';
import ipfs_logo from '../assets/images/ipfs_logo.svg';
import orbitdb_logo from '../assets/images/orbitdb_logo.png';
import logo from '../assets/images/logo.png';
class LoadingContainer extends Component {
render() {
if (this.props.web3.status === 'failed' || !this.props.web3.networkId) {
return (
<main className="loading-screen">
<div className="pure-g">
<div className="pure-u-1-1">
<img src={ethereum_logo} alt="ethereum_logo" className="loading-img" />
<p><strong>This browser has no connection to the Ethereum network</strong></p>
Please make sure that:
<ul>
<li>MetaMask is unlocked and pointed to the correct network</li>
<li>The app has been granted the right to connect to your account</li>
</ul>
</div>
</div>
</main>
);
}
if (this.props.web3.status === 'initialized' && Object.keys(this.props.accounts).length === 0) {
return (
<main className="loading-screen">
<div>
<img src={ethereum_logo} alt="ethereum_logo" className="loading-img" />
<p><strong>We can't find any Ethereum accounts!</strong></p>
<p>Please make sure that MetaMask is unlocked.</p>
</div>
</main>
);
}
if (!this.props.contractDeployed) {
return (
<main className="loading-screen">
<div>
<img src={ethereum_logo} alt="ethereum_logo" className="loading-img" />
<p><strong>No contracts found on the current network!</strong></p>
<p>Please make sure that you are connected to the correct network
and the contracts are deployed.</p>
</div>
</main>
);
}
if (!this.props.contractInitialized) {
return (
<main className="loading-screen">
<div>
<img src={ethereum_logo} alt="ethereum_logo" className="loading-img" />
<p><strong>Initializing contracts...</strong></p>
</div>
</main>
);
}
if (!this.props.ipfsInitialized) {
return (
<main className="loading-screen">
<div>
<img src={ipfs_logo} alt="ipfs_logo" className="loading-img" />
<p><strong>Initializing IPFS...</strong></p>
</div>
</main>
);
}
if (!this.props.orbitInitialized) {
const message = process.env.NODE_ENV === 'development'
? 'If needed, please sign the transaction in MetaMask to create the databases.'
: 'Please sign the transaction in MetaMask to create the databases.';
return (
<main className="loading-screen">
<div>
<img src={orbitdb_logo} alt="orbitdb_logo" className="loading-img" />
<p><strong>Preparing OrbitDB...</strong></p>
<p>{message}</p>
</div>
</main>
);
}
if (this.props.drizzleStatus.initialized) return Children.only(this.props.children);
return (
<main className="loading-screen">
<div>
<img src={logo} alt="app_logo" className="loading-img" />
<p><strong>Loading dapp...</strong></p>
</div>
</main>
);
}
}
const mapStateToProps = (state) => ({
accounts: state.accounts,
drizzleStatus: state.drizzleStatus,
breezeStatus: state.breezeStatus,
web3: state.web3,
contractInitialized: state.contracts.Forum.initialized,
contractDeployed: state.contracts.Forum.deployed,
ipfsInitialized: state.ipfs.initialized,
orbitInitialized: state.orbit.initialized,
});
export default connect(mapStateToProps)(LoadingContainer);

13
packages/concordia-app/src/components/NotFound.jsx

@ -0,0 +1,13 @@
import React from 'react';
import pageNotFound from '../assets/images/PageNotFound.jpg';
const NotFound = () => (
<div style={{
textAlign: 'center',
}}
>
<img src={pageNotFound} alt="Page not found!" />
</div>
);
export default NotFound;

29
packages/concordia-app/src/index.js

@ -0,0 +1,29 @@
import React from 'react';
import { render } from 'react-dom';
import App from './components/App'
import store from './redux/store';
import { Drizzle } from '@ezerous/drizzle'
import { Breeze } from '@ezerous/breeze'
import AppContext from "./components/AppContext";
import drizzleOptions from './options/drizzleOptions';
import * as serviceWorker from './utils/serviceWorker';
import './assets/css/index.css';
import breezeOptions from './options/breezeOptions';
const drizzle = new Drizzle(drizzleOptions, store);
const breeze = new Breeze(breezeOptions, store);
render(
<AppContext.Provider drizzle={drizzle} breeze={breeze}>
<App store={store} />
</AppContext.Provider>,
document.getElementById('root')
);
serviceWorker.unregister(); // See also: http://bit.ly/CRA-PWA

42
packages/concordia-app/src/options/breezeOptions.js

@ -0,0 +1,42 @@
import web3Options from './web3Options';
import EthereumIdentityProvider from '../orbit/ΕthereumIdentityProvider';
import {orbitTypes} from '@ezerous/breeze'
const { web3 } = web3Options;
EthereumIdentityProvider.setWeb3(web3);
const breezeOptions = {
ipfs: {
config: {
Addresses: {
Swarm: [
// Use local signaling server (see also rendezvous script in package.json)
// For more information: https://github.com/libp2p/js-libp2p-webrtc-star
'/ip4/127.0.0.1/tcp/9090/wss/p2p-webrtc-star'
],
},
},
preload: {
enabled: false,
},
init: {
emptyRepo: true,
}
},
web3,
orbit: {
identityProvider: EthereumIdentityProvider,
databases: [
{
name: 'topics',
type: orbitTypes.ORBIT_TYPE_KEYVALUE
},
{
name: 'posts',
type: orbitTypes.ORBIT_TYPE_KEYVALUE
}
]
}
};
export default breezeOptions;

15
packages/concordia-app/src/options/drizzleOptions.js

@ -0,0 +1,15 @@
// See also: https://truffleframework.com/docs/drizzle/reference/drizzle-options
import { contracts } from 'concordia-contracts';
import web3Options from './web3Options';
const drizzleOptions = {
web3: {
customProvider: web3Options.web3
},
contracts,
events: {
Forum: ['UserSignedUp', 'UsernameUpdated', 'TopicCreated', 'PostCreated']
}
};
export default drizzleOptions;

14
packages/concordia-app/src/options/web3Options.js

@ -0,0 +1,14 @@
import Web3 from 'web3';
import EthereumIdentityProvider from '../orbit/ΕthereumIdentityProvider';
const { WEB3_URL, WEB3_PORT } = process.env;
const web3 = new Web3(Web3.givenProvider || `ws://${WEB3_URL}:${WEB3_PORT}`);
EthereumIdentityProvider.setWeb3(web3);
const web3Options = {
web3
};
export default web3Options;

23
packages/concordia-app/src/orbit/levelUtils.js

@ -0,0 +1,23 @@
import level from 'level';
/* Used in development only to store the identity.signatures.publicKey so developers don't have to
repeatedly sign theOrbitDB creation transaction in MetaMask when React development server reloads
the app */
const concordiaDB = level('./concordia/identities');
async function storeIdentitySignaturePubKey(key, signaturePubKey) {
await concordiaDB.put(key, signaturePubKey);
}
// If it exists, it returns the identity.signatures.publicKey for the given key (key is the
// concatenation of identity.publicKey + identity.signatures.id
async function getIdentitySignaturePubKey(key) {
try {
return await concordiaDB.get(key);
} catch (err) {
if (err && err.notFound) return null; // Not found
throw err;
}
}
export { storeIdentitySignaturePubKey, getIdentitySignaturePubKey };

11
packages/concordia-app/src/orbit/orbitUtils.js

@ -0,0 +1,11 @@
// https://github.com/orbitdb/orbit-db/blob/master/GUIDE.md#address
export async function determineDBAddress({orbit, dbName, type, identityId}) {
const ipfsMultihash = (await orbit.determineAddress(dbName, type, {
accessController: { write: [identityId] },
})).root;
return `/orbitdb/${ipfsMultihash}/${dbName}`;
}

108
packages/concordia-app/src/orbit/ΕthereumIdentityProvider.js

@ -0,0 +1,108 @@
import { getIdentitySignaturePubKey, storeIdentitySignaturePubKey } from './levelUtils';
import IdentityProvider from "orbit-db-identity-provider";
const LOGGING_PREFIX = 'EthereumIdentityProvider: ';
class EthereumIdentityProvider extends IdentityProvider{
constructor(options = {}) {
if(!EthereumIdentityProvider.web3)
throw new Error(LOGGING_PREFIX + "Couldn't create identity, because web3 wasn't set. " +
"Please use setWeb3(web3) first!");
super(options);
// Orbit's Identity Id (user's Ethereum address) - Optional (will be grabbed later if omitted)
const id = options.id;
if(id){
if(EthereumIdentityProvider.web3.utils.isAddress(id))
this.id = options.id;
else
throw new Error(LOGGING_PREFIX + "Couldn't create identity, because an invalid id was supplied.");
}
}
static get type() { return 'ethereum'; }
async getId() {
// Id wasn't in the constructor, grab it now
if(!this.id) {
const accounts = await EthereumIdentityProvider.web3.eth.getAccounts();
if(!accounts[0])
throw new Error(LOGGING_PREFIX + "Couldn't create identity, because no web3 accounts were found (locked Metamask?).");
this.id = accounts[0];
}
return this.id;
}
async signIdentity(data) {
if (process.env.NODE_ENV === 'development') { //Don't sign repeatedly while in development
console.debug(LOGGING_PREFIX + 'Attempting to find stored Orbit identity data...');
const signaturePubKey = await getIdentitySignaturePubKey(data);
if (signaturePubKey) {
const identityInfo = {
id: this.id,
pubKeySignId: data,
signaturePubKey,
};
if (await EthereumIdentityProvider.verifyIdentityInfo(identityInfo)) {
console.debug(LOGGING_PREFIX + 'Found and verified stored Orbit identity data!');
return signaturePubKey;
}
console.debug(LOGGING_PREFIX + "Stored Orbit identity data couldn't be verified.");
} else
console.debug(LOGGING_PREFIX + 'No stored Orbit identity data were found.');
}
return await this.doSignIdentity(data);
}
async doSignIdentity(data) {
try {
const signaturePubKey = await EthereumIdentityProvider.web3.eth.personal.sign(data, this.id, '');
if (process.env.NODE_ENV === 'development') {
storeIdentitySignaturePubKey(data, signaturePubKey)
.then(() => {
console.debug(LOGGING_PREFIX + 'Successfully stored current Orbit identity data.');
})
.catch(() => {
console.warn(LOGGING_PREFIX + "Couldn't store current Orbit identity data...");
});
}
return signaturePubKey; // Password not required for MetaMask
} catch (error) {
if(error.code && error.code === 4001){
console.debug(LOGGING_PREFIX + 'User denied message signature.');
return await this.doSignIdentity(data);
}
else{
console.error(LOGGING_PREFIX + 'Failed to sign data.');
console.error(error);
}
}
}
static async verifyIdentity(identity) {
// Verify that identity was signed by the ID
return new Promise(resolve => {
resolve(EthereumIdentityProvider.web3.eth.accounts.recover(identity.publicKey + identity.signatures.id,
identity.signatures.publicKey) === identity.id)
})
}
static async verifyIdentityInfo(identityInfo) {
// Verify that identity was signed by the ID
return new Promise(resolve => {
resolve(EthereumIdentityProvider.web3.eth.accounts.recover(identityInfo.pubKeySignId,
identityInfo.signaturePubKey) === identityInfo.id)
})
}
// Initialize by supplying a web3 object
static setWeb3(web3){
EthereumIdentityProvider.web3 = web3;
}
}
EthereumIdentityProvider.web3 = {};
export default EthereumIdentityProvider;

23
packages/concordia-app/src/redux/sagas/orbitSaga.js

@ -0,0 +1,23 @@
import { put, all, take } from 'redux-saga/effects'
import { breezeActions } from '@ezerous/breeze'
import { drizzleActions } from '@ezerous/drizzle'
export function * initOrbitDatabases (action) {
const { account, breeze} = action;
yield put(breezeActions.orbit.orbitInit(breeze, account)); //same as breeze.initOrbit(account);
}
function * orbitSaga () {
// Not sure which will come first
const res = yield all([
take(breezeActions.breeze.BREEZE_INITIALIZED),
take(action => action.type === drizzleActions.account.ACCOUNTS_FETCHED
&& action.accounts.length > 0)
]);
yield initOrbitDatabases({breeze:res[0].breeze, account: res[1].accounts[0]});
}
export default orbitSaga

15
packages/concordia-app/src/redux/sagas/rootSaga.js

@ -0,0 +1,15 @@
import { all, fork } from 'redux-saga/effects';
import { drizzleSagas } from '@ezerous/drizzle';
import { breezeSagas } from '@ezerous/breeze'
import orbitSaga from './orbitSaga'
export default function* root() {
const sagas = [
...drizzleSagas,
...breezeSagas,
orbitSaga
];
yield all(
sagas.map((saga) => fork(saga)),
);
}

23
packages/concordia-app/src/redux/store.js

@ -0,0 +1,23 @@
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import { drizzleReducers, drizzleMiddlewares, generateContractsInitialState } from '@ezerous/drizzle';
import { breezeReducers, breezeMiddlewares } from '@ezerous/breeze'
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas/rootSaga';
import drizzleOptions from '../options/drizzleOptions';
const initialState = {
contracts: generateContractsInitialState(drizzleOptions),
};
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
reducer: {...drizzleReducers, ...breezeReducers },
middleware: getDefaultMiddleware({
serializableCheck: false, //https://redux.js.org/style-guide/style-guide/#do-not-put-non-serializable-values-in-state-or-actions
}).concat(drizzleMiddlewares).concat(breezeMiddlewares).concat(sagaMiddleware),
preloadedState: initialState
})
sagaMiddleware.run(rootSaga);
export default store;

135
packages/concordia-app/src/utils/serviceWorker.js

@ -0,0 +1,135 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

159
packages/concordia-contracts/contracts/Forum.sol

@ -0,0 +1,159 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.7.1;
contract Forum {
//----------------------------------------USER----------------------------------------
struct User {
string username; // TODO: set an upper bound instead of arbitrary string
uint[] topicIDs; // IDs of the topics the user created
uint[] postIDs; // IDs of the posts the user created
uint timestamp;
bool signedUp; // Helper variable for hasUserSignedUp()
}
mapping (address => User) users;
mapping (string => address) userAddresses;
event UserSignedUp(string username, address userAddress);
event UsernameUpdated(string newName, string oldName,address userAddress);
function signUp(string memory username) public returns (bool) {
require (!hasUserSignedUp(msg.sender), "User has already signed up.");
require(!isUserNameTaken(username), "Username is already taken.");
users[msg.sender] = User(username,
new uint[](0), new uint[](0), block.timestamp, true);
userAddresses[username] = msg.sender;
emit UserSignedUp(username, msg.sender);
return true;
}
function updateUsername(string memory newUsername) public returns (bool) {
require (hasUserSignedUp(msg.sender), "User hasn't signed up yet.");
require(!isUserNameTaken(newUsername), "Username is already taken.");
string memory oldUsername = getUsername(msg.sender);
delete userAddresses[users[msg.sender].username];
users[msg.sender].username = newUsername;
userAddresses[newUsername] = msg.sender;
emit UsernameUpdated(newUsername, oldUsername, msg.sender);
return true;
}
function getUsername(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up yet.");
return users[userAddress].username;
}
function getUserAddress(string memory username) public view returns (address) {
return userAddresses[username];
}
function hasUserSignedUp(address userAddress) public view returns (bool) {
return users[userAddress].signedUp;
}
function isUserNameTaken(string memory username) public view returns (bool) {
if (getUserAddress(username)!=address(0))
return true;
return false;
}
function getUserTopics(address userAddress) public view returns (uint[] memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up yet.");
return users[userAddress].topicIDs;
}
function getUserPosts(address userAddress) public view returns (uint[] memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up yet.");
return users[userAddress].postIDs;
}
function getUserDateOfRegister(address userAddress) public view returns (uint) {
require (hasUserSignedUp(userAddress), "User hasn't signed up yet.");
return users[userAddress].timestamp;
}
//----------------------------------------POSTING----------------------------------------
struct Topic {
uint topicID;
address author;
uint timestamp;
uint[] postIDs;
}
struct Post {
uint postID;
address author;
uint timestamp;
uint topicID;
}
uint numTopics; // Total number of topics
uint numPosts; // Total number of posts
mapping (uint => Topic) topics;
mapping (uint => Post) posts;
event TopicCreated(uint topicID, uint postID);
event PostCreated(uint postID, uint topicID);
function createTopic() public returns (uint, uint) {
require(hasUserSignedUp(msg.sender)); // Only registered users can create topics
//Creates topic
uint topicID = numTopics++;
topics[topicID] = Topic(topicID, msg.sender, block.timestamp, new uint[](0));
users[msg.sender].topicIDs.push(topicID);
//Adds first post to topic
uint postID = numPosts++;
posts[postID] = Post(postID, msg.sender, block.timestamp, topicID);
topics[topicID].postIDs.push(postID);
users[msg.sender].postIDs.push(postID);
emit TopicCreated(topicID, postID);
return (topicID, postID);
}
function createPost(uint topicID) public returns (uint) {
require(hasUserSignedUp(msg.sender)); // Only registered users can create posts
require(topicID<numTopics); // Only allow posting to a topic that exists
uint postID = numPosts++;
posts[postID] = Post(postID, msg.sender, block.timestamp, topicID);
topics[topicID].postIDs.push(postID);
users[msg.sender].postIDs.push(postID);
emit PostCreated(postID, topicID);
return postID;
}
function getNumberOfTopics() public view returns (uint) {
return numTopics;
}
function getTopic(uint topicID) public view returns (address, string memory, uint, uint[] memory) {
//TODO: require(hasUserSignedUp(msg.sender)); needed?
require(topicID<numTopics);
return (
topics[topicID].author,
users[topics[topicID].author].username,
topics[topicID].timestamp,
topics[topicID].postIDs
);
}
function getTopicPosts(uint topicID) public view returns (uint[] memory) {
require(topicID<numTopics); // Topic should exist
return topics[topicID].postIDs;
}
function getPost(uint postID) public view returns (address, string memory, uint, uint) {
//TODO: require(hasUserSignedUp(msg.sender)); needed?
require(postID<numPosts);
return (
posts[postID].author,
users[posts[postID].author].username,
posts[postID].timestamp,
posts[postID].topicID
);
}
}

23
packages/concordia-contracts/contracts/Migrations.sol

@ -0,0 +1,23 @@
pragma solidity 0.7.1;
contract Migrations {
address public owner;
uint public last_completed_migration;
constructor() public {
owner = msg.sender;
}
modifier restricted() {
if (msg.sender == owner) _;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

13
packages/concordia-contracts/index.js

@ -0,0 +1,13 @@
let Forum;
try {
// eslint-disable-next-line global-require
Forum = require('./build/Forum.json');
} catch (e) {
// eslint-disable-next-line no-console
console.error("Could not require contract artifacts. Haven't you run compile yet?");
}
module.exports = {
contracts: [Forum]
};

5
packages/concordia-contracts/migrations/1_initial_migration.js

@ -0,0 +1,5 @@
const Migrations = artifacts.require('./Migrations.sol');
module.exports = function (deployer) {
deployer.deploy(Migrations);
};

5
packages/concordia-contracts/migrations/2_deploy_contracts.js

@ -0,0 +1,5 @@
const Forum = artifacts.require('Forum');
module.exports = function (deployer) {
deployer.deploy(Forum);
};

22
packages/concordia-contracts/package.json

@ -0,0 +1,22 @@
{
"name": "concordia-contracts",
"version": "0.1.0",
"private": true,
"description": "Contracts used to power Concordia",
"main": "index.js",
"scripts": {
"compile": "yarn truffle compile",
"lint": "yarn _eslint && yarn _solhint",
"_eslint": "yarn eslint . --format table",
"_solhint": "yarn solhint --formatter table contracts/*.sol test/*.sol",
"test": "yarn truffle test",
"migrate": "yarn truffle migrate --network develop"
},
"dependencies": {
"@openzeppelin/contracts": "3.1.0",
"truffle": "5.1.43"
},
"devDependencies": {
"solhint": "3.2.0"
}
}

27
packages/concordia-contracts/truffle-config.js

@ -0,0 +1,27 @@
const path = require("path");
const { GANACHE_HOST } = process.env;
const { GANACHE_PORT } = process.env;
module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!
compilers: {
solc: {
version: "0.7.1"
}
},
contracts_build_directory: path.join(__dirname, 'build/'),
networks: {
develop: {
host: GANACHE_HOST || '127.0.0.1',
port: GANACHE_PORT || '8545',
network_id: '*',
},
test: {
host: GANACHE_HOST || '127.0.0.1',
port: GANACHE_PORT || '8546',
network_id: '*',
}
}
};

17510
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save