Commit d2f8682b authored by kevinflo's avatar kevinflo

initial public commit

parents
.idea
temp.sql
.vscode
notes.txt
node_modules
ethereum/build
secrets.json
wallet-key.json
scrap
.next
.DS_Store
\ No newline at end of file
{
"singleQuote": true
}
MIT License
Copyright (c) 2018 Kevin Florenzano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
{
"DPOSTS": {
"RINKEBY": "0x33D430BC40141917F4389CC340dC3DEFA3df8526",
"RINKEBY2": "0xA323C7A77c8778C96A2b89706331173fD306A0DC",
"RINKEBY3": "0x6830D2D3F83295056A6600D45c3c6C72E0987478",
"RINKEBY4": "0xcba065DCD2b7A8460bF3882d2586D75dE9E92E0f",
"RINKEBY5": "0xC18A50F422a5e224E8A9aAAF21AE8C4489E3Be40",
"RINKEBY6": "0xec42474bC2Fc05cdddEfA876d17737183FF4e4Fb"
}
}
const path = require('path');
const solc = require('solc');
const fs = require('fs-extra');
const buildPath = path.resolve(__dirname, 'build');
fs.removeSync(buildPath);
const dpostsPath = path.resolve(__dirname, 'contracts', 'Dposts.sol');
const source = fs.readFileSync(dpostsPath, 'utf8');
const output = solc.compile(source, 1).contracts;
fs.ensureDirSync(buildPath);
for (let contract in output) {
fs.outputJsonSync(
path.resolve(buildPath, contract.replace(':', '') + '.json'),
output[contract]
);
}
// module.exports = {
// Dposts: contracts[':Dposts'],
// Board: contracts[':Board']
// };
This diff is collapsed.
const HDWalletProvider = require('truffle-hdwallet-provider');
const Web3 = require('web3');
const compiledDposts = require('./build/Dposts.json');
const WALLET_KEY = require('../wallet-key.json').WALLET_KEY;
const INFURA_RINKEBY = require('../secrets.json').INFURA2.RINKEBY;
const INFURA_MAINNET = require('../secrets.json').INFURA2.MAINNET;
const provider = new HDWalletProvider(WALLET_KEY, INFURA_RINKEBY, 1, 1);
const web3 = new Web3(provider);
const deploy = async () => {
const accounts = await web3.eth.getAccounts();
console.log('Attempting to deploy from account', accounts[0]);
try {
const result = await new web3.eth.Contract(
JSON.parse(compiledDposts.interface)
)
.deploy({ data: compiledDposts.bytecode })
.send({ gas: 6000000, from: accounts[0] });
console.log('Contract deployed to', result.options.address);
} catch (err) {
console.error('deploy failed with error', err, err.message);
}
};
deploy();
import web3 from './web3';
import Dposts from './build/Dposts.json';
const addresses = require('../addresses.json');
const instance = new web3.eth.Contract(
JSON.parse(Dposts.interface),
addresses.DPOSTS.RINKEBY6
);
export default instance;
import Web3 from 'web3';
const INFURA_RINKEBY = require('../secrets.json').INFURA2.RINKEBY;
let web3;
if (typeof window !== 'undefined' && typeof window.web3 !== 'undefined') {
web3 = new Web3(window.web3.currentProvider);
web3.isServer = false;
} else {
const provider = new Web3.providers.HttpProvider(INFURA_RINKEBY);
web3 = new Web3(provider);
web3.isServer = true;
}
export default web3;
This diff is collapsed.
{
"name": "dposts",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "mocha",
"dev": "node ./web/server.js",
"build": "next build",
"start": "NODE_ENV=production node ./web/server.js"
},
"author": "Kflo",
"license": "MIT",
"dependencies": {
"@zeit/next-sass": "^0.1.2",
"bootstrap": "^4.1.0",
"ganache-cli": "^6.1.0",
"mocha": "^5.0.5",
"next": "^6.0.3",
"node-sass": "^4.8.3",
"react": "^16.3.1",
"react-dom": "^16.3.1",
"reactstrap": "^5.0.0-beta.3",
"solc": "^0.4.21",
"truffle-hdwallet-provider": "0.0.3",
"web3": "^1.0.0-beta.34"
}
}
const assert = require('assert');
const ganache = require('ganache-cli');
const Web3 = require('web3');
const provider = ganache.provider();
const web3 = new Web3(provider);
const compiledDposts = require('../ethereum/build/Dposts.json');
const compiledBoard = require('../ethereum/build/Board.json');
// const { Dposts, Board } = require('../compile');
let accounts;
let dposts;
let lastingDposts;
let firstBoardAddress;
let firstBoard;
before(async () => {
accounts = await web3.eth.getAccounts();
// Use one of those accounts to deploy the main Dposts contract
let gasCost = await new web3.eth.Contract(
JSON.parse(compiledDposts.interface)
)
.deploy({ data: compiledDposts.bytecode, arguments: [] })
.estimateGas({ from: accounts[0] });
lastingDposts = await new web3.eth.Contract(
JSON.parse(compiledDposts.interface)
)
.deploy({ data: compiledDposts.bytecode, arguments: [] })
.send({ from: accounts[0], gas: gasCost });
console.log('faddress', lastingDposts.options.address);
lastingDposts.setProvider(provider);
});
beforeEach(async () => {
// Get a list of all accounts
accounts = await web3.eth.getAccounts();
let gasCost = await new web3.eth.Contract(
JSON.parse(compiledDposts.interface)
)
.deploy({ data: compiledDposts.bytecode, arguments: [] })
.estimateGas({ from: accounts[0] });
// Use one of those accounts to deploy the main Dposts contract
dposts = await new web3.eth.Contract(JSON.parse(compiledDposts.interface))
.deploy({ data: compiledDposts.bytecode, arguments: [] })
.send({ from: accounts[0], gas: gasCost });
console.log('faddress', dposts.options.address);
dposts.setProvider(provider);
});
describe('Dposts', () => {
it('deploys a contract', () => {
console.log('daccounts', accounts);
assert.ok(dposts.options.address);
});
it('starts with no initial calculated tax', async () => {
const lastCurrentTaxCall = await dposts.methods.lastCurrentTax.call();
const lct = await lastCurrentTaxCall.call();
console.log('lct', lct);
assert.equal(lct, 0);
});
describe('Board', () => {
before(async () => {
await lastingDposts.methods
.createBoard(
web3.utils.utf8ToHex('foo'),
true,
true,
true,
true,
true,
true,
true,
10
)
.send({ from: accounts[1], gas: 6000000 });
firstBoardAddress = await lastingDposts.methods.boards(0).call();
firstBoard = await new web3.eth.Contract(
JSON.parse(compiledBoard.interface),
firstBoardAddress
);
});
it('allows board creation', () => {
console.log('fba', firstBoardAddress);
assert.ok(firstBoardAddress);
});
it('gets a working version of a board', () => {
assert.ok(firstBoard);
});
describe('taxed posting', () => {
let firstPost;
before(async () => {
let gasCost = await firstBoard.methods
.createPost(
web3.utils.fromAscii('bar1'),
'bar2',
web3.utils.fromAscii('www.google.com'),
10,
910000000000000
)
.estimateGas({ from: accounts[2], value: 910000000000020 });
await firstBoard.methods
.createPost(
web3.utils.fromAscii('bar1'),
'bar2',
web3.utils.fromAscii('www.google.com'),
10,
910000000000000
)
.send({ from: accounts[2], gas: gasCost, value: 910000000000020 });
firstPost = await firstBoard.methods.posts(0).call();
});
it('allows posting', async () => {
assert.equal(firstPost.body, 'bar2');
});
it('reduces tax based on tax pot', async () => {
const lastCurrentTax = await lastingDposts.methods
.lastCurrentTax()
.call();
assert.ok(lastCurrentTax < 910000000000000);
});
it('disallows posting if value is insufficient', async () => {
const postsStartingLength = await firstBoard.methods
.getPostsLength()
.call();
try {
await firstBoard.methods
.createPost(
'insufficient',
'bar2',
'www.google.com',
10,
910000000000000
)
.send({ from: accounts[2], gas: 6000000, value: 10 });
} catch (e) {
// do nothing
}
const postsEndingLength = await firstBoard.methods
.getPostsLength()
.call();
assert.equal(postsStartingLength, postsEndingLength);
});
describe('commenting', () => {
before(async () => {
let gasCost = await firstBoard.methods
.createComment(0, 'commentbody1')
.estimateGas({ from: accounts[3] });
await firstBoard.methods
.createComment(0, 'commentbody1')
.send({ from: accounts[3], gas: gasCost });
});
it('allows commenting', async () => {
let commentsLength = await firstBoard.methods
.getPostCommentsLength(0)
.call();
assert.equal(commentsLength, 1);
});
it('allows for comment retrieval', async () => {
let firstCommentIndex = await firstBoard.methods
.getCommentIndexByPostCommentsIndex(0, 0)
.call();
assert.equal(firstCommentIndex, 0);
let firstComment = await firstBoard.methods
.comments(firstCommentIndex)
.call();
assert.equal(firstComment.body, 'commentbody1');
});
});
});
});
});
import React, { Component } from 'react';
import web3 from '../../ethereum/web3';
const compiledBoard = require('../../ethereum/build/Board.json');
import Link from 'next/link';
import { Row, Col, Button, Alert, Table, Card } from 'reactstrap';
class BoardCard extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
owner: '',
name: '',
boardUrl: '',
urlStr: '',
owner: '',
postsLength: 0,
destroyingBoard: false,
boardDestroyed: false,
loadingErrorMessage: '',
deletionErrorMessage: ''
};
this.destroyBoard = this.destroyBoard.bind(this);
}
async componentDidMount() {
const board = await new web3.eth.Contract(
JSON.parse(compiledBoard.interface),
this.props.boardId
);
let name;
try {
// need to block for one accessor first to make sure the board wasn't selfdestructed
name = await board.methods.name().call();
} catch (err) {
this.setState({ loadingErrorMessage: err.message, loading: false });
return;
}
let postPromises = [];
postPromises.push(board.methods.url().call());
postPromises.push(board.methods.owner().call());
postPromises.push(board.methods.getPostsLength().call());
const postRetrievedValues = await Promise.all(postPromises);
let urlStr = web3.utils
.toAscii(postRetrievedValues[0])
.replace(/\u0000/g, '');
this.setState({
name: name,
boardUrl: postRetrievedValues[0],
owner: postRetrievedValues[1],
urlStr: urlStr,
postsLength: postRetrievedValues[2],
loading: false
});
}
async destroyBoard() {
if (!confirm('Are you sure???! This absolutely cannot be undone.')) {
return;
}
try {
this.setState({
deletionErrorMessage: '',
boardDestroyFailed: false,
destroyingBoard: true
});
const board = await new web3.eth.Contract(
JSON.parse(compiledBoard.interface),
this.props.boardId
);
let gasCost = await board.methods
.terminate()
.send({ from: this.props.accounts[0] });
let terminated = await board.methods
.terminate()
.send({ from: this.props.accounts[0], gas: gasCost });
this.setState({ boardDestroyed: true });
console.log('terminated', terminated);
} catch (err) {
this.setState({
boardDestroyFailed: true,
destroyingBoard: false,
deletionErrorMessage: err.message
});
}
}
render() {
const loading = this.state.loading;
const published = !!this.state.urlStr;
const currentUserCreated =
this.props.accounts.length && this.props.accounts[0] === this.state.owner;
let className = 'board-card';
if (!published) {
className += ' unpublished';
}
return (
<Card
className={className}
hidden={
this.state.loadingErrorMessage ||
(!this.state.loading && !this.state.urlStr && !currentUserCreated)
? 'hidden'
: ''
}
>
<Col className="board-inner-content" xs={12}>
<h2>{this.state.name && web3.utils.toAscii(this.state.name)}</h2>
{!published && (
<p className="unpublished-explainer">
Board currently unpublished. Add a board URL to publish. See{' '}
<Link href="/boardhosting" prefetch>
<a>Board Hosting</a>
</Link>{' '}
for more info.
</p>
)}
<Table borderless="true">
<tbody>
<tr>
<th scope="row">Link</th>
<td>
{this.state.urlStr ? (
<a
target="_blank"
rel="noopener noreferrer"
href={this.state.urlStr}
>
{this.state.urlStr}
</a>
) : (
'unpublished'
)}
</td>
</tr>
<tr>
<th scope="row">Contract Address</th>
<td>
<a
target="_blank"
rel="noopener noreferrer"
href={'https://etherscan.io/address/' + this.props.boardId}
>
{this.props.boardId}
</a>
</td>
</tr>
<tr>
<th scope="row">Owner</th>
<td>
<a
target="_blank"
rel="noopener noreferrer"
href={'https://etherscan.io/address/' + this.state.owner}
>
{this.state.owner}
</a>
</td>
</tr>
<tr>
<th scope="row">Post Count</th>
<td>{this.state.postsLength}</td>
</tr>
</tbody>
</Table>
{this.state.deletionErrorMessage ? (
<Alert color="danger">
{this.state.deletionErrorMessage.substring(0, 120)}
</Alert>
) : (
''
)}
{loading ? (
<div>loading...</div>
) : (
<div>
{this.state.boardDestroyed ? (
<p style={{ color: 'red' }}>Board Destroyed</p>
) : (
''
)}
{currentUserCreated ? (
<div className="destroy-button-holder">
<p>You are the owner of this board.</p>
<Button
color="danger"
disabled={this.state.destroyingBoard}
onClick={this.destroyBoard}
>
{this.state.destroyingBoard ? 'Destroying...' : 'Destroy'}
</Button>
</div>
) : null}
{this.state.destroyingBoard ? (
<p style={{ color: 'red' }}>This will take a few minutes...</p>
) : (
''
)}
</div>
)}
</Col>
</Card>
);
}
}
export default BoardCard;
import React from 'react';
import {
Col,
Button,
Form,
FormGroup,
Label,
Input,
FormText,
Tooltip,
Alert
} from 'reactstrap';
import Link from 'next/link';
import CheckboxRow from './CheckboxRow';
import dposts from '../../ethereum/dposts';
import { ENGINE_METHOD_DIGESTS } from 'constants';
import web3 from '../../ethereum/web3';
/*
board signature is:
bytes32 name,
bool allowPosterEditing,
bool allowOwnerModeration,
bool allowBoardTipping,
bool allowTaxation,
bool allowPostTipping,
bool allowComments,
bool allowCommentTipping,
uint fee
*/
class BoardCreation extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
allowPosterEditing: false,
allowOwnerModeration: true,
allowBoardTipping: true,
allowTaxation: true,
allowPostTipping: true,
allowComments: false,
allowCommentTipping: false,
fee: 0,
submitting: false,
errorMessage: '',
accounts: [],
board: {}
};
this.handleInputChange = this.handleInputChange.bind(this);
this.submitBoard = this.submitBoard.bind(this);
}
async submitBoard() {
this.setState({
submitting: true,
errorMessage: ''
});
try {
const accounts = await web3.eth.getAccounts();
console.log('got accounts3', accounts);
this.setState({ accounts });
let gasCost = await dposts.methods
.createBoard(
web3.utils.asciiToHex(this.state.name),
this.state.allowPosterEditing,
this.state.allowOwnerModeration,
this.state.allowBoardTipping,
this.state.allowTaxation,
this.state.allowPostTipping,
this.state.allowComments,
this.state.allowCommentTipping,
this.state.fee
)
.estimateGas({ from: accounts[0] });
let board = await dposts.methods
.createBoard(
web3.utils.asciiToHex(this.state.name),
this.state.allowPosterEditing,
this.state.allowOwnerModeration,
this.state.allowBoardTipping,
this.state.allowTaxation,
this.state.allowPostTipping,
this.state.allowComments,
this.state.allowCommentTipping,
this.state.fee
)
.send({ gas: gasCost, from: accounts[0] });
this.setState({ submitting: false, board: board });
console.log('board made', board);
} catch (err) {
console.log('error yo', err);
this.setState({ errorMessage: err.message, submitting: false });
}
}
handleInputChange(event) {
const target = event.target;
let value;
switch (target.type) {
case 'checkbox':
value = target.checked;
break;
case 'number':
if (target.value) {
value = parseFloat(target.value);
}
break;
default:
value = target.value;
break;