Skip to content

Commit

Permalink
feat: partial stacking, closes #295
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Nov 24, 2020
1 parent c92b124 commit 30a661d
Show file tree
Hide file tree
Showing 18 changed files with 236 additions and 73 deletions.
2 changes: 1 addition & 1 deletion app/api/watch-contract-execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function watchContractExecution(args: WatchContractExecutionArgs) {
tx.tx_status === 'abort_by_post_condition'
) {
clearInterval(timeoutInterval);
return reject(new Error(error?.message || defaultErrorMsg));
return reject(error?.message || defaultErrorMsg);
}

if (tx.tx_status === 'success') {
Expand Down
14 changes: 13 additions & 1 deletion app/components/home/stacking-begins-soon-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,19 @@ interface StackingBeginsSoonCardProps {
export const StackingBeginsSoonCard: FC<StackingBeginsSoonCardProps> = ({
blocksTillNextCycle,
}) => (
<Flex flexDirection="column" flex={1} justifyContent="center" textAlign="center">
<Flex
flexDirection="column"
flex={1}
justifyContent="center"
textAlign="center"
alignItems="center"
mt="extra-loose"
borderRadius="8px"
boxShadow="0px 1px 2px rgba(0, 0, 0, 0.04);"
border="1px solid #F0F0F5"
px="loose"
minHeight="180px"
>
<Box>
<Text display="block" textStyle="body.small" color="ink.600" width="100%">
Stacking will begin in the next cycle
Expand Down
3 changes: 0 additions & 3 deletions app/components/home/stacking-loading.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { FC } from 'react';
import { Flex, Spinner } from '@blockstack/ui';
import { Card } from '@components/card';

export const StackingLoading: FC = () => (
<Flex
Expand All @@ -13,8 +12,6 @@ export const StackingLoading: FC = () => (
px="loose"
minHeight="180px"
>
{/* <Card> */}
<Spinner size="sm" color="ink.300" />
{/* </Card> */}
</Flex>
);
10 changes: 6 additions & 4 deletions app/components/home/stacking-promo-card.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React, { FC } from 'react';
import { Box, Flex, Text, Button } from '@blockstack/ui';

import btcPodium from '../../assets/images/btc-podium.svg';
import { openExternalLink } from '@utils/external-links';
import { BUY_STX_URL } from '@constants/index';
import { toHumanReadableStx } from '@utils/unit-convert';
import btcPodium from '../../assets/images/btc-podium.svg';

interface StackingPromoCardProps {
minRequiredStx: number;
minRequiredMicroStx: number;
}

export const StackingPromoCard: FC<StackingPromoCardProps> = ({ minRequiredStx }) => (
export const StackingPromoCard: FC<StackingPromoCardProps> = ({ minRequiredMicroStx }) => (
<Box
mt="extra-loose"
borderRadius="8px"
Expand All @@ -22,7 +23,8 @@ export const StackingPromoCard: FC<StackingPromoCardProps> = ({ minRequiredStx }
Earn Bitcoin rewards with Stacking
</Text>
<Text display="block" mt="tight" textAlign="center" maxWidth="320px" mx="auto">
You can earn Bitcoin by temporarily locking {Math.ceil(minRequiredStx)} STX or more
You can earn Bitcoin by temporarily locking {toHumanReadableStx(minRequiredMicroStx)} or
more
</Text>
<Button
size="md"
Expand Down
4 changes: 3 additions & 1 deletion app/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ export const MNEMONIC_ENTROPY = 256;

type Environments = 'development' | 'testing' | 'production';

export const STX_DECIMAL_PRECISION = 6;

export const ENV = (process.env.NODE_ENV ?? 'production') as Environments;

export const BUY_STX_URL = 'https://coinmarketcap.com/currencies/blockstack/markets';

export const STATUS_PAGE_URL = 'http://status.test-blockstack.com';

export const DEFAULT_STACKS_NODE_URL = 'https://stacks-node-api.krypton.blockstack.org';
export const DEFAULT_STACKS_NODE_URL = 'https://stacks-node-api.blockstack.org';

export const TREZOR_HELP_URL =
'https://www.blockstack.org/questions/how-can-i-use-my-trezor-device-with-the-stacks-wallet';
Expand Down
1 change: 0 additions & 1 deletion app/hooks/use-ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export function useLedger() {
const [error, t] = await safeAwait(TransportNodeHid.open(event.descriptor));

if (error) {
console.log(error);
setUsbError('Unable to connect to device. You may need to configure your udev rules.');
return;
}
Expand Down
23 changes: 13 additions & 10 deletions app/modals/stacking/stacking-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,15 @@ enum StackingModalStep {
type StackingModalComponents = () => Record<'header' | 'body' | 'footer', JSX.Element>;

interface StackingModalProps {
onClose(): void;
poxAddress: string;
numCycles: number;
amountToStack: BigNumber;
onClose(): void;
}

export const StackingModal: FC<StackingModalProps> = ({ onClose, numCycles, poxAddress }) => {
export const StackingModal: FC<StackingModalProps> = props => {
const { onClose, numCycles, poxAddress, amountToStack } = props;

const dispatch = useDispatch();
const history = useHistory();
useHotkeys('esc', () => onClose());
Expand Down Expand Up @@ -105,16 +108,15 @@ export const StackingModal: FC<StackingModalProps> = ({ onClose, numCycles, poxA
salt,
password,
});
const balanceBN = new BigNumber(balance, 10);
const txOptions = poxClient.getLockTxOptions({
cycles: numCycles,
poxAddress,
amountMicroStx: balanceBN,
amountMicroStx: amountToStack,
contract: poxInfo.contract_id,
burnBlockHeight: coreNodeInfo.burn_block_height,
});
const tx = await makeContractCall({ ...txOptions, senderKey: privateKey });
poxClient.modifyLockTxFee({ tx, amountMicroStx: balanceBN });
poxClient.modifyLockTxFee({ tx, amountMicroStx: amountToStack });
const signer = new TransactionSigner(tx);
signer.signOrigin(createStacksPrivateKey(privateKey));
return tx;
Expand All @@ -128,6 +130,7 @@ export const StackingModal: FC<StackingModalProps> = ({ onClose, numCycles, poxA
node.url,
numCycles,
poxAddress,
amountToStack,
]);

const createLedgerWalletTx = useCallback(
Expand All @@ -137,9 +140,9 @@ export const StackingModal: FC<StackingModalProps> = ({ onClose, numCycles, poxA
if (!blockstackApp || !poxInfo || !balance)
throw new Error('`poxInfo` or `blockstackApp` is not defined');
// 1. Form unsigned contract call transaction
const balanceBN = new BigNumber(balance, 10);

const txOptions = poxClient.getLockTxOptions({
amountMicroStx: balanceBN,
amountMicroStx: amountToStack,
poxAddress,
cycles: numCycles,
contract: poxInfo.contract_id,
Expand All @@ -151,7 +154,7 @@ export const StackingModal: FC<StackingModalProps> = ({ onClose, numCycles, poxA
publicKey: options.publicKey.toString('hex'),
});

poxClient.modifyLockTxFee({ tx: unsignedTx, amountMicroStx: balanceBN });
poxClient.modifyLockTxFee({ tx: unsignedTx, amountMicroStx: amountToStack });

// 2. Sign transaction
const resp: ResponseSign = await blockstackApp.sign(
Expand All @@ -164,14 +167,14 @@ export const StackingModal: FC<StackingModalProps> = ({ onClose, numCycles, poxA

return unsignedTx.createTxWithSignature(resp.signatureVRS);
},
[coreNodeInfo, node.url, blockstackApp, poxInfo, balance, poxAddress, numCycles]
[coreNodeInfo, node.url, blockstackApp, poxInfo, amountToStack, poxAddress, numCycles]
);

const broadcastTx = async () => {
if (balance === null) return;

const broadcastActions: Omit<BroadcastTransactionArgs, 'transaction'> = {
amount: new BigNumber(balance),
amount: amountToStack,
isStackingCall: true,
onBroadcastSuccess: txId => {
dispatch(activeStackingTx({ txId }));
Expand Down
12 changes: 3 additions & 9 deletions app/modals/transaction/transaction-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { useHotkeys } from 'react-hotkeys-hook';

import { safeAwait } from '@utils/safe-await';
import { Api } from '@api/api';
import { STX_TRANSFER_TX_SIZE_BYTES } from '@constants/index';
import { STX_DECIMAL_PRECISION, STX_TRANSFER_TX_SIZE_BYTES } from '@constants/index';
import { RootState } from '@store/index';
import routes from '@constants/routes.json';
import { LedgerConnectStep } from '@hooks/use-ledger';
Expand Down Expand Up @@ -52,6 +52,7 @@ import { SignTxWithLedger } from './steps/sign-tx-with-ledger';
import { FailedBroadcastError } from './steps/failed-broadcast-error';
import { PreviewTransaction } from './steps/preview-transaction';
import { StacksTestnet } from '@stacks/network';
import { validateDecimalPrecision } from '../../utils/form/validate-decimals';

interface TxModalProps {
balance: string;
Expand Down Expand Up @@ -230,12 +231,7 @@ export const TransactionModal: FC<TxModalProps> = ({ balance, address }) => {
.test(
'test-has-less-than-or-equal-to-6-decimal-places',
'STX do not have more than 6 decimal places',
value => {
if (value === null || value === undefined) return false;
// Explicit base ensures BigNumber doesn't use exponential notation
const decimals = new BigNumber(value).toString(10).split('.')[1];
return decimals === undefined || decimals.length <= 6;
}
value => validateDecimalPrecision(STX_DECIMAL_PRECISION)(value)
)
.test(
'test-address-has-enough-balance',
Expand All @@ -246,8 +242,6 @@ export const TransactionModal: FC<TxModalProps> = ({ balance, address }) => {
// otherwise it'll render the error for this test
if (value === undefined) return true;
const enteredAmount = stxToMicroStx(value);
// console.log(enteredAmount.toString());
// console.log(balance.toString());
return enteredAmount.isLessThanOrEqualTo(balance);
}
)
Expand Down
5 changes: 1 addition & 4 deletions app/pages/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,7 @@ export const App: FC = ({ children }) => {
useEffect(() => {
async function run() {
if (!activeStackingTx || !address) return;
await watchContractExecution({
nodeUrl: activeNode.url,
txId: activeStackingTx,
});
await safeAwait(watchContractExecution({ nodeUrl: activeNode.url, txId: activeStackingTx }));
dispatch(fetchStackerInfo(address));
setTimeout(() => dispatch(removeStackingTx()), 2000);
}
Expand Down
13 changes: 2 additions & 11 deletions app/pages/home/home.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { FC, useRef, useCallback, useEffect } from 'react';
import React, { FC, useRef, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Spinner } from '@blockstack/ui';
import { useHotkeys } from 'react-hotkeys-hook';

import { Api } from '@api/api';
import { microStxToStx } from '@utils/unit-convert';
import { increment, decrement } from '@utils/mutate-numbers';
import { RootState } from '@store/index';
import { openInExplorer } from '@utils/external-links';
Expand Down Expand Up @@ -110,13 +109,6 @@ export const Home: FC = () => {
useHotkeys('j', () => focusTxDomNode(increment), [txs, pendingTxs]);
useHotkeys('k', () => focusTxDomNode(decrement), [txs, pendingTxs]);

useEffect(() => {
console.log({
blocksToNextCycle: nextCycleInfo?.blocksToNextCycle,
nextCycleStartBlock: nextCycleInfo?.nextCycleStartBlock,
});
}, [nextCycleInfo?.blocksToNextCycle]);

if (!address) return <Spinner />;

const txCount = txs.length + pendingTxs.length;
Expand Down Expand Up @@ -167,7 +159,7 @@ export const Home: FC = () => {
[HomeCardState.LoadingResources]: <StackingLoading />,
[HomeCardState.NotEnoughStx]: (
<StackingPromoCard
minRequiredStx={microStxToStx(stackingDetails?.min_amount_ustx || 0).toNumber()}
minRequiredMicroStx={stackingDetails?.paddedMinimumStackingAmountMicroStx || 0}
/>
),
[HomeCardState.EligibleToParticipate]: <StackingParticipationCard />,
Expand All @@ -187,7 +179,6 @@ export const Home: FC = () => {
<>
{receiveModalOpen && <ReceiveStxModal address={address} />}
{txModalOpen && <TransactionModal balance={spendableBalance || '0'} address={address} />}
{String(HomeCardState[stackingCardState])}
<HomeLayout
transactionList={transactionList}
balanceCard={balanceCard}
Expand Down
15 changes: 10 additions & 5 deletions app/pages/stacking/components/stacking-info-card.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import React, { FC } from 'react';
import { BigNumber } from 'bignumber.js';
import dayjs from 'dayjs';

import { Flex, FlexProps, Text } from '@blockstack/ui';

import { Hr } from '@components/hr';

import { ExplainerTooltip } from '@components/tooltip';
import { toHumanReadableStx } from '@utils/unit-convert';
import dayjs from 'dayjs';

interface StackingInfoCardProps extends FlexProps {
cycles: number;
duration: string;
startDate: Date;
balance: string | null;
blocksPerCycle: number;
balance: BigNumber | null;
}

export const StackingInfoCard: FC<StackingInfoCardProps> = props => {
const { cycles, duration, balance, startDate } = props;
const { cycles, duration, balance, blocksPerCycle, startDate } = props;

const amountToBeStacked = balance === null ? new BigNumber(0) : balance;

return (
<Flex
flexDirection="column"
Expand All @@ -33,7 +38,7 @@ export const StackingInfoCard: FC<StackingInfoCardProps> = props => {
<Flex flexDirection="column" px={['loose', 'extra-loose']} pt="extra-loose" pb="base-loose">
<Text textStyle="body.large.medium">You'll lock</Text>
<Text fontSize="24px" fontWeight={600} letterSpacing="-0.04em" mt="extra-tight">
{toHumanReadableStx(balance || 0)}
{toHumanReadableStx(amountToBeStacked.toString())}
</Text>
</Flex>
<Hr />
Expand All @@ -44,7 +49,7 @@ export const StackingInfoCard: FC<StackingInfoCardProps> = props => {
Cycles
</Text>
<ExplainerTooltip>
One cycle lasts 1000 blocks on the bitcoin blockchain
One cycle lasts {blocksPerCycle} blocks on the Bitcoin blockchain
</ExplainerTooltip>
</Flex>
<Text textAlign="right">{cycles}</Text>
Expand Down
Loading

0 comments on commit 30a661d

Please sign in to comment.