Track proposal contributions (#219)
* BE proposal contribution tracking * FE proposal contribution tracking * validate contributions * make sure we catch errors in the 'confirmation' listener * remove console.log * lowercase from address compare * remove validate_contribution_tx from post_proposal_contribution
This commit is contained in:
parent
a95a8ff080
commit
d367e6e474
|
@ -45,6 +45,33 @@ class ProposalUpdate(db.Model):
|
||||||
self.date_created = datetime.datetime.now()
|
self.date_created = datetime.datetime.now()
|
||||||
|
|
||||||
|
|
||||||
|
class ProposalContribution(db.Model):
|
||||||
|
__tablename__ = "proposal_contribution"
|
||||||
|
|
||||||
|
tx_id = db.Column(db.String(255), primary_key=True)
|
||||||
|
date_created = db.Column(db.DateTime, nullable=False)
|
||||||
|
|
||||||
|
proposal_id = db.Column(db.Integer, db.ForeignKey("proposal.id"), nullable=False)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True)
|
||||||
|
from_address = db.Column(db.String(255), nullable=False)
|
||||||
|
amount = db.Column(db.String(255), nullable=False) # in eth
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
tx_id: str,
|
||||||
|
proposal_id: int,
|
||||||
|
user_id: int,
|
||||||
|
from_address: str,
|
||||||
|
amount: str
|
||||||
|
):
|
||||||
|
self.tx_id = tx_id
|
||||||
|
self.proposal_id = proposal_id
|
||||||
|
self.user_id = user_id
|
||||||
|
self.from_address = from_address
|
||||||
|
self.amount = amount
|
||||||
|
self.date_created = datetime.datetime.now()
|
||||||
|
|
||||||
|
|
||||||
class Proposal(db.Model):
|
class Proposal(db.Model):
|
||||||
__tablename__ = "proposal"
|
__tablename__ = "proposal"
|
||||||
|
|
||||||
|
@ -60,6 +87,7 @@ class Proposal(db.Model):
|
||||||
team = db.relationship("User", secondary=proposal_team)
|
team = db.relationship("User", secondary=proposal_team)
|
||||||
comments = db.relationship(Comment, backref="proposal", lazy=True)
|
comments = db.relationship(Comment, backref="proposal", lazy=True)
|
||||||
updates = db.relationship(ProposalUpdate, backref="proposal", lazy=True)
|
updates = db.relationship(ProposalUpdate, backref="proposal", lazy=True)
|
||||||
|
contributions = db.relationship(ProposalContribution, backref="proposal", lazy=True)
|
||||||
milestones = db.relationship("Milestone", backref="proposal", lazy=True)
|
milestones = db.relationship("Milestone", backref="proposal", lazy=True)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -110,6 +138,7 @@ class ProposalSchema(ma.Schema):
|
||||||
"body",
|
"body",
|
||||||
"comments",
|
"comments",
|
||||||
"updates",
|
"updates",
|
||||||
|
"contributions",
|
||||||
"milestones",
|
"milestones",
|
||||||
"category",
|
"category",
|
||||||
"team"
|
"team"
|
||||||
|
@ -121,6 +150,7 @@ class ProposalSchema(ma.Schema):
|
||||||
|
|
||||||
comments = ma.Nested("CommentSchema", many=True)
|
comments = ma.Nested("CommentSchema", many=True)
|
||||||
updates = ma.Nested("ProposalUpdateSchema", many=True)
|
updates = ma.Nested("ProposalUpdateSchema", many=True)
|
||||||
|
contributions = ma.Nested("ProposalContributionSchema", many=True)
|
||||||
team = ma.Nested("UserSchema", many=True)
|
team = ma.Nested("UserSchema", many=True)
|
||||||
milestones = ma.Nested("MilestoneSchema", many=True)
|
milestones = ma.Nested("MilestoneSchema", many=True)
|
||||||
|
|
||||||
|
@ -166,3 +196,30 @@ class ProposalUpdateSchema(ma.Schema):
|
||||||
|
|
||||||
proposal_update_schema = ProposalUpdateSchema()
|
proposal_update_schema = ProposalUpdateSchema()
|
||||||
proposals_update_schema = ProposalUpdateSchema(many=True)
|
proposals_update_schema = ProposalUpdateSchema(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ProposalContributionSchema(ma.Schema):
|
||||||
|
class Meta:
|
||||||
|
model = ProposalContribution
|
||||||
|
# Fields to expose
|
||||||
|
fields = (
|
||||||
|
"id",
|
||||||
|
"tx_id",
|
||||||
|
"proposal_id",
|
||||||
|
"user_id",
|
||||||
|
"from_address",
|
||||||
|
"amount",
|
||||||
|
"date_created",
|
||||||
|
)
|
||||||
|
id = ma.Method("get_id")
|
||||||
|
date_created = ma.Method("get_date_created")
|
||||||
|
|
||||||
|
def get_id(self, obj):
|
||||||
|
return obj.tx_id
|
||||||
|
|
||||||
|
def get_date_created(self, obj):
|
||||||
|
return dt_to_unix(obj.date_created)
|
||||||
|
|
||||||
|
|
||||||
|
proposal_contribution_schema = ProposalContributionSchema()
|
||||||
|
proposals_contribution_schema = ProposalContributionSchema(many=True)
|
||||||
|
|
|
@ -8,8 +8,17 @@ from grant.comment.models import Comment, comment_schema
|
||||||
from grant.milestone.models import Milestone
|
from grant.milestone.models import Milestone
|
||||||
from grant.user.models import User, SocialMedia, Avatar
|
from grant.user.models import User, SocialMedia, Avatar
|
||||||
from grant.utils.auth import requires_sm, requires_team_member_auth
|
from grant.utils.auth import requires_sm, requires_team_member_auth
|
||||||
from grant.web3.proposal import read_proposal
|
from grant.web3.proposal import read_proposal, validate_contribution_tx
|
||||||
from .models import Proposal, proposals_schema, proposal_schema, ProposalUpdate, proposal_update_schema, db
|
from .models import(
|
||||||
|
Proposal,
|
||||||
|
proposals_schema,
|
||||||
|
proposal_schema,
|
||||||
|
ProposalUpdate,
|
||||||
|
proposal_update_schema,
|
||||||
|
ProposalContribution,
|
||||||
|
proposal_contribution_schema,
|
||||||
|
db
|
||||||
|
)
|
||||||
|
|
||||||
blueprint = Blueprint("proposal", __name__, url_prefix="/api/v1/proposals")
|
blueprint = Blueprint("proposal", __name__, url_prefix="/api/v1/proposals")
|
||||||
|
|
||||||
|
@ -209,3 +218,52 @@ def post_proposal_update(proposal_id, title, content):
|
||||||
|
|
||||||
dumped_update = proposal_update_schema.dump(update)
|
dumped_update = proposal_update_schema.dump(update)
|
||||||
return dumped_update, 201
|
return dumped_update, 201
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/<proposal_id>/contributions", methods=["GET"])
|
||||||
|
@endpoint.api()
|
||||||
|
def get_proposal_contributions(proposal_id):
|
||||||
|
proposal = Proposal.query.filter_by(id=proposal_id).first()
|
||||||
|
if proposal:
|
||||||
|
dumped_proposal = proposal_schema.dump(proposal)
|
||||||
|
return dumped_proposal["contributions"]
|
||||||
|
else:
|
||||||
|
return {"message": "No proposal matching id"}, 404
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/<proposal_id>/contributions/<contribution_id>", methods=["GET"])
|
||||||
|
@endpoint.api()
|
||||||
|
def get_proposal_contribution(proposal_id, contribution_id):
|
||||||
|
proposal = Proposal.query.filter_by(id=proposal_id).first()
|
||||||
|
if proposal:
|
||||||
|
contribution = ProposalContribution.query.filter_by(tx_id=contribution_id).first()
|
||||||
|
if contribution:
|
||||||
|
return proposal_contribution_schema.dump(contribution)
|
||||||
|
else:
|
||||||
|
return {"message": "No contribution matching id"}
|
||||||
|
else:
|
||||||
|
return {"message": "No proposal matching id"}, 404
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/<proposal_id>/contributions", methods=["POST"])
|
||||||
|
@requires_sm
|
||||||
|
@endpoint.api(
|
||||||
|
parameter('txId', type=str, required=True),
|
||||||
|
parameter('fromAddress', type=str, required=True),
|
||||||
|
parameter('amount', type=str, required=True)
|
||||||
|
)
|
||||||
|
def post_proposal_contribution(proposal_id, tx_id, from_address, amount):
|
||||||
|
proposal = Proposal.query.filter_by(id=proposal_id).first()
|
||||||
|
if proposal:
|
||||||
|
contribution = ProposalContribution(
|
||||||
|
tx_id=tx_id,
|
||||||
|
proposal_id=proposal_id,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
from_address=from_address,
|
||||||
|
amount=amount
|
||||||
|
)
|
||||||
|
db.session.add(contribution)
|
||||||
|
db.session.commit()
|
||||||
|
dumped_contribution = proposal_contribution_schema.dump(contribution)
|
||||||
|
return dumped_contribution, 201
|
||||||
|
return {"message": "No proposal matching id"}, 404
|
||||||
|
|
|
@ -136,3 +136,14 @@ def read_proposal(address):
|
||||||
crowd_fund[k] = str(crowd_fund[k])
|
crowd_fund[k] = str(crowd_fund[k])
|
||||||
|
|
||||||
return crowd_fund
|
return crowd_fund
|
||||||
|
|
||||||
|
|
||||||
|
def validate_contribution_tx(tx_id, from_address, to_address, amount):
|
||||||
|
amount_wei = current_web3.toWei(amount, 'ether')
|
||||||
|
tx = current_web3.eth.getTransaction(tx_id)
|
||||||
|
if tx:
|
||||||
|
if from_address.lower() == tx.get("from").lower() and \
|
||||||
|
to_address == tx.get("to") and \
|
||||||
|
amount_wei == tx.get("value"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 1d06a5e43324
|
||||||
|
Revises: 312db8611967
|
||||||
|
Create Date: 2018-11-17 11:07:40.413141
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '1d06a5e43324'
|
||||||
|
down_revision = '312db8611967'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('proposal_contribution',
|
||||||
|
sa.Column('tx_id', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('date_created', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('proposal_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('from_address', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('amount', sa.String(length=255), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('tx_id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('proposal_contribution')
|
||||||
|
# ### end Alembic commands ###
|
|
@ -1,4 +1,5 @@
|
||||||
import json
|
import json
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
from grant.proposal.models import Proposal
|
from grant.proposal.models import Proposal
|
||||||
from grant.user.models import SocialMedia, Avatar
|
from grant.user.models import SocialMedia, Avatar
|
||||||
|
@ -71,3 +72,110 @@ class TestAPI(BaseUserConfig):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(proposal_res2.status_code, 409)
|
self.assertEqual(proposal_res2.status_code, 409)
|
||||||
|
|
||||||
|
@patch('grant.proposal.views.validate_contribution_tx', return_value=True)
|
||||||
|
def test_create_proposal_contribution(self, mock_validate_contribution_tx):
|
||||||
|
proposal_res = self.app.post(
|
||||||
|
"/api/v1/proposals/",
|
||||||
|
data=json.dumps(test_proposal),
|
||||||
|
headers=self.headers,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
proposal_json = proposal_res.json
|
||||||
|
proposal_id = proposal_json["proposalId"]
|
||||||
|
|
||||||
|
contribution = {
|
||||||
|
"txId": "0x12345",
|
||||||
|
"fromAddress": "0x23456",
|
||||||
|
"amount": "1.2345"
|
||||||
|
}
|
||||||
|
|
||||||
|
contribution_res = self.app.post(
|
||||||
|
"/api/v1/proposals/{}/contributions".format(proposal_id),
|
||||||
|
data=json.dumps(contribution),
|
||||||
|
headers=self.headers,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
res = contribution_res.json
|
||||||
|
exp = contribution
|
||||||
|
|
||||||
|
def eq(k):
|
||||||
|
self.assertEqual(exp[k], res[k])
|
||||||
|
eq("txId")
|
||||||
|
eq("fromAddress")
|
||||||
|
eq("amount")
|
||||||
|
self.assertEqual(proposal_id, res["proposalId"])
|
||||||
|
|
||||||
|
@patch('grant.proposal.views.validate_contribution_tx', return_value=True)
|
||||||
|
def test_get_proposal_contribution(self, mock_validate_contribution_tx):
|
||||||
|
proposal_res = self.app.post(
|
||||||
|
"/api/v1/proposals/",
|
||||||
|
data=json.dumps(test_proposal),
|
||||||
|
headers=self.headers,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
proposal_json = proposal_res.json
|
||||||
|
proposal_id = proposal_json["proposalId"]
|
||||||
|
|
||||||
|
contribution = {
|
||||||
|
"txId": "0x12345",
|
||||||
|
"fromAddress": "0x23456",
|
||||||
|
"amount": "1.2345"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.post(
|
||||||
|
"/api/v1/proposals/{}/contributions".format(proposal_id),
|
||||||
|
data=json.dumps(contribution),
|
||||||
|
headers=self.headers,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
|
||||||
|
contribution_res = self.app.get(
|
||||||
|
"/api/v1/proposals/{0}/contributions/{1}".format(proposal_id, contribution["txId"])
|
||||||
|
)
|
||||||
|
res = contribution_res.json
|
||||||
|
exp = contribution
|
||||||
|
|
||||||
|
def eq(k):
|
||||||
|
self.assertEqual(exp[k], res[k])
|
||||||
|
eq("txId")
|
||||||
|
eq("fromAddress")
|
||||||
|
eq("amount")
|
||||||
|
self.assertEqual(proposal_id, res["proposalId"])
|
||||||
|
|
||||||
|
@patch('grant.proposal.views.validate_contribution_tx', return_value=True)
|
||||||
|
def test_get_proposal_contributions(self, mock_validate_contribution_tx):
|
||||||
|
proposal_res = self.app.post(
|
||||||
|
"/api/v1/proposals/",
|
||||||
|
data=json.dumps(test_proposal),
|
||||||
|
headers=self.headers,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
proposal_json = proposal_res.json
|
||||||
|
proposal_id = proposal_json["proposalId"]
|
||||||
|
|
||||||
|
contribution = {
|
||||||
|
"txId": "0x12345",
|
||||||
|
"fromAddress": "0x23456",
|
||||||
|
"amount": "1.2345"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.post(
|
||||||
|
"/api/v1/proposals/{}/contributions".format(proposal_id),
|
||||||
|
data=json.dumps(contribution),
|
||||||
|
headers=self.headers,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
|
||||||
|
contributions_res = self.app.get(
|
||||||
|
"/api/v1/proposals/{0}/contributions".format(proposal_id)
|
||||||
|
)
|
||||||
|
res = contributions_res.json[0]
|
||||||
|
exp = contribution
|
||||||
|
|
||||||
|
def eq(k):
|
||||||
|
self.assertEqual(exp[k], res[k])
|
||||||
|
eq("txId")
|
||||||
|
eq("fromAddress")
|
||||||
|
eq("amount")
|
||||||
|
self.assertEqual(proposal_id, res["proposalId"])
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import axios from './axios';
|
import axios from './axios';
|
||||||
import { Proposal, TeamMember, Update } from 'types';
|
import { Proposal, TeamMember, Update, Contribution } from 'types';
|
||||||
import {
|
import {
|
||||||
formatProposalFromGet,
|
formatProposalFromGet,
|
||||||
formatTeamMemberForPost,
|
formatTeamMemberForPost,
|
||||||
|
@ -111,3 +111,16 @@ export function postProposalUpdate(
|
||||||
content,
|
content,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function postProposalContribution(
|
||||||
|
proposalId: number,
|
||||||
|
txId: string,
|
||||||
|
fromAddress: string,
|
||||||
|
amount: string,
|
||||||
|
): Promise<{ data: Contribution }> {
|
||||||
|
return axios.post(`/api/v1/proposals/${proposalId}/contributions`, {
|
||||||
|
txId,
|
||||||
|
fromAddress,
|
||||||
|
amount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
getProposal,
|
getProposal,
|
||||||
getProposalComments,
|
getProposalComments,
|
||||||
getProposalUpdates,
|
getProposalUpdates,
|
||||||
|
postProposalContribution as apiPostProposalContribution,
|
||||||
} from 'api/api';
|
} from 'api/api';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { ProposalWithCrowdFund, Comment } from 'types';
|
import { ProposalWithCrowdFund, Comment } from 'types';
|
||||||
|
@ -108,3 +109,17 @@ export function postProposalComment(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function postProposalContribution(
|
||||||
|
proposalId: number,
|
||||||
|
txId: string,
|
||||||
|
account: string,
|
||||||
|
amount: string,
|
||||||
|
) {
|
||||||
|
return async (dispatch: Dispatch<any>) => {
|
||||||
|
await dispatch({
|
||||||
|
type: types.POST_PROPOSAL_CONTRIBUTION,
|
||||||
|
payload: apiPostProposalContribution(proposalId, txId, account, amount),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
enum clockTypes {
|
enum proposalTypes {
|
||||||
PROPOSALS_DATA = 'PROPOSALS_DATA',
|
PROPOSALS_DATA = 'PROPOSALS_DATA',
|
||||||
PROPOSALS_DATA_FULFILLED = 'PROPOSALS_DATA_FULFILLED',
|
PROPOSALS_DATA_FULFILLED = 'PROPOSALS_DATA_FULFILLED',
|
||||||
PROPOSALS_DATA_REJECTED = 'PROPOSALS_DATA_REJECTED',
|
PROPOSALS_DATA_REJECTED = 'PROPOSALS_DATA_REJECTED',
|
||||||
|
@ -23,6 +23,8 @@ enum clockTypes {
|
||||||
POST_PROPOSAL_COMMENT_FULFILLED = 'POST_PROPOSAL_COMMENT_FULFILLED',
|
POST_PROPOSAL_COMMENT_FULFILLED = 'POST_PROPOSAL_COMMENT_FULFILLED',
|
||||||
POST_PROPOSAL_COMMENT_REJECTED = 'POST_PROPOSAL_COMMENT_REJECTED',
|
POST_PROPOSAL_COMMENT_REJECTED = 'POST_PROPOSAL_COMMENT_REJECTED',
|
||||||
POST_PROPOSAL_COMMENT_PENDING = 'POST_PROPOSAL_COMMENT_PENDING',
|
POST_PROPOSAL_COMMENT_PENDING = 'POST_PROPOSAL_COMMENT_PENDING',
|
||||||
|
|
||||||
|
POST_PROPOSAL_CONTRIBUTION = 'POST_PROPOSAL_CONTRIBUTION',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default clockTypes;
|
export default proposalTypes;
|
||||||
|
|
|
@ -5,7 +5,11 @@ import { postProposal } from 'api/api';
|
||||||
import getContract, { WrongNetworkError } from 'lib/getContract';
|
import getContract, { WrongNetworkError } from 'lib/getContract';
|
||||||
import { sleep } from 'utils/helpers';
|
import { sleep } from 'utils/helpers';
|
||||||
import { web3ErrorToString } from 'utils/web3';
|
import { web3ErrorToString } from 'utils/web3';
|
||||||
import { fetchProposal, fetchProposals } from 'modules/proposals/actions';
|
import {
|
||||||
|
fetchProposal,
|
||||||
|
fetchProposals,
|
||||||
|
postProposalContribution,
|
||||||
|
} from 'modules/proposals/actions';
|
||||||
import { PROPOSAL_CATEGORY } from 'api/constants';
|
import { PROPOSAL_CATEGORY } from 'api/constants';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { Wei } from 'utils/units';
|
import { Wei } from 'utils/units';
|
||||||
|
@ -278,6 +282,14 @@ export function fundCrowdFund(proposal: ProposalWithCrowdFund, value: number | s
|
||||||
const { proposalAddress, proposalId } = proposal;
|
const { proposalAddress, proposalId } = proposal;
|
||||||
const crowdFundContract = await getCrowdFundContract(web3, proposalAddress);
|
const crowdFundContract = await getCrowdFundContract(web3, proposalAddress);
|
||||||
|
|
||||||
|
const handleErr = (err: Error) => {
|
||||||
|
dispatch({
|
||||||
|
type: types.SEND_REJECTED,
|
||||||
|
payload: err.message || err.toString(),
|
||||||
|
error: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!web3) {
|
if (!web3) {
|
||||||
throw new Error('No web3 instance available');
|
throw new Error('No web3 instance available');
|
||||||
|
@ -285,20 +297,27 @@ export function fundCrowdFund(proposal: ProposalWithCrowdFund, value: number | s
|
||||||
await crowdFundContract.methods
|
await crowdFundContract.methods
|
||||||
.contribute()
|
.contribute()
|
||||||
.send({ from: account, value: web3.utils.toWei(String(value), 'ether') })
|
.send({ from: account, value: web3.utils.toWei(String(value), 'ether') })
|
||||||
.once('confirmation', async () => {
|
.once('confirmation', async (_: number, receipt: any) => {
|
||||||
await sleep(5000);
|
try {
|
||||||
await dispatch(fetchProposal(proposalId));
|
await sleep(5000);
|
||||||
dispatch({
|
await dispatch(
|
||||||
type: types.SEND_FULFILLED,
|
postProposalContribution(
|
||||||
});
|
proposalId,
|
||||||
|
receipt.transactionHash,
|
||||||
|
account,
|
||||||
|
String(value),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await dispatch(fetchProposal(proposalId));
|
||||||
|
dispatch({
|
||||||
|
type: types.SEND_FULFILLED,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
handleErr(err);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
handleErr(err);
|
||||||
dispatch({
|
|
||||||
type: types.SEND_REJECTED,
|
|
||||||
payload: err.message || err.toString(),
|
|
||||||
error: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
export interface Contribution {
|
||||||
|
id: string;
|
||||||
|
txId: string;
|
||||||
|
proposalId: number;
|
||||||
|
userId: number;
|
||||||
|
fromAddress: string;
|
||||||
|
amount: string;
|
||||||
|
dateCreated: number;
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ export * from './user';
|
||||||
export * from './social';
|
export * from './social';
|
||||||
export * from './create';
|
export * from './create';
|
||||||
export * from './comment';
|
export * from './comment';
|
||||||
|
export * from './contribution';
|
||||||
export * from './milestone';
|
export * from './milestone';
|
||||||
export * from './update';
|
export * from './update';
|
||||||
export * from './proposal';
|
export * from './proposal';
|
||||||
|
|
Loading…
Reference in New Issue