mirror of https://gitlab.com/ecentrics/concordia
Ezerous
4 years ago
commit
29bdd3dd8e
40 changed files with 18644 additions and 0 deletions
@ -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 |
@ -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 |
@ -0,0 +1,8 @@ |
|||
{ |
|||
"version": "0.0.1", |
|||
"npmClient": "yarn", |
|||
"useWorkspaces": true, |
|||
"packages": [ |
|||
"packages/*" |
|||
] |
|||
} |
@ -0,0 +1,8 @@ |
|||
{ |
|||
"name": "apella", |
|||
"private": true, |
|||
"workspaces": { |
|||
"packages": ["packages/*"], |
|||
"nohoist": ["**/web3", "**/web3/**"] |
|||
} |
|||
} |
@ -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" |
|||
} |
|||
} |
@ -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({ |
After Width: | Height: | Size: 3.8 KiB |
@ -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> |
@ -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" |
|||
} |
@ -0,0 +1,2 @@ |
|||
# https://www.robotstxt.org/robotstxt.html |
|||
User-agent: * |
@ -0,0 +1,4 @@ |
|||
body { |
|||
margin: 10em; |
|||
padding: 0; |
|||
} |
@ -0,0 +1,12 @@ |
|||
.loading-screen { |
|||
text-align: center; |
|||
} |
|||
|
|||
.loading-img { |
|||
margin-bottom: 30px; |
|||
height: 100px; |
|||
} |
|||
|
|||
ul { |
|||
list-style-position: inside; |
|||
} |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 216 KiB |
After Width: | Height: | Size: 112 KiB |
@ -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 |
@ -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 |
|||
}; |
@ -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); |
@ -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; |
@ -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
|
|||
|
|||
|
|||
|
@ -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; |
@ -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; |
@ -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; |
@ -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 }; |
@ -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}`; |
|||
} |
|||
|
|||
|
|||
|
|||
|
@ -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; |
@ -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 |
|||
|
@ -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)), |
|||
); |
|||
} |
@ -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; |
@ -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(); |
|||
}); |
|||
} |
|||
} |
@ -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 |
|||
); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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] |
|||
}; |
@ -0,0 +1,5 @@ |
|||
const Migrations = artifacts.require('./Migrations.sol'); |
|||
|
|||
module.exports = function (deployer) { |
|||
deployer.deploy(Migrations); |
|||
}; |
@ -0,0 +1,5 @@ |
|||
const Forum = artifacts.require('Forum'); |
|||
|
|||
module.exports = function (deployer) { |
|||
deployer.deploy(Forum); |
|||
}; |
@ -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" |
|||
} |
|||
} |
@ -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: '*', |
|||
} |
|||
} |
|||
}; |
File diff suppressed because it is too large
Loading…
Reference in new issue