diff --git a/app/api/api.ts b/app/api/api.ts index 631946458..56d129432 100644 --- a/app/api/api.ts +++ b/app/api/api.ts @@ -5,6 +5,7 @@ import { TransactionResults, MempoolTransaction, AddressBalanceResponse, + CoreNodePoxResponse, } from '@blockstack/stacks-blockchain-api-types'; export class Api { @@ -29,9 +30,14 @@ export class Api { } async getFaucetStx(address: string) { - return axios.post(urljoin(this.baseUrl, `/extended/v1/debug/faucet?address=${address}`), { - address, - }); + return axios.post( + urljoin(this.baseUrl, `/extended/v1/debug/faucet?address=${address}&stacking=true`), + { address } + ); + } + + async getPoxInfo() { + return axios.get(urljoin(this.baseUrl, `/v2/pox`)); } async getNodeStatus() { diff --git a/app/constants/index.ts b/app/constants/index.ts index 2427f39e4..b5e97b4b8 100644 --- a/app/constants/index.ts +++ b/app/constants/index.ts @@ -10,14 +10,12 @@ 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 NETWORK = process.env.STX_NETWORK; +export const NETWORK = process.env.STX_NETWORK as 'mainnet' | 'testnet'; export const MAX_STACKING_CYCLES = 12; export const MIN_STACKING_CYCLES = 1; -export const REQUIRED_STX_FOR_STACKING = 100_000; - export const SUPPORTED_BTC_ADDRESS_FORMATS = ['p2pkh', 'p2sh'] as const; export const features = { diff --git a/app/modals/stacking/stacking-modal-last-step.tsx b/app/modals/stacking/stacking-modal-last-step.tsx new file mode 100644 index 000000000..7fbc707d4 --- /dev/null +++ b/app/modals/stacking/stacking-modal-last-step.tsx @@ -0,0 +1,40 @@ +import React, { FC } from 'react'; +import { LargeCheckmark } from '@components/icons/large-checkmark'; +import { Button, Flex, Modal, Text } from '@blockstack/ui'; +import { useHotkeys } from 'react-hotkeys-hook'; +import { WalletType } from '../../types/wallet-type'; + +interface StackingModalProps { + walletType: WalletType; + onClose(): void; +} + +export const StackingModal: FC = props => { + const { onClose } = props; + useHotkeys('esc', onClose, [onClose]); + + return ( + + + + + + You locked your STX for 2 weeks + + + You’ll receive Bitcoin twice, at the end of every cycle. + + + + + + ); +}; diff --git a/app/modals/stacking/stacking-modal-layout.tsx b/app/modals/stacking/stacking-modal-layout.tsx new file mode 100644 index 000000000..356868221 --- /dev/null +++ b/app/modals/stacking/stacking-modal-layout.tsx @@ -0,0 +1,47 @@ +import React, { FC } from 'react'; +import { Flex, Text, CloseIcon, Button, ButtonProps } from '@blockstack/ui'; + +export const modalStyle = { + minWidth: ['100%', '488px'], +}; + +interface StackingModalHeaderProps { + onSelectClose: () => void; +} + +export const StackingModalHeader: FC = ({ children, onSelectClose }) => ( + + + {children} + + + +); + +export const StackingModalFooter: FC = ({ children }) => ( + + {children} + +); + +export const StackingModalButton: FC = ({ children, ...props }) => ( + +); diff --git a/app/modals/stacking/stacking-modal.tsx b/app/modals/stacking/stacking-modal.tsx index 24e243fe4..64e807eca 100644 --- a/app/modals/stacking/stacking-modal.tsx +++ b/app/modals/stacking/stacking-modal.tsx @@ -1,36 +1,244 @@ -import React, { FC } from 'react'; -import { LargeCheckmark } from '@components/icons/large-checkmark'; -import { Button, Flex, Modal, Text } from '@blockstack/ui'; +import React, { FC, useState, useCallback } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { Modal } from '@blockstack/ui'; +import { useHistory } from 'react-router-dom'; +import log from 'electron-log'; +import BlockstackApp, { LedgerError } from '@zondax/ledger-blockstack'; import { useHotkeys } from 'react-hotkeys-hook'; -import { WalletType } from '../../types/wallet-type'; + +import { RootState } from '@store/index'; +import routes from '@constants/routes.json'; +import { selectPublicKey } from '@store/keys/keys.reducer'; +import { LedgerConnectStep } from '@hooks/use-ledger'; +import { safeAwait } from '@utils/safe-await'; +import { homeActions } from '@store/home/home.reducer'; +import { + selectEncryptedMnemonic, + selectSalt, + decryptSoftwareWallet, + selectWalletType, +} from '@store/keys'; + +import { + StackingModalHeader, + StackingModalFooter, + StackingModalButton, + modalStyle, +} from './stacking-modal-layout'; +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'; + +enum StackingModalStep { + DecryptWalletAndSend, + SignWithLedgerAndSend, + StackingSuccess, + NetworkError, +} + +type ModalComponents = () => Record<'header' | 'body' | 'footer', JSX.Element>; interface StackingModalProps { - walletType: WalletType; onClose(): void; } -export const StackingModal: FC = props => { - const { onClose } = props; - useHotkeys('esc', () => onClose()); +export const StackingModal: FC = ({ onClose }) => { + const dispatch = useDispatch(); + const history = useHistory(); + useHotkeys('esc', () => void dispatch(homeActions.closeTxModal())); + + const [password, setPassword] = useState(''); + const [hasSubmitted, setHasSubmitted] = useState(false); + + const [decryptionError, setDecryptionError] = useState(null); + const [isDecrypting, setIsDecrypting] = useState(false); + // const [loading, setLoading] = useState(false); + const [blockstackApp, setBlockstackApp] = useState(null); + + const { encryptedMnemonic, salt, walletType, publicKey } = useSelector((state: RootState) => ({ + salt: selectSalt(state), + encryptedMnemonic: selectEncryptedMnemonic(state), + walletType: selectWalletType(state), + publicKey: selectPublicKey(state), + })); + + const initialStep = + walletType === 'software' + ? StackingModalStep.DecryptWalletAndSend + : StackingModalStep.SignWithLedgerAndSend; + const [step, setStep] = useState(initialStep); + + const createSoftwareWalletTx = useCallback(async () => { + if (!password || !encryptedMnemonic || !salt) { + throw new Error('One of `password`, `encryptedMnemonic` or `salt` is missing'); + } + const { privateKey } = await decryptSoftwareWallet({ + ciphertextMnemonic: encryptedMnemonic, + salt, + password, + }); + console.log({ privateKey }); + // return makeSTXTokenTransfer({ ...options, senderKey: privateKey }); + }, [encryptedMnemonic, password, salt]); + + const createLedgerWalletTx = useCallback( + async (options: { publicKey: Buffer }) => { + console.log(options); + if (!publicKey || !blockstackApp) + throw new Error('`publicKey` or `blockstackApp` is not defined'); + // 1. Form unsigned contract call transaction + + // 2. Sign transaction + // const resp = await blockstackApp.sign(`m/44'/5757'/0'/0/0`, unsignedTx.serialize()); + + // 3. Add signature to unsigned tx + }, + [blockstackApp, publicKey] + ); + + const broadcastTx = async () => { + setHasSubmitted(true); + if (walletType === 'software') { + setIsDecrypting(true); + + const [error, transaction] = await safeAwait(createSoftwareWalletTx()); + + if (error) { + setIsDecrypting(false); + setDecryptionError('Unable to decrypt wallet'); + return; + } + + if (transaction) { + setIsDecrypting(false); + // dispatch(broadcastStxTransaction({ ...broadcastActions, transaction })); + } + } + + if (walletType === 'ledger') { + if (publicKey === null) { + log.error('Tried to create Ledger transaction without persisted private key'); + return; + } + + const [error, transaction] = await safeAwait(createLedgerWalletTx({})); + + if (error) { + setHasSubmitted(false); + return; + } + + if (transaction) { + // dispatch(broadcastStxTransaction({ ...broadcastActions, transaction })); + } + } + }; + + const [ledgerConnectStep, setLedgerConnectStep] = useState(LedgerConnectStep.Disconnected); + + const closeModal = () => onClose(); + + const setBlockstackAppCallback = useCallback( + blockstackApp => setBlockstackApp(blockstackApp), + [] + ); + const updateStep = useCallback(step => setLedgerConnectStep(step), []); + + const txFormStepMap: Record = { + [StackingModalStep.DecryptWalletAndSend]: () => ({ + header: ( + Confirm and lock + ), + body: ( + setPassword(password)} + onForgottenPassword={() => { + closeModal(); + history.push(routes.SETTINGS); + }} + hasSubmitted={hasSubmitted} + decryptionError={decryptionError} + /> + ), + footer: ( + + closeModal()}> + Close + + broadcastTx()} + > + Send transaction + + + ), + }), + [StackingModalStep.SignWithLedgerAndSend]: () => ({ + header: ( + Confirm on your Ledger + ), + body: , + footer: ( + + { + setHasSubmitted(false); + closeModal(); + }} + > + Close + + { + if (blockstackApp === null) return; + void broadcastTx(); + }} + > + Sign transaction + + + ), + }), + + [StackingModalStep.StackingSuccess]: () => ({ + header: , + body: , + footer: ( + + Close + + ), + }), + + [StackingModalStep.NetworkError]: () => ({ + header: , + body: , + footer: ( + + + Close + + setStep(initialStep)}>Try again + + ), + }), + }; + + const { header, body, footer } = txFormStepMap[step](); return ( - - - - - - You locked your STX for 2 weeks - - - You’ll receive Bitcoin twice, at the end of every cycle. - - + + {body} ); }; diff --git a/app/modals/stacking/steps/decrypt-wallet-form.tsx b/app/modals/stacking/steps/decrypt-wallet-form.tsx new file mode 100644 index 000000000..e04bfd6b5 --- /dev/null +++ b/app/modals/stacking/steps/decrypt-wallet-form.tsx @@ -0,0 +1,47 @@ +import React, { FC } from 'react'; +import { Box, Input, Text } from '@blockstack/ui'; +import { ErrorLabel } from '@components/error-label'; +import { ErrorText } from '@components/error-text'; + +interface DecryptWalletFormProps { + hasSubmitted: boolean; + decryptionError: string | null; + onForgottenPassword(): void; + onSetPassword(password: string): void; +} + +type Props = FC; + +export const DecryptWalletForm: Props = props => { + const { onSetPassword, decryptionError, hasSubmitted, onForgottenPassword } = props; + + const handlePasswordInput = (e: React.FormEvent) => { + e.preventDefault(); + const pass = e.currentTarget.value; + onSetPassword(pass); + }; + return ( + + Enter your password to confirm your transaction + + {hasSubmitted && decryptionError && ( + + Password entered is incorrect + + )} + + Forgot password?{' '} + + Reset your wallet + {' '} + to set a new password. + + + ); +}; diff --git a/app/modals/stacking/steps/failed-broadcast-error.tsx b/app/modals/stacking/steps/failed-broadcast-error.tsx new file mode 100644 index 000000000..f717575b7 --- /dev/null +++ b/app/modals/stacking/steps/failed-broadcast-error.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Flex, Box, Text } from '@blockstack/ui'; + +import failedCrossSvg from '../../../assets/images/failed-cross.svg'; + +export const FailedBroadcastError = () => ( + + + + + + Your transaction failed to verify + + + Please make sure you are signing your transaction with the same Ledger or Secret Key used to + set up your wallet. + + +); diff --git a/app/modals/stacking/steps/sign-tx-with-ledger.tsx b/app/modals/stacking/steps/sign-tx-with-ledger.tsx new file mode 100644 index 000000000..4eb9a934a --- /dev/null +++ b/app/modals/stacking/steps/sign-tx-with-ledger.tsx @@ -0,0 +1,42 @@ +import React, { FC, useEffect } from 'react'; +import { LedgerConnectInstructions } from '@components/ledger/ledger-connect-instructions'; +import { Box } from '@blockstack/ui'; +import { useLedger, LedgerConnectStep } from '@hooks/use-ledger'; +import BlockstackApp from '@zondax/ledger-blockstack'; +import { delay } from '@utils/delay'; + +interface SignTxWithLedgerProps { + onLedgerConnect(app: BlockstackApp): void; + updateStep(step: LedgerConnectStep): void; +} + +export const SignTxWithLedger: FC = ({ onLedgerConnect, updateStep }) => { + const { transport, step } = useLedger(); + + useEffect(() => { + updateStep(step); + + async function run() { + const usbTransport = transport; + + if (usbTransport === null) return; + + const app = new BlockstackApp(usbTransport); + + try { + await app.getVersion(); + await delay(1); + onLedgerConnect(app); + } catch (e) { + console.log(e); + } + } + void run(); + }, [transport, step, onLedgerConnect, updateStep]); + + return ( + + + + ); +}; diff --git a/app/modals/stacking/steps/stacking-success.tsx b/app/modals/stacking/steps/stacking-success.tsx new file mode 100644 index 000000000..9bd0d01ef --- /dev/null +++ b/app/modals/stacking/steps/stacking-success.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react'; +import { Flex, Text } from '@blockstack/ui'; +import { LargeCheckmark } from '@components/icons/large-checkmark'; + +export const StackingSuccess: FC = () => ( + + + + + + You locked your STX for 2 weeks + + + You’ll receive Bitcoin twice, at the end of every cycle. + + +); diff --git a/app/pages/app.tsx b/app/pages/app.tsx index 7c4b93185..bbd3004b0 100644 --- a/app/pages/app.tsx +++ b/app/pages/app.tsx @@ -1,7 +1,6 @@ -import React, { useCallback, useEffect, FC, useState } from 'react'; +import React, { useCallback, useEffect, FC } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { connectWebSocketClient } from '@stacks/blockchain-api-client'; - import { useNavigatorOnline } from '@hooks/use-navigator-online'; import { BetaNotice } from '@components/beta-notice'; import { @@ -14,15 +13,15 @@ import { RootState } from '@store/index'; import { TitleBar } from '@components/title-bar'; import { selectAddress } from '@store/keys'; import { safeAwait } from '@utils/safe-await'; -import { Api } from '../api/api'; +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/pending-transaction.reducer'; +import { useInterval } from '@hooks/use-interval'; +import { selectPendingTransactions } from '@store/pending-transaction'; +import { fetchBlocktimeInfo, fetchCoreDetails, fetchStackingInfo } from '@store/stacking'; export const App: FC = ({ children }) => { const dispatch = useDispatch(); - const [webSocket, setWebSocket] = useState('Disconnected'); const { address, activeNode, pendingTxs } = useSelector((state: RootState) => ({ address: selectAddress(state), @@ -64,16 +63,20 @@ export const App: FC = ({ children }) => { initAppWithStxAddressInfo(); }, [address, activeNode, initAppWithStxAddressInfo]); + useEffect(() => { + dispatch(fetchStackingInfo()); + dispatch(fetchCoreDetails()); + dispatch(fetchBlocktimeInfo()); + }, [dispatch]); + useEffect(() => { const wsUrl = new URL(activeNode.url); wsUrl.protocol = 'ws:'; async function run() { const client = await connectWebSocketClient( urljoin(wsUrl.toString(), 'extended', 'v1', 'ws') - ).finally(() => { - setWebSocket('Disconnected'); - }); - setWebSocket('Connected'); + ); + if (!address) return; await client.subscribeAddressBalanceUpdates(address, ({ address, balance }) => { dispatch(updateAddressBalance({ address, balance })); diff --git a/app/pages/home/home.tsx b/app/pages/home/home.tsx index 2b1c5b2a9..fe91205bd 100644 --- a/app/pages/home/home.tsx +++ b/app/pages/home/home.tsx @@ -16,8 +16,8 @@ import { selectTransactionsLoading, selectTransactionListFetchError, } from '@store/transaction'; -import { REQUIRED_STX_FOR_STACKING } from '@constants/index'; import { selectPendingTransactions } from '@store/pending-transaction'; +import { selectPoxInfo } from '@store/stacking'; import { homeActions, selectTxModalOpen, selectReceiveModalOpen } from '@store/home'; import { increment, decrement } from '@utils/mutate-numbers'; import { @@ -48,6 +48,7 @@ export const Home: FC = () => { txListFetchError, receiveModalOpen, activeNode, + stackingDetails, } = useSelector((state: RootState) => ({ address: selectAddress(state), txs: selectTransactionList(state), @@ -58,10 +59,11 @@ export const Home: FC = () => { loadingTxs: selectTransactionsLoading(state), txListFetchError: selectTransactionListFetchError(state), activeNode: selectActiveNodeApi(state), + stackingDetails: selectPoxInfo(state), })); const focusedTxIdRef = useRef(null); - const txDomNodeRefMap = useRef<{ [txId: string]: HTMLButtonElement }>({}); + const txDomNodeRefMap = useRef>({}); const focusTxDomNode = useCallback( (shift: (i: number) => number) => { @@ -89,7 +91,9 @@ export const Home: FC = () => { if (!address) return ; const meetsMinStackingThreshold = - balance !== null && new BigNumber(balance).isGreaterThan(REQUIRED_STX_FOR_STACKING); + balance !== null && + stackingDetails !== null && + new BigNumber(balance).isGreaterThan(stackingDetails.min_amount_ustx); const txCount = txs.length + pendingTxs.length; diff --git a/app/pages/stacking/stacking.tsx b/app/pages/stacking/stacking.tsx index d6c5208c1..3ab43ce32 100644 --- a/app/pages/stacking/stacking.tsx +++ b/app/pages/stacking/stacking.tsx @@ -16,6 +16,8 @@ import { useSelector } from 'react-redux'; import { RootState } from '@store/index'; import { selectWalletType } from '@store/keys'; +import { selectActiveNodeApi } from '@store/stacks-node'; + enum Step { ChooseCycles = 'Choose your duration', ChooseBtcAddress = 'Add your Bitcoin address', @@ -40,20 +42,21 @@ export const Stacking: FC = () => { walletType: selectWalletType(state), })); - const isComplete = (step: Step) => stepConfirmation[step] === 'complete'; - const updateStep = (step: Step, to: StepState) => setStepConfirmation(state => ({ ...state, [step]: to })); const dateRef = useRef(new Date()); - const formComplete = - stepConfirmation[Step.ChooseCycles] === 'complete' && - stepConfirmation[Step.ChooseBtcAddress] === 'complete' && - !!btcAddress; + const isComplete = (step: Step) => stepConfirmation[step] === 'complete'; + + const formComplete = [Step.ChooseCycles, Step.ChooseBtcAddress].every(isComplete) && !!btcAddress; const stackingInfoCard = ( - + ); const stackingForm = ( @@ -84,7 +87,7 @@ export const Stacking: FC = () => { return ( <> - {modalOpen && setModalOpen(false)} />} + {modalOpen && setModalOpen(false)} />} } stackingInfoCard={stackingInfoCard} diff --git a/app/store/index.ts b/app/store/index.ts index 12ac45bc1..b34377a9c 100644 --- a/app/store/index.ts +++ b/app/store/index.ts @@ -8,6 +8,7 @@ import { addressReducer, AddressState } from './address'; import { HomeState, homeSlice } from './home'; import { pendingTransactionReducer, PendingTransactionState } from './pending-transaction'; import { stacksNodeReducer, StacksNodeState } from './stacks-node'; +import { StackingState, stackingSlice } from './stacking'; import { reduxPersistElectronStore } from './persist-middleware'; import { PersistConfig } from 'redux-persist'; import { configureStore } from './configureStore'; @@ -20,6 +21,7 @@ export interface RootState { address: AddressState; home: HomeState; stacksNode: StacksNodeState; + stacking: StackingState; _persist: any; } @@ -47,6 +49,7 @@ export function createRootReducer({ history, keys }: RootReducerArgs) { address: addressReducer, home: homeSlice.reducer, stacksNode: stacksNodeReducer, + stacking: stackingSlice.reducer, }); } diff --git a/app/store/stacking/index.ts b/app/store/stacking/index.ts new file mode 100644 index 000000000..1fb4fcbd3 --- /dev/null +++ b/app/store/stacking/index.ts @@ -0,0 +1,2 @@ +export * from './stacking.reducer'; +export * from './stacking.actions'; diff --git a/app/store/stacking/stacking.actions.ts b/app/store/stacking/stacking.actions.ts new file mode 100644 index 000000000..850f8aeaa --- /dev/null +++ b/app/store/stacking/stacking.actions.ts @@ -0,0 +1,41 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { selectActiveNodeApi } from '../stacks-node/stacks-node.reducer'; +import { RootState } from '@store/index'; +import { Api } from '@api/api'; +import { Configuration, InfoApi } from '@stacks/blockchain-api-client'; +import fetch from 'cross-fetch'; + +const createApi = (url: string) => { + const config = new Configuration({ + fetchApi: fetch, + basePath: url, + }); + return new InfoApi(config); +}; + +export const fetchStackingInfo = createAsyncThunk('stacking/details', async (_arg, thunkApi) => { + const state = thunkApi.getState() as RootState; + const network = selectActiveNodeApi(state); + const resp = await new Api(network.url).getPoxInfo(); + return resp.data; +}); + +export const fetchCoreDetails = createAsyncThunk( + 'stacking/core-node-details', + async (_arg, thunkApi) => { + const state = thunkApi.getState() as RootState; + const network = selectActiveNodeApi(state); + const api = createApi(network.url); + return api.getCoreApiInfo(); + } +); + +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(); + } +); diff --git a/app/store/stacking/stacking.reducer.ts b/app/store/stacking/stacking.reducer.ts new file mode 100644 index 000000000..cba08d101 --- /dev/null +++ b/app/store/stacking/stacking.reducer.ts @@ -0,0 +1,61 @@ +import { + CoreNodePoxResponse, + CoreNodeInfoResponse, + NetworkBlockTimesResponse, +} from '@blockstack/stacks-blockchain-api-types'; +import { createSlice, PayloadAction, createSelector } from '@reduxjs/toolkit'; + +import { RootState } from '..'; +import { fetchStackingInfo, fetchCoreDetails, fetchBlocktimeInfo } from './stacking.actions'; +import { NETWORK } from '../../constants/index'; + +export interface StackingState { + poxInfo: CoreNodePoxResponse | null; + coreNodeInfo: CoreNodeInfoResponse | null; + blockTimeInfo: NetworkBlockTimesResponse | null; +} + +const initialState: StackingState = { + poxInfo: null, + coreNodeInfo: null, + blockTimeInfo: null, +}; + +export const stackingSlice = createSlice({ + name: 'stacking', + initialState, + reducers: {}, + extraReducers: { + [fetchStackingInfo.fulfilled.toString()]: (state, a: PayloadAction) => { + state.poxInfo = a.payload; + }, + [fetchCoreDetails.fulfilled.toString()]: (state, a: PayloadAction) => { + state.coreNodeInfo = a.payload; + }, + [fetchBlocktimeInfo.fulfilled.toString()]: (s, a: PayloadAction) => { + s.blockTimeInfo = a.payload; + }, + }, +}); + +export const stackingActions = stackingSlice.actions; + +export const selectStackingState = (state: RootState) => state.stacking; +export const selectPoxInfo = createSelector(selectStackingState, state => state.poxInfo); +export const selectCoreNodeInfo = createSelector(selectStackingState, state => state.coreNodeInfo); +export const selectBlocktimeInfo = createSelector( + selectStackingState, + state => state.blockTimeInfo +); + +// `rejection_votes_left_required` not returned by API +// eslint-disable-next-line @typescript-eslint/no-empty-function +export const stackingWillBeExecuted = createSelector(selectStackingState, () => {}); + +export const estimatedStackingCycleDuration = createSelector( + selectStackingState, + ({ poxInfo, blockTimeInfo }) => { + if (poxInfo === null || blockTimeInfo === null) return 0; + return poxInfo.reward_cycle_length * blockTimeInfo[NETWORK].target_block_time; + } +); diff --git a/package.json b/package.json index e6fd275f2..7f8e77af9 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "@babel/register": "7.11.5", "@blockstack/eslint-config": "1.0.5", "@blockstack/prettier-config": "0.0.6", - "@blockstack/stacks-blockchain-api-types": "0.14.4", + "@blockstack/stacks-blockchain-api-types": "0.25.3", "@commitlint/config-conventional": "11.0.0", "@types/argon2-browser": "1.12.0", "@types/bcryptjs": "2.4.2", diff --git a/yarn.lock b/yarn.lock index cea080ea7..cab9a89e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1144,10 +1144,10 @@ "@blockstack/stacks-transactions" "0.5.1" cross-fetch "^3.0.4" -"@blockstack/stacks-blockchain-api-types@0.14.4": - version "0.14.4" - resolved "https://registry.yarnpkg.com/@blockstack/stacks-blockchain-api-types/-/stacks-blockchain-api-types-0.14.4.tgz#9f79993b9dd2c7232215a67ccb10e77162853950" - integrity sha512-o7AlZToMjeyl32tMOQH1PtzpWGGoPMGb+kMvQ76Cy9PWB9bi+PMT4eoXNt0BLoHgUpktOhRyuNUIpPJhv3r7dQ== +"@blockstack/stacks-blockchain-api-types@0.25.3": + version "0.25.3" + resolved "https://registry.yarnpkg.com/@blockstack/stacks-blockchain-api-types/-/stacks-blockchain-api-types-0.25.3.tgz#e84ad2e7e6daffcd79ecf80a5c520b2e235079b1" + integrity sha512-HFoFyc7L3bINgGYLc6WDVhltlQ1lGlIvQhGRbCLnefm01t+j+COWrn72fcpFLEi6GKl4yPdBTUIZEeZ2jgRZCg== "@blockstack/stacks-blockchain-api-types@^0.5.3": version "0.5.3"