Merge pull request #33 from dternyak/proposal-rework-amounts
Proposal rework amounts
This commit is contained in:
commit
a0d115a703
|
@ -236,6 +236,7 @@ class ProposalSchema(ma.Schema):
|
|||
"brief",
|
||||
"proposal_id",
|
||||
"target",
|
||||
"funded",
|
||||
"content",
|
||||
"comments",
|
||||
"updates",
|
||||
|
@ -250,6 +251,7 @@ class ProposalSchema(ma.Schema):
|
|||
|
||||
date_created = ma.Method("get_date_created")
|
||||
proposal_id = ma.Method("get_proposal_id")
|
||||
funded = ma.Method("get_funded")
|
||||
|
||||
comments = ma.Nested("CommentSchema", many=True)
|
||||
updates = ma.Nested("ProposalUpdateSchema", many=True)
|
||||
|
@ -264,6 +266,10 @@ class ProposalSchema(ma.Schema):
|
|||
def get_date_created(self, obj):
|
||||
return dt_to_unix(obj.date_created)
|
||||
|
||||
def get_funded(self, obj):
|
||||
# TODO: Add up all contributions and return that
|
||||
return "0"
|
||||
|
||||
|
||||
proposal_schema = ProposalSchema()
|
||||
proposals_schema = ProposalSchema(many=True)
|
||||
|
|
|
@ -34,7 +34,7 @@ export default class AddressInput extends React.Component<Props> {
|
|||
};
|
||||
|
||||
const inputProps = {
|
||||
placeholder: DONATION.ETH,
|
||||
placeholder: DONATION.ZCASH_SPROUT,
|
||||
prefix: value &&
|
||||
showIdenticon && (
|
||||
<Identicon className="AddressInput-input-identicon" address={value} />
|
||||
|
|
|
@ -111,7 +111,7 @@ export default class CreateFlowBasics extends React.Component<Props, State> {
|
|||
type="number"
|
||||
value={target}
|
||||
onChange={this.handleInputChange}
|
||||
addonAfter="ETH"
|
||||
addonAfter="ZEC"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
|
|
@ -40,7 +40,7 @@ export default class CreateFlowPayment extends React.Component<Props, State> {
|
|||
<Input
|
||||
size="large"
|
||||
name="payoutAddress"
|
||||
placeholder={DONATION.ETH}
|
||||
placeholder={DONATION.ZCASH_SPROUT}
|
||||
type="text"
|
||||
value={payoutAddress}
|
||||
onChange={this.handleInputChange}
|
||||
|
|
|
@ -64,7 +64,7 @@ class CreateReview extends React.Component<Props> {
|
|||
},
|
||||
{
|
||||
key: 'target',
|
||||
content: <div style={{ fontSize: '1.2rem' }}>{form.target} ETH</div>,
|
||||
content: <div style={{ fontSize: '1.2rem' }}>{form.target} ZEC</div>,
|
||||
error: errors.target,
|
||||
},
|
||||
],
|
||||
|
|
|
@ -21,9 +21,9 @@ export default class Profile extends React.Component<OwnProps> {
|
|||
</Link>
|
||||
<div className="ProfileProposal-brief">{brief}</div>
|
||||
<div className="ProfileProposal-raised">
|
||||
<UnitDisplay value={funded} symbol="ETH" displayShortBalance={4} />{' '}
|
||||
<UnitDisplay value={funded} symbol="ZEC" displayShortBalance={4} />{' '}
|
||||
<small>raised</small> of{' '}
|
||||
<UnitDisplay value={target} symbol="ETH" displayShortBalance={4} /> goal
|
||||
<UnitDisplay value={target} symbol="ZEC" displayShortBalance={4} /> goal
|
||||
</div>
|
||||
</div>
|
||||
<div className="ProfileProposal-block">
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import BN from 'bn.js';
|
||||
import { Spin, Form, Input, Button, Icon } from 'antd';
|
||||
import { Proposal } from 'types';
|
||||
import classnames from 'classnames';
|
||||
import { fromWei } from 'utils/units';
|
||||
import { fromZat } from 'utils/units';
|
||||
import { connect } from 'react-redux';
|
||||
import { compose } from 'recompose';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { withRouter } from 'react-router';
|
||||
import ShortAddress from 'components/ShortAddress';
|
||||
import UnitDisplay from 'components/UnitDisplay';
|
||||
import { getAmountError } from 'utils/validators';
|
||||
import { CATEGORY_UI } from 'api/constants';
|
||||
|
@ -51,8 +49,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
// TODO: Get values from proposal
|
||||
const target = new BN(0);
|
||||
const funded = new BN(0);
|
||||
const { target, funded } = this.props.proposal;
|
||||
const remainingTarget = target.sub(funded);
|
||||
const amount = parseFloat(value);
|
||||
let amountError = null;
|
||||
|
@ -60,8 +57,8 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
if (Number.isNaN(amount)) {
|
||||
// They're entering some garbage, they’ll work it out
|
||||
} else {
|
||||
const remainingEthNum = parseFloat(fromWei(remainingTarget, 'ether'));
|
||||
amountError = getAmountError(amount, remainingEthNum);
|
||||
const remainingTargetNum = parseFloat(fromZat(remainingTarget));
|
||||
amountError = getAmountError(amount, remainingTargetNum);
|
||||
}
|
||||
|
||||
this.setState({ amountToRaise: value, amountError });
|
||||
|
@ -80,22 +77,16 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
const amountFloat = parseFloat(amountToRaise) || 0;
|
||||
let content;
|
||||
if (proposal) {
|
||||
const { target, funded, percentFunded } = proposal;
|
||||
const isRaiseGoalReached = funded.gte(target);
|
||||
// TODO: Get values from proposal
|
||||
console.warn('TODO: Get real values from proposal for CampaignBlock');
|
||||
const isRaiseGoalReached = false;
|
||||
console.warn('TODO: Get deadline and isFrozen from proposal data');
|
||||
const deadline = 0;
|
||||
const isFrozen = false;
|
||||
const target = new BN(0);
|
||||
const funded = new BN(0);
|
||||
const percentFunded = 0;
|
||||
const beneficiary = 'z123';
|
||||
|
||||
const isFundingOver =
|
||||
isRaiseGoalReached ||
|
||||
deadline < Date.now() ||
|
||||
isFrozen;
|
||||
const isFundingOver = isRaiseGoalReached || deadline < Date.now() || isFrozen;
|
||||
const isDisabled = isFundingOver || !!amountError || !amountFloat || isPreview;
|
||||
const remainingEthNum = parseFloat(fromWei(target.sub(funded), 'ether'));
|
||||
const remainingTargetNum = parseFloat(fromZat(target.sub(funded)));
|
||||
|
||||
content = (
|
||||
<React.Fragment>
|
||||
|
@ -123,17 +114,10 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="ProposalCampaignBlock-info">
|
||||
<div className="ProposalCampaignBlock-info-label">Beneficiary</div>
|
||||
<div className="ProposalCampaignBlock-info-value">
|
||||
<ShortAddress address={beneficiary} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="ProposalCampaignBlock-info">
|
||||
<div className="ProposalCampaignBlock-info-label">Funding</div>
|
||||
<div className="ProposalCampaignBlock-info-value">
|
||||
<UnitDisplay value={funded} /> /{' '}
|
||||
<UnitDisplay value={target} symbol="ETH" />
|
||||
<UnitDisplay value={funded} /> / <UnitDisplay value={target} symbol="ZEC" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -175,7 +159,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
size="large"
|
||||
type="number"
|
||||
placeholder="0.5"
|
||||
addonAfter="ETH"
|
||||
addonAfter="ZEC"
|
||||
disabled={true}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
@ -193,10 +177,10 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
value={amountToRaise}
|
||||
placeholder="0.5"
|
||||
min={0}
|
||||
max={remainingEthNum}
|
||||
max={remainingTargetNum}
|
||||
step={0.1}
|
||||
onChange={this.handleAmountChange}
|
||||
addonAfter="ETH"
|
||||
addonAfter="ZEC"
|
||||
disabled={isPreview}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
|
|
@ -15,7 +15,9 @@ const ContributorsBlock = () => {
|
|||
<AddressRow
|
||||
key={contributor.address}
|
||||
address={contributor.address}
|
||||
secondary={<UnitDisplay value={contributor.contributionAmount} symbol="ETH" />}
|
||||
secondary={
|
||||
<UnitDisplay value={contributor.contributionAmount} symbol="ZEC" />
|
||||
}
|
||||
/>
|
||||
));
|
||||
} else {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import lodash from 'lodash';
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import BN from 'bn.js';
|
||||
import { Alert, Steps, Spin } from 'antd';
|
||||
import { Proposal, MILESTONE_STATE } from 'types';
|
||||
import UnitDisplay from 'components/UnitDisplay';
|
||||
|
@ -98,11 +97,8 @@ class ProposalMilestones extends React.Component<Props, State> {
|
|||
|
||||
const className = this.state.step === i ? 'is-active' : 'is-inactive';
|
||||
const estimatedDate = moment(milestone.dateEstimated).format('MMMM YYYY');
|
||||
// TODO: Real milestone amount
|
||||
console.warn('TODO: Real milestone amount in Proposal/Milestones/index.tsx');
|
||||
const amount = new BN(0);
|
||||
const reward = (
|
||||
<UnitDisplay value={amount} symbol="ETH" displayShortBalance={4} />
|
||||
<UnitDisplay value={milestone.amount} symbol="ZEC" displayShortBalance={4} />
|
||||
);
|
||||
const alertStyle = { width: 'fit-content', margin: '0 0 1rem 0' };
|
||||
|
||||
|
@ -125,9 +121,8 @@ class ProposalMilestones extends React.Component<Props, State> {
|
|||
The team was awarded <strong>{reward}</strong>{' '}
|
||||
{milestone.isImmediatePayout
|
||||
? 'as an initial payout'
|
||||
: `on ${moment(milestone.payoutRequestVoteDeadline).format(
|
||||
'MMM Do, YYYY',
|
||||
)}`}
|
||||
// TODO: Add property for payout date on milestones
|
||||
: `on ${moment().format('MMM Do, YYYY')}`}
|
||||
.
|
||||
</span>
|
||||
}
|
||||
|
@ -139,12 +134,10 @@ class ProposalMilestones extends React.Component<Props, State> {
|
|||
notification = (
|
||||
<Alert
|
||||
type="info"
|
||||
message={
|
||||
<span>
|
||||
Payout vote is in progress! The approval period ends{' '}
|
||||
{moment(milestone.payoutRequestVoteDeadline).from(new Date())}.
|
||||
</span>
|
||||
}
|
||||
message={`
|
||||
The team has requested a payout for this milestone. It is
|
||||
currently under review.
|
||||
`}
|
||||
style={alertStyle}
|
||||
/>
|
||||
);
|
||||
|
@ -155,10 +148,11 @@ class ProposalMilestones extends React.Component<Props, State> {
|
|||
type="warning"
|
||||
message={
|
||||
<span>
|
||||
Payout was voted against on{' '}
|
||||
{moment(milestone.payoutRequestVoteDeadline).format('MMM Do, YYYY')}.
|
||||
{isTrustee ? ' You ' : ' The team '} can request another payout vote at
|
||||
any time.
|
||||
Payout for this milestone was rejected on{' '}
|
||||
{/* TODO: add property for payout rejection date on milestones */}
|
||||
{moment().format('MMM Do, YYYY')}.
|
||||
{isTrustee ? ' You ' : ' The team '} can request another
|
||||
review for payout at any time.
|
||||
</span>
|
||||
}
|
||||
style={alertStyle}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import BN from 'bn.js';
|
||||
import { Progress, Icon } from 'antd';
|
||||
import moment from 'moment';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
@ -23,12 +22,10 @@ export class ProposalCard extends React.Component<Proposal> {
|
|||
category,
|
||||
dateCreated,
|
||||
team,
|
||||
target,
|
||||
funded,
|
||||
percentFunded,
|
||||
} = this.props;
|
||||
// TODO: Real values from proposal
|
||||
console.warn('TODO: Real values for ProposalCard');
|
||||
const target = new BN(0);
|
||||
const funded = new BN(0);
|
||||
const percentFunded = 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -38,8 +35,8 @@ export class ProposalCard extends React.Component<Proposal> {
|
|||
<h3 className="ProposalCard-title">{title}</h3>
|
||||
<div className="ProposalCard-funding">
|
||||
<div className="ProposalCard-funding-raised">
|
||||
<UnitDisplay value={funded} symbol="ETH" /> <small>raised</small> of{' '}
|
||||
<UnitDisplay value={target} symbol="ETH" /> goal
|
||||
<UnitDisplay value={funded} symbol="ZEC" /> <small>raised</small> of{' '}
|
||||
<UnitDisplay value={target} symbol="ZEC" /> goal
|
||||
</div>
|
||||
<div
|
||||
className={classnames({
|
||||
|
|
|
@ -1,23 +1,28 @@
|
|||
// Adapted from https://github.com/MyCryptoHQ/MyCrypto/blob/develop/common/components/ui/UnitDisplay.tsx
|
||||
import React from 'react';
|
||||
import {
|
||||
fromTokenBase,
|
||||
getDecimalFromEtherUnit,
|
||||
baseToConvertedUnit,
|
||||
getDecimalFromUnitKey,
|
||||
UnitKey,
|
||||
Wei,
|
||||
TokenValue,
|
||||
Zat,
|
||||
} from 'utils/units';
|
||||
import { formatNumber } from 'utils/formatters';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* @description base value of the token / ether, incase of waiting for API calls, we can return '???'
|
||||
* @type {TokenValue | Wei}
|
||||
* @description base value of the unit
|
||||
* @type {Zat}
|
||||
* @memberof Props
|
||||
*/
|
||||
value: TokenValue | Wei;
|
||||
value: Zat;
|
||||
/**
|
||||
* @description Symbol to display to the right of the value, such as 'ETH'
|
||||
* @description Name of the unit to display, defaults to 'zcash'
|
||||
* @type {UnitKey}
|
||||
* @memberof Props
|
||||
*/
|
||||
unit?: UnitKey;
|
||||
/**
|
||||
* @description Symbol to display to the right of the value, such as 'ZEC'
|
||||
* @type {string}
|
||||
* @memberof Props
|
||||
*/
|
||||
|
@ -32,22 +37,13 @@ interface Props {
|
|||
checkOffline?: boolean;
|
||||
}
|
||||
|
||||
interface EthProps extends Props {
|
||||
unit?: UnitKey;
|
||||
}
|
||||
interface TokenProps extends Props {
|
||||
decimal: number;
|
||||
}
|
||||
|
||||
const isTokenUnit = (param: EthProps | TokenProps): param is TokenProps =>
|
||||
!!(param as TokenProps).decimal;
|
||||
|
||||
const UnitDisplay: React.SFC<EthProps | TokenProps> = params => {
|
||||
const UnitDisplay: React.SFC<Props> = params => {
|
||||
const { value, symbol, displayShortBalance, displayTrailingZeroes } = params;
|
||||
|
||||
const convertedValue = isTokenUnit(params)
|
||||
? fromTokenBase(value, params.decimal)
|
||||
: fromTokenBase(value, getDecimalFromEtherUnit(params.unit || 'ether'));
|
||||
const convertedValue = baseToConvertedUnit(
|
||||
value.toString(),
|
||||
getDecimalFromUnitKey(params.unit || 'zcash'),
|
||||
);
|
||||
|
||||
let formattedValue;
|
||||
|
||||
|
|
|
@ -2,12 +2,11 @@ import { ProposalDraft, CreateMilestone } from 'types';
|
|||
import { User } from 'types';
|
||||
import { getAmountError } from 'utils/validators';
|
||||
import { MILESTONE_STATE, Proposal } from 'types';
|
||||
import { Wei, toWei } from 'utils/units';
|
||||
import { Zat, toZat } from 'utils/units';
|
||||
import { ONE_DAY } from 'utils/time';
|
||||
import { PROPOSAL_CATEGORY } from 'api/constants';
|
||||
|
||||
// TODO: Raise this limit
|
||||
export const TARGET_ETH_LIMIT = 1000;
|
||||
export const TARGET_ZEC_LIMIT = 1000;
|
||||
|
||||
interface CreateFormErrors {
|
||||
title?: string;
|
||||
|
@ -75,7 +74,7 @@ export function getCreateErrors(
|
|||
// Amount to raise
|
||||
const targetFloat = target ? parseFloat(target) : 0;
|
||||
if (target && !Number.isNaN(targetFloat)) {
|
||||
const targetErr = getAmountError(targetFloat, TARGET_ETH_LIMIT);
|
||||
const targetErr = getAmountError(targetFloat, TARGET_ZEC_LIMIT);
|
||||
if (targetErr) {
|
||||
errors.target = targetErr;
|
||||
}
|
||||
|
@ -148,19 +147,19 @@ export function getCreateWarnings(form: Partial<ProposalDraft>): string[] {
|
|||
return warnings;
|
||||
}
|
||||
|
||||
function milestoneToMilestoneAmount(milestone: CreateMilestone, raiseGoal: Wei) {
|
||||
return raiseGoal.divn(100).mul(Wei(milestone.payoutPercent));
|
||||
function milestoneToMilestoneAmount(milestone: CreateMilestone, raiseGoal: Zat) {
|
||||
return raiseGoal.divn(100).mul(Zat(milestone.payoutPercent));
|
||||
}
|
||||
|
||||
export function proposalToContractData(form: ProposalDraft): any {
|
||||
const targetInWei = toWei(form.target, 'ether');
|
||||
const targetInZat = toZat(form.target);
|
||||
const milestoneAmounts = form.milestones.map(m =>
|
||||
milestoneToMilestoneAmount(m, targetInWei),
|
||||
milestoneToMilestoneAmount(m, targetInZat),
|
||||
);
|
||||
const immediateFirstMilestonePayout = form.milestones[0]!.immediatePayout;
|
||||
|
||||
return {
|
||||
ethAmount: targetInWei,
|
||||
ethAmount: targetInZat,
|
||||
payoutAddress: form.payoutAddress,
|
||||
trusteesAddresses: [],
|
||||
milestoneAmounts,
|
||||
|
@ -180,10 +179,15 @@ export function makeProposalPreviewFromDraft(
|
|||
proposalId: 0,
|
||||
proposalUrlId: '0-title',
|
||||
proposalAddress: '0x0',
|
||||
payoutAddress: '0x0',
|
||||
dateCreated: Date.now(),
|
||||
title: draft.title,
|
||||
brief: draft.brief,
|
||||
content: draft.content,
|
||||
deadlineDuration: 86400 * 60,
|
||||
target: toZat(draft.target),
|
||||
funded: Zat('0'),
|
||||
percentFunded: 0,
|
||||
stage: 'preview',
|
||||
category: draft.category || PROPOSAL_CATEGORY.DAPP,
|
||||
team: draft.team,
|
||||
|
@ -191,17 +195,13 @@ export function makeProposalPreviewFromDraft(
|
|||
index: idx,
|
||||
title: m.title,
|
||||
content: m.content,
|
||||
amount: toWei(target * (parseInt(m.payoutPercent, 10) / 100), 'ether'),
|
||||
amountAgainstPayout: Wei('0'),
|
||||
percentAgainstPayout: 0,
|
||||
payoutRequestVoteDeadline: Date.now(),
|
||||
amount: toZat(target * (parseInt(m.payoutPercent, 10) / 100)),
|
||||
dateEstimated: m.dateEstimated,
|
||||
immediatePayout: m.immediatePayout,
|
||||
isImmediatePayout: m.immediatePayout,
|
||||
isPaid: false,
|
||||
payoutPercent: m.payoutPercent.toString(),
|
||||
state: MILESTONE_STATE.WAITING,
|
||||
stage: MILESTONE_STATE.WAITING,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
|
@ -1,7 +1,8 @@
|
|||
import BN from 'bn.js';
|
||||
import { User, Proposal, UserProposal } from 'types';
|
||||
import { User, Proposal, UserProposal, MILESTONE_STATE } from 'types';
|
||||
import { UserState } from 'modules/users/reducers';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { toZat } from './units';
|
||||
|
||||
export function formatUserForPost(user: User) {
|
||||
return {
|
||||
|
@ -21,8 +22,22 @@ export function formatUserFromGet(user: UserState) {
|
|||
return user;
|
||||
}
|
||||
|
||||
export function formatProposalFromGet(proposal: Proposal) {
|
||||
export function formatProposalFromGet(p: any): Proposal {
|
||||
const proposal = { ...p } as Proposal;
|
||||
proposal.proposalUrlId = generateProposalUrl(proposal.proposalId, proposal.title);
|
||||
proposal.target = toZat(p.target);
|
||||
proposal.funded = toZat(p.funded);
|
||||
proposal.percentFunded = proposal.funded.div(proposal.target.divn(100)).toNumber();
|
||||
proposal.milestones = proposal.milestones.map((m: any, index: number) => {
|
||||
return {
|
||||
...m,
|
||||
index,
|
||||
amount: proposal.target.mul(new BN(m.payoutPercent)).divn(100),
|
||||
// TODO: Get data from backend
|
||||
state: MILESTONE_STATE.WAITING,
|
||||
isPaid: false,
|
||||
};
|
||||
});
|
||||
return proposal;
|
||||
}
|
||||
|
||||
|
@ -48,6 +63,16 @@ export function extractProposalIdFromUrl(slug: string) {
|
|||
|
||||
// pre-hydration massage (BNify JSONed BNs)
|
||||
export function massageSerializedState(state: AppState) {
|
||||
// proposals
|
||||
state.proposal.proposals = state.proposal.proposals.map(p => ({
|
||||
...p,
|
||||
target: new BN((p.target as any) as string, 16),
|
||||
funded: new BN((p.funded as any) as string, 16),
|
||||
milestones: p.milestones.map(m => ({
|
||||
...m,
|
||||
amount: new BN((m.amount as any) as string, 16),
|
||||
})),
|
||||
}));
|
||||
// users
|
||||
const bnUserProp = (p: UserProposal) => {
|
||||
p.funded = new BN(p.funded, 16);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export const DONATION = {
|
||||
ETH: '0x8B0B72F8bDE212991135668922fD5acE557DE6aB',
|
||||
ZCASH_TRANSPARENT: 't1aib2cbwPVrFfrjGGkhWD67imdBet1xDTr',
|
||||
ZCASH_SPROUT: 'zcWGwZU7FyUgpdrWGkeFqCEnvhLRDAVuf2ZbhW4vzNMTTR6VUgfiBGkiNbkC4e38QaPtS13RKZCriqN9VcyyKNRRQxbgnen',
|
||||
};
|
||||
|
|
|
@ -2,42 +2,16 @@
|
|||
import BN from 'bn.js';
|
||||
import { stripHexPrefix } from 'utils/formatters';
|
||||
|
||||
type UnitKey = keyof typeof Units;
|
||||
type Wei = BN;
|
||||
type TokenValue = BN;
|
||||
type Address = Buffer;
|
||||
type Nonce = BN;
|
||||
type Data = Buffer;
|
||||
|
||||
export const ETH_DECIMAL = 18;
|
||||
|
||||
export const ZCASH_DECIMAL = 8;
|
||||
export const Units = {
|
||||
wei: '1',
|
||||
kwei: '1000',
|
||||
ada: '1000',
|
||||
femtoether: '1000',
|
||||
mwei: '1000000',
|
||||
babbage: '1000000',
|
||||
picoether: '1000000',
|
||||
gwei: '1000000000',
|
||||
shannon: '1000000000',
|
||||
nanoether: '1000000000',
|
||||
nano: '1000000000',
|
||||
szabo: '1000000000000',
|
||||
microether: '1000000000000',
|
||||
micro: '1000000000000',
|
||||
finney: '1000000000000000',
|
||||
milliether: '1000000000000000',
|
||||
milli: '1000000000000000',
|
||||
ether: '1000000000000000000',
|
||||
kether: '1000000000000000000000',
|
||||
grand: '1000000000000000000000',
|
||||
einstein: '1000000000000000000000',
|
||||
mether: '1000000000000000000000000',
|
||||
gether: '1000000000000000000000000000',
|
||||
tether: '1000000000000000000000000000000',
|
||||
zat: '1',
|
||||
zcash: '100000000',
|
||||
};
|
||||
const handleValues = (input: string | BN) => {
|
||||
|
||||
export type Zat = BN;
|
||||
export type UnitKey = keyof typeof Units;
|
||||
|
||||
export const handleValues = (input: string | BN) => {
|
||||
if (typeof input === 'string') {
|
||||
return input.startsWith('0x') ? new BN(stripHexPrefix(input), 16) : new BN(input);
|
||||
}
|
||||
|
@ -51,20 +25,14 @@ const handleValues = (input: string | BN) => {
|
|||
}
|
||||
};
|
||||
|
||||
const Nonce = (input: string | BN) => handleValues(input);
|
||||
|
||||
const Wei = (input: string | BN): Wei => handleValues(input);
|
||||
|
||||
const TokenValue = (input: string | BN) => handleValues(input);
|
||||
|
||||
const getDecimalFromEtherUnit = (key: UnitKey) => Units[key].length - 1;
|
||||
export const Zat = (input: string | BN): Zat => handleValues(input);
|
||||
|
||||
const stripRightZeros = (str: string) => {
|
||||
const strippedStr = str.replace(/0+$/, '');
|
||||
return strippedStr === '' ? null : strippedStr;
|
||||
};
|
||||
|
||||
const baseToConvertedUnit = (value: string, decimal: number) => {
|
||||
export const baseToConvertedUnit = (value: string, decimal: number) => {
|
||||
if (decimal === 0) {
|
||||
return value;
|
||||
}
|
||||
|
@ -83,52 +51,14 @@ const convertedToBaseUnit = (value: string, decimal: number) => {
|
|||
return `${integerPart}${paddedFraction}`;
|
||||
};
|
||||
|
||||
const fromWei = (wei: Wei, unit: UnitKey) => {
|
||||
const decimal = getDecimalFromEtherUnit(unit);
|
||||
return baseToConvertedUnit(wei.toString(), decimal);
|
||||
export const fromZat = (zat: Zat) => {
|
||||
return baseToConvertedUnit(zat.toString(), ZCASH_DECIMAL);
|
||||
};
|
||||
|
||||
const toWei = (value: string | number, unitType: number | UnitKey): Wei => {
|
||||
export const toZat = (value: string | number): Zat => {
|
||||
value = value.toString();
|
||||
let decimal;
|
||||
if (typeof unitType === 'string') {
|
||||
decimal = getDecimalFromEtherUnit(unitType);
|
||||
} else {
|
||||
decimal = unitType;
|
||||
}
|
||||
const wei = convertedToBaseUnit(value, decimal);
|
||||
return Wei(wei);
|
||||
const zat = convertedToBaseUnit(value, ZCASH_DECIMAL);
|
||||
return Zat(zat);
|
||||
};
|
||||
|
||||
const fromTokenBase = (value: TokenValue, decimal: number) =>
|
||||
baseToConvertedUnit(value.toString(), decimal);
|
||||
|
||||
const toTokenBase = (value: string, decimal: number) =>
|
||||
TokenValue(convertedToBaseUnit(value, decimal));
|
||||
|
||||
const convertTokenBase = (value: TokenValue, oldDecimal: number, newDecimal: number) => {
|
||||
if (oldDecimal === newDecimal) {
|
||||
return value;
|
||||
}
|
||||
return toTokenBase(fromTokenBase(value, oldDecimal), newDecimal);
|
||||
};
|
||||
|
||||
const gasPriceToBase = (price: number) =>
|
||||
toWei(price.toString(), getDecimalFromEtherUnit('gwei'));
|
||||
|
||||
export {
|
||||
Data,
|
||||
Address,
|
||||
TokenValue,
|
||||
fromWei,
|
||||
toWei,
|
||||
toTokenBase,
|
||||
fromTokenBase,
|
||||
convertTokenBase,
|
||||
Wei,
|
||||
getDecimalFromEtherUnit,
|
||||
UnitKey,
|
||||
Nonce,
|
||||
handleValues,
|
||||
gasPriceToBase,
|
||||
};
|
||||
export const getDecimalFromUnitKey = (key: UnitKey) => Units[key].length - 1;
|
||||
|
|
|
@ -7,7 +7,7 @@ export function getAmountError(amount: number, max: number = Infinity) {
|
|||
) {
|
||||
return 'Must be in increments of 0.001';
|
||||
} else if (amount > max) {
|
||||
return `Cannot exceed maximum (${max} ETH)`;
|
||||
return `Cannot exceed maximum (${max} ZEC)`;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -64,7 +64,7 @@ class Example extends React.Component<ExampleProps, ExampleState> {
|
|||
constructor(props: ExampleProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: props.defaultValue !== undefined ? props.defaultValue : DONATION.ETH,
|
||||
value: props.defaultValue !== undefined ? props.defaultValue : DONATION.ZCASH_SPROUT,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -7,21 +7,21 @@ import Contributors from 'components/Proposal/Contributors';
|
|||
import 'styles/style.less';
|
||||
import 'components/Proposal/style.less';
|
||||
import 'components/Proposal/Governance/style.less';
|
||||
import { getProposalWithCrowdFund } from './props';
|
||||
import { generateProposal } from './props';
|
||||
|
||||
const propsNoFunding = getProposalWithCrowdFund({
|
||||
const propsNoFunding = generateProposal({
|
||||
amount: 5,
|
||||
funded: 0,
|
||||
});
|
||||
const propsHalfFunded = getProposalWithCrowdFund({
|
||||
const propsHalfFunded = generateProposal({
|
||||
amount: 5,
|
||||
funded: 2.5,
|
||||
});
|
||||
const propsFunded = getProposalWithCrowdFund({
|
||||
const propsFunded = generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
});
|
||||
const propsNotFundedExpired = getProposalWithCrowdFund({
|
||||
const propsNotFundedExpired = generateProposal({
|
||||
created: Date.now() - 10,
|
||||
deadline: Date.now() - 1,
|
||||
});
|
||||
|
|
|
@ -6,21 +6,21 @@ import { ProposalCard } from 'components/Proposals/ProposalCard';
|
|||
import 'styles/style.less';
|
||||
import 'components/Proposal/style.less';
|
||||
import 'components/Proposal/Governance/style.less';
|
||||
import { getProposalWithCrowdFund } from './props';
|
||||
import { generateProposal } from './props';
|
||||
|
||||
const propsNoFunding = getProposalWithCrowdFund({
|
||||
const propsNoFunding = generateProposal({
|
||||
amount: 5,
|
||||
funded: 0,
|
||||
});
|
||||
const propsHalfFunded = getProposalWithCrowdFund({
|
||||
const propsHalfFunded = generateProposal({
|
||||
amount: 5,
|
||||
funded: 2.5,
|
||||
});
|
||||
const propsFunded = getProposalWithCrowdFund({
|
||||
const propsFunded = generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
});
|
||||
const propsNotFundedExpired = getProposalWithCrowdFund({
|
||||
const propsNotFundedExpired = generateProposal({
|
||||
created: Date.now() - 1000 * 60 * 60 * 10,
|
||||
deadline: Date.now() - 1,
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@ const { WAITING, ACTIVE, PAID, REJECTED } = MILESTONE_STATE;
|
|||
import 'styles/style.less';
|
||||
import 'components/Proposal/style.less';
|
||||
import 'components/Proposal/Governance/style.less';
|
||||
import { getProposalWithCrowdFund } from './props';
|
||||
import { generateProposal } from './props';
|
||||
|
||||
const msWaiting = { state: WAITING, isPaid: false };
|
||||
const msPaid = { state: PAID, isPaid: true };
|
||||
|
@ -22,96 +22,96 @@ const trustee = 'z123';
|
|||
const contributor = 'z456';
|
||||
|
||||
const geometryCases = [...Array(10).keys()].map(i =>
|
||||
getProposalWithCrowdFund({ milestoneCount: i + 1 }),
|
||||
generateProposal({ milestoneCount: i + 1 }),
|
||||
);
|
||||
|
||||
const cases: { [index: string]: any } = {
|
||||
// trustee - first
|
||||
['not funded']: getProposalWithCrowdFund({
|
||||
['not funded']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 0,
|
||||
}),
|
||||
['first - waiting']: getProposalWithCrowdFund({
|
||||
['first - waiting']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
}),
|
||||
['first - not paid']: getProposalWithCrowdFund({
|
||||
['first - not paid']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
milestoneOverrides: [
|
||||
{ state: PAID, isPaid: false, payoutRequestVoteDeadline: Date.now() },
|
||||
{ state: PAID, isPaid: false },
|
||||
msWaiting,
|
||||
msWaiting,
|
||||
],
|
||||
}),
|
||||
|
||||
// trustee - second
|
||||
['second - waiting']: getProposalWithCrowdFund({
|
||||
['second - waiting']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
milestoneOverrides: [msPaid, msWaiting, msWaiting],
|
||||
}),
|
||||
['second - active']: getProposalWithCrowdFund({
|
||||
['second - active']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
milestoneOverrides: [msPaid, msActive, msWaiting],
|
||||
}),
|
||||
['second - not paid']: getProposalWithCrowdFund({
|
||||
['second - not paid']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
milestoneOverrides: [
|
||||
msPaid,
|
||||
{ state: PAID, isPaid: false, payoutRequestVoteDeadline: Date.now() },
|
||||
{ state: PAID, isPaid: false },
|
||||
msWaiting,
|
||||
],
|
||||
}),
|
||||
['second - no vote']: getProposalWithCrowdFund({
|
||||
['second - no vote']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
milestoneOverrides: [
|
||||
msPaid,
|
||||
{ state: ACTIVE, isPaid: false, percentAgainstPayout: 33 },
|
||||
{ state: ACTIVE, isPaid: false },
|
||||
msWaiting,
|
||||
],
|
||||
contributorOverrides: [{ milestoneNoVotes: [false, true, false] }],
|
||||
}),
|
||||
['second - rejected']: getProposalWithCrowdFund({
|
||||
['second - rejected']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
milestoneOverrides: [msPaid, msRejected, msWaiting],
|
||||
}),
|
||||
|
||||
// trustee - third
|
||||
['final - waiting']: getProposalWithCrowdFund({
|
||||
['final - waiting']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
milestoneOverrides: [msPaid, msPaid, msWaiting],
|
||||
}),
|
||||
['final - active']: getProposalWithCrowdFund({
|
||||
['final - active']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
milestoneOverrides: [msPaid, msPaid, msActive],
|
||||
}),
|
||||
['final - not paid']: getProposalWithCrowdFund({
|
||||
['final - not paid']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
milestoneOverrides: [
|
||||
msPaid,
|
||||
msPaid,
|
||||
{ state: PAID, isPaid: false, payoutRequestVoteDeadline: Date.now() },
|
||||
{ state: PAID, isPaid: false },
|
||||
],
|
||||
}),
|
||||
['final - no vote']: getProposalWithCrowdFund({
|
||||
['final - no vote']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
milestoneOverrides: [
|
||||
msPaid,
|
||||
msPaid,
|
||||
{ state: ACTIVE, isPaid: false, percentAgainstPayout: 33 },
|
||||
{ state: ACTIVE, isPaid: false },
|
||||
],
|
||||
contributorOverrides: [{ milestoneNoVotes: [false, true, false] }],
|
||||
}),
|
||||
['final - rejected']: getProposalWithCrowdFund({
|
||||
['final - rejected']: generateProposal({
|
||||
amount: 5,
|
||||
funded: 5,
|
||||
milestoneOverrides: [msPaid, msPaid, msRejected],
|
||||
|
|
|
@ -4,30 +4,30 @@ import BN from 'bn.js';
|
|||
|
||||
import UnitDisplay from '../client/components/UnitDisplay';
|
||||
|
||||
const oneEth = new BN('1000000000000000000');
|
||||
const oneZEC = new BN('1000000000000000000');
|
||||
|
||||
const cases = [
|
||||
{
|
||||
disp: 'basic',
|
||||
props: { value: oneEth.mul(new BN(25)), symbol: 'ETH' },
|
||||
props: { value: oneZEC.mul(new BN(25)), symbol: 'ZEC' },
|
||||
},
|
||||
{
|
||||
disp: 'fraction',
|
||||
props: { value: oneEth.div(new BN(3)), symbol: 'ETH' },
|
||||
props: { value: oneZEC.div(new BN(3)), symbol: 'ZEC' },
|
||||
},
|
||||
{
|
||||
disp: 'fraction - displayShortBalance: true',
|
||||
props: { value: oneEth.div(new BN(3)), symbol: 'ETH', displayShortBalance: true },
|
||||
props: { value: oneZEC.div(new BN(3)), symbol: 'ZEC', displayShortBalance: true },
|
||||
},
|
||||
{
|
||||
disp: 'fraction - displayShortBalance: 2',
|
||||
props: { value: oneEth.div(new BN(3)), symbol: 'ETH', displayShortBalance: 2 },
|
||||
props: { value: oneZEC.div(new BN(3)), symbol: 'ZEC', displayShortBalance: 2 },
|
||||
},
|
||||
{
|
||||
disp: 'fraction - displayShortBalance: 4, displayTrailingZeros: false',
|
||||
props: {
|
||||
value: oneEth.div(new BN(2)),
|
||||
symbol: 'ETH',
|
||||
value: oneZEC.div(new BN(2)),
|
||||
symbol: 'ZEC',
|
||||
displayShortBalance: 4,
|
||||
displayTrailingZeroes: false,
|
||||
},
|
||||
|
@ -35,21 +35,21 @@ const cases = [
|
|||
{
|
||||
disp: 'fraction - displayShortBalance: 4, displayTrailingZeros: true',
|
||||
props: {
|
||||
value: oneEth.div(new BN(2)),
|
||||
symbol: 'ETH',
|
||||
value: oneZEC.div(new BN(2)),
|
||||
symbol: 'ZEC',
|
||||
displayShortBalance: 4,
|
||||
displayTrailingZeroes: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
disp: 'tiny',
|
||||
props: { value: new BN(1), symbol: 'ETH' },
|
||||
props: { value: new BN(1), symbol: 'ZEC' },
|
||||
},
|
||||
{
|
||||
disp: 'tiny - displayShortBalance: true',
|
||||
props: {
|
||||
value: new BN(1),
|
||||
symbol: 'ETH',
|
||||
symbol: 'ZEC',
|
||||
displayShortBalance: true,
|
||||
},
|
||||
},
|
||||
|
@ -57,7 +57,7 @@ const cases = [
|
|||
disp: 'tiny - displayShortBalance: 2',
|
||||
props: {
|
||||
value: new BN(1),
|
||||
symbol: 'ETH',
|
||||
symbol: 'ZEC',
|
||||
displayShortBalance: 2,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -34,7 +34,7 @@ const cases: Case[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
disp: 'ETH Address Only User',
|
||||
disp: 'ZEC Address Only User',
|
||||
props: {
|
||||
user: {
|
||||
...user,
|
||||
|
@ -43,7 +43,7 @@ const cases: Case[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
disp: 'No Avatar, No ETH Address User',
|
||||
disp: 'No Avatar, No ZEC Address User',
|
||||
props: {
|
||||
user: {
|
||||
...user,
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
import { PROPOSAL_CATEGORY } from 'api/constants';
|
||||
import BN from 'bn.js';
|
||||
|
||||
const oneEth = new BN('1000000000000000000');
|
||||
const oneZec = new BN('100000000');
|
||||
|
||||
export function getGovernanceMilestonesProps({
|
||||
isContributor = true,
|
||||
|
@ -26,7 +26,7 @@ export function getGovernanceMilestonesProps({
|
|||
};
|
||||
}
|
||||
|
||||
export function getProposalWithCrowdFund({
|
||||
export function generateProposal({
|
||||
amount = 10,
|
||||
funded = 5,
|
||||
created = Date.now(),
|
||||
|
@ -42,8 +42,9 @@ export function getProposalWithCrowdFund({
|
|||
contributorOverrides?: Array<Partial<Contributor>>;
|
||||
milestoneCount?: number;
|
||||
}) {
|
||||
const amountBn = oneEth.mul(new BN(amount));
|
||||
const fundedBn = oneEth.mul(new BN(funded));
|
||||
const amountBn = oneZec.mul(new BN(amount));
|
||||
const fundedBn = oneZec.mul(new BN(funded));
|
||||
const percentFunded = amount / funded;
|
||||
|
||||
let contributors = [
|
||||
{
|
||||
|
@ -96,34 +97,27 @@ export function getProposalWithCrowdFund({
|
|||
return ts.slice(rand).join('');
|
||||
};
|
||||
|
||||
const genMilestone = (overrides: Partial<ProposalMilestone> = {}) => {
|
||||
const genMilestone = (overrides: Partial<ProposalMilestone> = {}): ProposalMilestone => {
|
||||
const now = new Date();
|
||||
if (overrides.index) {
|
||||
const estimate = new Date(now.setMonth(now.getMonth() + overrides.index));
|
||||
overrides.dateEstimated = estimate.toISOString();
|
||||
}
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
const defaults: ProposalMilestone = {
|
||||
title: 'Milestone A',
|
||||
body: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||
content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua.`,
|
||||
content: '',
|
||||
dateEstimated: '2018-10-01T00:00:00+00:00',
|
||||
immediatePayout: true,
|
||||
index: 0,
|
||||
state: MILESTONE_STATE.WAITING,
|
||||
amount: amountBn,
|
||||
amountAgainstPayout: new BN(0),
|
||||
percentAgainstPayout: 0,
|
||||
payoutRequestVoteDeadline: 0,
|
||||
isPaid: false,
|
||||
isImmediatePayout: true,
|
||||
payoutPercent: '33',
|
||||
stage: 'NOT_REQUESTED',
|
||||
},
|
||||
overrides,
|
||||
);
|
||||
};
|
||||
return { ...defaults, ...overrides };
|
||||
};
|
||||
|
||||
const milestones = [...Array(milestoneCount).keys()].map(i => {
|
||||
|
@ -148,7 +142,12 @@ export function getProposalWithCrowdFund({
|
|||
proposalId: 12345,
|
||||
proposalUrlId: '12345-crowdfund-title',
|
||||
proposalAddress: '0x033fDc6C01DC2385118C7bAAB88093e22B8F0710',
|
||||
payoutAddress: 'z123',
|
||||
dateCreated: created / 1000,
|
||||
deadlineDuration: 86400 * 60,
|
||||
target: amountBn,
|
||||
funded: fundedBn,
|
||||
percentFunded,
|
||||
title: 'Crowdfund Title',
|
||||
brief: 'A cool test crowdfund',
|
||||
content: 'body',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Wei } from 'utils/units';
|
||||
import { Zat } from 'utils/units';
|
||||
|
||||
export enum MILESTONE_STATE {
|
||||
WAITING = 'WAITING',
|
||||
|
@ -10,10 +10,7 @@ export enum MILESTONE_STATE {
|
|||
export interface Milestone {
|
||||
index: number;
|
||||
state: MILESTONE_STATE;
|
||||
amount: Wei;
|
||||
amountAgainstPayout: Wei;
|
||||
percentAgainstPayout: number;
|
||||
payoutRequestVoteDeadline: number;
|
||||
amount: Zat;
|
||||
isPaid: boolean;
|
||||
isImmediatePayout: boolean;
|
||||
}
|
||||
|
@ -23,7 +20,6 @@ export interface ProposalMilestone extends Milestone {
|
|||
immediatePayout: boolean;
|
||||
dateEstimated: string;
|
||||
payoutPercent: string;
|
||||
stage: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { Wei } from 'utils/units';
|
||||
import BN from 'bn.js';
|
||||
import { Zat } from 'utils/units';
|
||||
import { PROPOSAL_CATEGORY } from 'api/constants';
|
||||
import {
|
||||
CreateMilestone,
|
||||
ProposalMilestone,
|
||||
Update,
|
||||
User,
|
||||
Comment,
|
||||
} from 'types';
|
||||
import { ProposalMilestone } from './milestone';
|
||||
|
||||
export interface TeamInvite {
|
||||
id: number;
|
||||
|
@ -17,7 +18,7 @@ export interface TeamInvite {
|
|||
|
||||
export interface Contributor {
|
||||
address: string;
|
||||
contributionAmount: Wei;
|
||||
contributionAmount: Zat;
|
||||
refundVote: boolean;
|
||||
refunded: boolean;
|
||||
proportionalContribution: string;
|
||||
|
@ -40,18 +41,14 @@ export interface ProposalDraft {
|
|||
invites: TeamInvite[];
|
||||
}
|
||||
|
||||
export interface Proposal {
|
||||
proposalId: number;
|
||||
|
||||
export interface Proposal extends Omit<ProposalDraft, 'target' | 'invites'> {
|
||||
proposalAddress: string;
|
||||
proposalUrlId: string;
|
||||
dateCreated: number;
|
||||
title: string;
|
||||
brief: string;
|
||||
content: string;
|
||||
stage: string;
|
||||
category: PROPOSAL_CATEGORY;
|
||||
target: BN;
|
||||
funded: BN;
|
||||
percentFunded: number;
|
||||
milestones: ProposalMilestone[];
|
||||
team: User[];
|
||||
}
|
||||
|
||||
export interface TeamInviteWithProposal extends TeamInvite {
|
||||
|
@ -74,6 +71,6 @@ export interface UserProposal {
|
|||
title: string;
|
||||
brief: string;
|
||||
team: User[];
|
||||
funded: Wei;
|
||||
target: Wei;
|
||||
funded: BN;
|
||||
target: BN;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue