Skip to content

Commit

Permalink
fix: stacking flow
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Oct 26, 2020
1 parent 49d5e50 commit f00dd99
Show file tree
Hide file tree
Showing 13 changed files with 208 additions and 125 deletions.
38 changes: 38 additions & 0 deletions app/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
MempoolTransaction,
AddressBalanceResponse,
CoreNodePoxResponse,
CoreNodeInfoResponse,
NetworkBlockTimesResponse,
} from '@blockstack/stacks-blockchain-api-types';

export class Api {
Expand Down Expand Up @@ -42,4 +44,40 @@ export class Api {
async getNodeStatus() {
return axios.post(urljoin(this.baseUrl, `/extended/v1/status`));
}

async getCoreDetails() {
return axios.get<CoreNodeInfoResponse>(urljoin(this.baseUrl, `/v2/info`));
}

async getNetworkBlockTimes() {
return axios.get<NetworkBlockTimesResponse>(
urljoin(this.baseUrl, `/extended/v1/info/network_block_times`)
);
}

async callReadOnly({
contract,
functionName,
args,
}: {
contract: string;
functionName: string;
args: string[];
}) {
const [contractAddress, contractName] = contract.split('.');
const url = urljoin(
this.baseUrl,
`/v2/contracts/call-read/${contractAddress}/${contractName}/${functionName}`
);
const body = {
sender: 'ST384HBMC97973427QMM58NY2R9TTTN4M599XM5TD',
arguments: args,
};
const response = await axios.post(url, body, {
headers: {
'Content-Type': 'application/json',
},
});
return response.data.result as string;
}
}
2 changes: 1 addition & 1 deletion app/components/home/stacking-promo-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const StackingPromoCard: FC<StackingPromoCardProps> = ({ minRequiredStx }
Earn Bitcoin rewards
</Text>
<Text display="block" mt="tight" textAlign="center" maxWidth="320px" mx="auto">
You’ll earn Bitcoin when you temporarily lock {minRequiredStx} STX or more
You’ll earn Bitcoin when you temporarily lock {Math.ceil(minRequiredStx)} STX or more
</Text>
<Button
size="md"
Expand Down
4 changes: 2 additions & 2 deletions app/components/home/transaction-list/transaction-icon.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { FC } from 'react';
import { Flex, FlexProps, Spinner, FailedIcon } from '@blockstack/ui';
import { Flex, FlexProps, Box, Spinner, FailedIcon } from '@blockstack/ui';

import { SentArrow } from '@components/icons/sent-arrow';
import { ReceivedArrow } from '@components/icons/received-arrow';
Expand All @@ -14,7 +14,7 @@ const iconMap: Record<TransactionIconVariants, () => JSX.Element> = {
locked: LockedIcon,
failed: () => <FailedIcon size="16px" />,
pending: () => <Spinner size="xs" color="#5548FF" />,
default: () => <></>,
default: () => <Box width="16px" height="16px" borderRadius="50%" backgroundColor="ink.100" />,
};

function getTxTypeIcon(direction: TransactionIconVariants) {
Expand Down
10 changes: 5 additions & 5 deletions app/components/home/transaction-list/transaction-list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ import { useHover, useFocus } from 'use-events';
import { Box, Text, useClipboard } from '@blockstack/ui';
import { Transaction } from '@blockstack/stacks-blockchain-api-types';

import { RootState } from '@store/index';
import { selectPoxInfo } from '@store/stacking';
import { capitalize } from '@utils/capitalize';
import { getStxTxDirection } from '@utils/get-stx-transfer-direction';
import { sumStxTxTotal } from '@utils/sum-stx-tx-total';
import { TransactionIcon, TransactionIconVariants } from './transaction-icon';
import { toHumanReadableStx } from '@utils/unit-convert';
import { makeExplorerLink } from '@utils/external-links';
import { getRecipientAddress, isLockTx } from '@utils/tx-utils';

import {
createTxListContextMenu,
registerHandler,
deregisterHandler,
} from './transaction-list-context-menu';
import { makeExplorerLink } from '@utils/external-links';
import { getRecipientAddress, isLockTx } from '@utils/tx-utils';
import { TransactionListItemContainer } from './transaction-list-item-container';
import { RootState } from '@store/index';
import { selectPoxInfo } from '@store/stacking';

const dateOptions = {
year: 'numeric',
Expand Down Expand Up @@ -70,7 +70,7 @@ export const TransactionListItem: FC<TransactionListItemProps> = props => {
const transactionTitle = useCallback(() => {
if (tx.tx_type === 'token_transfer') return capitalize(direction);
return capitalize(tx.tx_type).replace('_', ' ');
}, [tx.tx_id]);
}, [direction, tx.tx_type]);

const sumPrefix = direction === 'sent' ? '−' : '';
const memo =
Expand Down
12 changes: 5 additions & 7 deletions app/components/home/transaction-list/transaction-list-title.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import React, { FC } from 'react';
import { Text } from '@blockstack/ui';

export const TransactionListTitle: FC = ({ children }) => {
return (
<Text display="block" textStyle="body.large.medium" mb="loose">
{children}
</Text>
);
};
export const TransactionListTitle: FC = ({ children }) => (
<Text display="block" textStyle="body.large.medium" mb="loose">
{children}
</Text>
);
65 changes: 42 additions & 23 deletions app/modals/stacking/stacking-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ import { DecryptWalletForm } from './steps/decrypt-wallet-form';
import { SignTxWithLedger } from './steps/sign-tx-with-ledger';
import { FailedBroadcastError } from './steps/failed-broadcast-error';
import { StackingSuccess } from './steps/stacking-success';
import { selectPoxInfo } from '@store/stacking';
import { selectCoreNodeInfo, selectPoxInfo } from '@store/stacking';
import {
makeUnsignedContractCall,
StacksTransaction,
makeContractCall,
TransactionSigner,
createStacksPrivateKey,
} from '@blockstack/stacks-transactions';
import { broadcastStxTransaction } from '@store/transaction';
import { broadcastTransaction } from '@store/transaction';
import { selectActiveNodeApi } from '@store/stacks-node';
import { selectAddressBalance } from '@store/address';

Expand Down Expand Up @@ -71,17 +71,25 @@ export const StackingModal: FC<StackingModalProps> = ({ onClose, numCycles, poxA
// const [loading, setLoading] = useState(false);
const [blockstackApp, setBlockstackApp] = useState<null | BlockstackApp>(null);

const { encryptedMnemonic, salt, walletType, publicKey, poxInfo, node, balance } = useSelector(
(state: RootState) => ({
salt: selectSalt(state),
encryptedMnemonic: selectEncryptedMnemonic(state),
walletType: selectWalletType(state),
publicKey: selectPublicKey(state),
poxInfo: selectPoxInfo(state),
node: selectActiveNodeApi(state),
balance: selectAddressBalance(state),
})
);
const {
encryptedMnemonic,
salt,
walletType,
publicKey,
poxInfo,
node,
coreNodeInfo,
balance,
} = useSelector((state: RootState) => ({
salt: selectSalt(state),
encryptedMnemonic: selectEncryptedMnemonic(state),
walletType: selectWalletType(state),
publicKey: selectPublicKey(state),
poxInfo: selectPoxInfo(state),
coreNodeInfo: selectCoreNodeInfo(state),
node: selectActiveNodeApi(state),
balance: selectAddressBalance(state),
}));

const initialStep =
walletType === 'software'
Expand All @@ -94,6 +102,7 @@ export const StackingModal: FC<StackingModalProps> = ({ onClose, numCycles, poxA
if (!password || !encryptedMnemonic || !salt || !poxInfo || !balance) {
throw new Error('One of `password`, `encryptedMnemonic` or `salt` is missing');
}
if (coreNodeInfo === null) throw new Error('Stacking requires coreNodeInfo');
const poxClient = new Pox(node.url);
const { privateKey } = await decryptSoftwareWallet({
ciphertextMnemonic: encryptedMnemonic,
Expand All @@ -104,31 +113,41 @@ export const StackingModal: FC<StackingModalProps> = ({ onClose, numCycles, poxA
const txOptions = poxClient.getLockTxOptions({
cycles: numCycles,
poxAddress,
amountMicroSTX: balanceBN,
amountMicroStx: balanceBN,
contract: poxInfo.contract_id,
burnBlockHeight: coreNodeInfo.burn_block_height,
});
const tx = await makeContractCall({
...txOptions,
senderKey: privateKey,
});
const tx = await makeContractCall({ ...txOptions, senderKey: privateKey });
poxClient.modifyLockTxFee({ tx, amountMicroStx: balanceBN });
const signer = new TransactionSigner(tx);
signer.signOrigin(createStacksPrivateKey(privateKey));
return tx;
}, [encryptedMnemonic, password, salt, numCycles, balance, poxInfo, poxAddress, node.url]);
}, [
password,
encryptedMnemonic,
salt,
poxInfo,
balance,
coreNodeInfo,
node.url,
numCycles,
poxAddress,
]);

const createLedgerWalletTx = useCallback(
async (options: { publicKey: Buffer }): Promise<StacksTransaction> => {
if (coreNodeInfo === null) throw new Error('Stacking requires coreNodeInfo');
const poxClient = new Pox(node.url);
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: balanceBN,
poxAddress,
cycles: numCycles,
contract: poxInfo.contract_id,
burnBlockHeight: coreNodeInfo.burn_block_height,
});

const unsignedTx = await makeUnsignedContractCall({
Expand All @@ -149,7 +168,7 @@ export const StackingModal: FC<StackingModalProps> = ({ onClose, numCycles, poxA

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

const closeModal = () => onClose();
Expand Down Expand Up @@ -177,7 +196,7 @@ export const StackingModal: FC<StackingModalProps> = ({ onClose, numCycles, poxA

if (transaction) {
setIsDecrypting(false);
dispatch(broadcastStxTransaction({ ...broadcastActions, transaction }));
dispatch(broadcastTransaction({ ...broadcastActions, transaction }));
}
}

Expand All @@ -195,7 +214,7 @@ export const StackingModal: FC<StackingModalProps> = ({ onClose, numCycles, poxA
}

if (transaction) {
dispatch(broadcastStxTransaction({ ...broadcastActions, transaction }));
dispatch(broadcastTransaction({ ...broadcastActions, transaction }));
}
}
};
Expand Down
6 changes: 3 additions & 3 deletions app/modals/transaction/transaction-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
selectWalletType,
} from '@store/keys';
import { validateAddressChain } from '../../crypto/validate-address-net';
import { broadcastStxTransaction } from '@store/transaction';
import { broadcastTransaction } from '@store/transaction';
import { stxToMicroStx, microStxToStx } from '@utils/unit-convert';

import { stacksNetwork } from '../../environment';
Expand Down Expand Up @@ -163,7 +163,7 @@ export const TransactionModal: FC<TxModalProps> = ({ balance, address }) => {

if (transaction) {
setIsDecrypting(false);
dispatch(broadcastStxTransaction({ ...broadcastActions, transaction }));
dispatch(broadcastTransaction({ ...broadcastActions, transaction }));
}
}

Expand All @@ -183,7 +183,7 @@ export const TransactionModal: FC<TxModalProps> = ({ balance, address }) => {
}

if (transaction) {
dispatch(broadcastStxTransaction({ ...broadcastActions, transaction }));
dispatch(broadcastTransaction({ ...broadcastActions, transaction }));
}
}
};
Expand Down
3 changes: 2 additions & 1 deletion app/pages/app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { useCallback, useEffect, FC } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import urljoin from 'url-join';

import { connectWebSocketClient } from '@stacks/blockchain-api-client';
import { useNavigatorOnline } from '@hooks/use-navigator-online';
import { BetaNotice } from '@components/beta-notice';
Expand All @@ -15,7 +17,6 @@ import { selectAddress } from '@store/keys';
import { safeAwait } from '@utils/safe-await';
import { Api } from '@api/api';
import { selectActiveNodeApi } from '@store/stacks-node';
import urljoin from 'url-join';
import { useInterval } from '@hooks/use-interval';
import { selectPendingTransactions } from '@store/pending-transaction';
import {
Expand Down
30 changes: 14 additions & 16 deletions app/store/stacking/stacking.actions.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { selectActiveNodeApi } from '../stacks-node/stacks-node.reducer';

import { selectActiveNodeApi } from '@store/stacks-node/stacks-node.reducer';
import { RootState } from '@store/index';
import { Api } from '@api/api';
import { Configuration, InfoApi } from '@stacks/blockchain-api-client';
import { Pox } from '@utils/stacking/pox';

const createApi = (url: string) => {
const config = new Configuration({
fetchApi: fetch,
basePath: url,
});
return new InfoApi(config);
};
import { safeAwait } from '@utils/safe-await';

export const fetchStackingInfo = createAsyncThunk('stacking/details', async (_arg, thunkApi) => {
const state = thunkApi.getState() as RootState;
Expand All @@ -25,18 +18,17 @@ export const fetchCoreDetails = createAsyncThunk(
async (_arg, thunkApi) => {
const state = thunkApi.getState() as RootState;
const network = selectActiveNodeApi(state);
const api = createApi(network.url);
return api.getCoreApiInfo();
const resp = await new Api(network.url).getCoreDetails();
return resp.data;
}
);

export const fetchBlocktimeInfo = createAsyncThunk(
'stacking/block-time-details',
async (_arg, thunkApi) => {
const state = thunkApi.getState() as RootState;
const network = selectActiveNodeApi(state);
const api = createApi(network.url);
return api.getNetworkBlockTimes();
const resp = await new Api(network.url).getNetworkBlockTimes();
return resp.data;
}
);

Expand All @@ -46,6 +38,12 @@ export const fetchStackerInfo = createAsyncThunk(
const state = thunkApi.getState() as RootState;
const node = selectActiveNodeApi(state);
const poxClient = new Pox(node.url);
return poxClient.getStackerInfo(address);
const [error, resp] = await safeAwait(poxClient.getStackerInfo(address));
if (error) {
console.log(error);
throw error;
}
if (resp) return resp;
return null;
}
);
Loading

0 comments on commit f00dd99

Please sign in to comment.