add multi-author support to frontend
This commit is contained in:
parent
ece57c9706
commit
626905bad6
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"localhost": {
|
||||
"DAO": "0x8d914D38dD301FC4606f5aa9fEcF8A76389020d3",
|
||||
"Work1": "0xfe58B9EB03F75A603de1B286584f5E9532ab8fB5",
|
||||
"Onboarding": "0x1d63FDe5B461106729fE1e5e38A02fc68C518Af5",
|
||||
"Proposals": "0x050C420Cc4995B41217Eba1B54B82Fd5687e9139"
|
||||
"DAO": "0xD60A1c64B96a133587A75C2771690072F238a549",
|
||||
"Work1": "0xCF3f16D151052FA7b99a71E79EC3b0e6C793aa0b",
|
||||
"Onboarding": "0xE148e864A646B8bFc95dcc9acd3dBcB52704EE60",
|
||||
"Proposals": "0x981234BBBC1ec93200F5BB3a65e2F9711A6109aa"
|
||||
},
|
||||
"sepolia": {
|
||||
"DAO": "0x241514DC94568e98222fBE66662b054b545A61AE",
|
|
@ -82,9 +82,10 @@ function App() {
|
|||
}, [DAORef, account]);
|
||||
|
||||
const fetchPost = useCallback(async (postId) => {
|
||||
const p = await DAORef.current.methods.posts(postId).call();
|
||||
dispatchPost({ type: 'updateById', item: p });
|
||||
return p;
|
||||
const post = await DAORef.current.methods.posts(postId).call();
|
||||
post.authors = await DAORef.current.methods.getPostAuthors(postId).call();
|
||||
dispatchPost({ type: 'updateById', item: post });
|
||||
return post;
|
||||
}, [DAORef, dispatchPost]);
|
||||
|
||||
const fetchPostId = useCallback(async (postIndex) => {
|
||||
|
@ -186,14 +187,14 @@ function App() {
|
|||
setWork1(Work1Contract);
|
||||
setOnboarding(OnboardingContract);
|
||||
|
||||
const fetchReputationInterval = setInterval(() => {
|
||||
console.log('reputation', reputation);
|
||||
if (reputation !== undefined) {
|
||||
clearInterval(fetchReputationInterval);
|
||||
return;
|
||||
}
|
||||
fetchReputation();
|
||||
}, 1000);
|
||||
// const fetchReputationInterval = setInterval(() => {
|
||||
// // console.log('reputation', reputation);
|
||||
// if (reputation !== undefined) {
|
||||
// clearInterval(fetchReputationInterval);
|
||||
// return;
|
||||
// }
|
||||
// fetchReputation();
|
||||
// }, 1000);
|
||||
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
/* --------------------------- BEGIN EVENT HANDLERS ------------------------------- */
|
||||
|
@ -414,7 +415,7 @@ function App() {
|
|||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Author</th>
|
||||
<th>Authors</th>
|
||||
<th>Sender</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
|
@ -423,7 +424,18 @@ function App() {
|
|||
{posts.filter((x) => !!x).map((post) => (
|
||||
<tr key={post.id}>
|
||||
<td>{post.id.toString()}</td>
|
||||
<td>{getAddressName(chainId, post.author)}</td>
|
||||
<td>
|
||||
<Stack>
|
||||
{post.authors.map(({ authorAddress, weightPercent }) => (
|
||||
<div key={authorAddress}>
|
||||
{getAddressName(chainId, authorAddress)}
|
||||
{' '}
|
||||
{weightPercent.toString()}
|
||||
%
|
||||
</div>
|
||||
))}
|
||||
</Stack>
|
||||
</td>
|
||||
<td>{getAddressName(chainId, post.sender)}</td>
|
||||
<td>
|
||||
<Button onClick={() => handleShowViewPost(post)}>
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -19,7 +19,7 @@ function AddPostModal({
|
|||
|
||||
const handleSubmit = useCallback(async () => {
|
||||
// Upload content to API
|
||||
const post = new Post({ content });
|
||||
const post = new Post({ content, authors: [{ weightPercent: 100, authorAddress: account }] });
|
||||
// Include metamask signature
|
||||
await post.sign(provider, account);
|
||||
// Clear the input and hide the modal
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import Button from 'react-bootstrap/Button';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
import Stack from 'react-bootstrap/Stack';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function ViewPostModal({
|
||||
show, setShow, title, post,
|
||||
}) {
|
||||
const handleClose = () => setShow(false);
|
||||
const { content, author, embeddedData } = post;
|
||||
const { content, authors, embeddedData } = post;
|
||||
|
||||
const embeddedDataJson = JSON.stringify(embeddedData, null, 2);
|
||||
|
||||
|
@ -19,9 +20,18 @@ function ViewPostModal({
|
|||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<h6>
|
||||
Author:
|
||||
{' '}
|
||||
{author}
|
||||
Authors:
|
||||
<Stack>
|
||||
{authors?.map(({ authorAddress, weightPercent }) => (
|
||||
<div key={authorAddress}>
|
||||
{authorAddress}
|
||||
{' '}
|
||||
{weightPercent.toString()}
|
||||
%
|
||||
</div>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
</h6>
|
||||
<hr />
|
||||
<p className="post-content">
|
||||
|
|
|
@ -9,25 +9,26 @@ window.Buffer = Buffer;
|
|||
|
||||
class Post {
|
||||
constructor({
|
||||
author, content, signature, hash, embeddedData,
|
||||
authors, content, signature, hash, embeddedData, citations,
|
||||
}) {
|
||||
this.author = author;
|
||||
this.authors = authors;
|
||||
this.content = content;
|
||||
this.signature = signature;
|
||||
this.hash = hash;
|
||||
this.embeddedData = embeddedData;
|
||||
this.citations = citations;
|
||||
}
|
||||
|
||||
// Read from API
|
||||
static async read(hash) {
|
||||
const {
|
||||
data: {
|
||||
content, author, signature, embeddedData,
|
||||
content, authors, signature, embeddedData, citations,
|
||||
},
|
||||
} = await axios.get(`/api/read/${hash}`);
|
||||
// Verify hash
|
||||
const derivedHash = objectHash({
|
||||
author, content, signature, embeddedData,
|
||||
authors, content, signature, embeddedData,
|
||||
});
|
||||
if (hash !== derivedHash) {
|
||||
throw new Error('Hash mismatch');
|
||||
|
@ -38,12 +39,12 @@ class Post {
|
|||
contentToVerify += `\n\n${JSON.stringify(embeddedData, null, 2)}`;
|
||||
}
|
||||
const recovered = recoverPersonalSignature({ data: contentToVerify, signature });
|
||||
|
||||
if (recovered !== author) {
|
||||
throw new Error('Author mismatch');
|
||||
const authorAddresses = authors.map((author) => author.authorAddress);
|
||||
if (!authorAddresses.includes(recovered)) {
|
||||
throw new Error('Signer is not among the authors');
|
||||
}
|
||||
return new Post({
|
||||
content, author, signature, hash, embeddedData,
|
||||
content, authors, signature, hash, embeddedData, citations,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -56,10 +57,13 @@ class Post {
|
|||
|
||||
// Include MetaMask signature
|
||||
async sign(web3Provider, account) {
|
||||
this.author = account;
|
||||
const author = this.authors?.find(({ authorAddress }) => authorAddress === account);
|
||||
if (!author) {
|
||||
throw new Error('Post must be signed by one of its authors');
|
||||
}
|
||||
let contentToSign = this.content;
|
||||
if (this.embeddedData && Object.entries(this.embeddedData).length) {
|
||||
contentToSign += `\n\n${JSON.stringify(this.embeddedData, null, 2)}`;
|
||||
contentToSign += `\n\nDATA\n${JSON.stringify(this.embeddedData, null, 2)}`;
|
||||
}
|
||||
const msg = `0x${Buffer.from(contentToSign, 'utf8').toString('hex')}`;
|
||||
this.signature = await web3Provider.request({
|
||||
|
@ -72,10 +76,11 @@ class Post {
|
|||
// Write to API
|
||||
async write() {
|
||||
const data = {
|
||||
author: this.author,
|
||||
authors: this.authors,
|
||||
content: this.content,
|
||||
signature: this.signature,
|
||||
embeddedData: this.embeddedData,
|
||||
citations: this.citations,
|
||||
};
|
||||
const { data: hash } = await axios.post('/api/write', data);
|
||||
this.hash = hash;
|
||||
|
@ -83,10 +88,7 @@ class Post {
|
|||
|
||||
// Upload hash to blockchain
|
||||
async publish(DAO, account) {
|
||||
await DAO.methods.addPost([{
|
||||
weightPercent: 100,
|
||||
authorAddress: account,
|
||||
}], this.hash, []).send({
|
||||
await DAO.methods.addPost(this.authors, this.hash, this.citations ?? []).send({
|
||||
from: account,
|
||||
gas: 1000000,
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import contractAddresses from '../contract-addresses.json';
|
||||
import contractAddresses from '../../contract-addresses.json';
|
||||
|
||||
const networks = {
|
||||
localhost: '0x539',
|
||||
|
|
Loading…
Reference in New Issue