diff --git a/solidity/dashboard/src/components/copy-stake-steps/Step1.jsx b/solidity/dashboard/src/components/copy-stake-steps/Step1.jsx index f788cc89dc..f8f7d5f8ff 100644 --- a/solidity/dashboard/src/components/copy-stake-steps/Step1.jsx +++ b/solidity/dashboard/src/components/copy-stake-steps/Step1.jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react" +import React from "react" import TokenAmount from "../TokenAmount" import Button from "../Button" import { KeepLoadingIndicator } from "../Loadable" @@ -26,7 +26,6 @@ const styles = { } const CopyStakeStep1 = ({ - fetchDelegations, isFetching, delegations, decrementStep, @@ -34,10 +33,6 @@ const CopyStakeStep1 = ({ onSelectDelegation, selectedDelegation, }) => { - useEffect(() => { - fetchDelegations() - }, [fetchDelegations]) - return ( <>

Stake balances to be upgraded.

diff --git a/solidity/dashboard/src/pages/CopyStakePage.jsx b/solidity/dashboard/src/pages/CopyStakePage.jsx index 077d11520d..1783860acd 100644 --- a/solidity/dashboard/src/pages/CopyStakePage.jsx +++ b/solidity/dashboard/src/pages/CopyStakePage.jsx @@ -12,7 +12,6 @@ import { DECREMENT_STEP, SET_STRATEGY, SET_DELEGATION, - FETCH_DELEGATIONS_FROM_OLD_STAKING_CONTRACT_REQUEST, RESET_COPY_STAKE_FLOW, } from "../actions" import { connect } from "react-redux" @@ -72,7 +71,6 @@ const CopyStakePage = ({ isFetching={oldDelegationsFetching} selectedDelegation={selectedDelegation} onSelectDelegation={setDelegation} - fetchDelegations={fetchOldDelegations} /> ) case 2: @@ -135,8 +133,6 @@ const mapDispatchToProps = (dispatch) => { dispatch({ type: SET_STRATEGY, payload: strategy }), setDelegation: (delegation) => dispatch({ type: SET_DELEGATION, payload: delegation }), - fetchOldDelegations: () => - dispatch({ type: FETCH_DELEGATIONS_FROM_OLD_STAKING_CONTRACT_REQUEST }), undelegateOldStake: (delegation) => dispatch({ type: "copy-stake/undelegate_request", payload: delegation }), recoverOldStake: (delegation) => diff --git a/solidity/dashboard/src/pages/TokensPageContainer.jsx b/solidity/dashboard/src/pages/TokensPageContainer.jsx index b437cd3fb6..0f74a84e46 100644 --- a/solidity/dashboard/src/pages/TokensPageContainer.jsx +++ b/solidity/dashboard/src/pages/TokensPageContainer.jsx @@ -18,6 +18,8 @@ import { TOP_UP_INITIATED, TOP_UP_COMPLETED, } from "../reducers/tokens-page.reducer.js" +import { FETCH_DELEGATIONS_FROM_OLD_STAKING_CONTRACT_REQUEST } from "../actions" +import { connect } from "react-redux" import { TOKEN_STAKING_CONTRACT_NAME, TOKEN_GRANT_CONTRACT_NAME, @@ -25,6 +27,7 @@ import { } from "../constants/constants" import { isSameEthAddress } from "../utils/general.utils" import { sub, add } from "../utils/arithmetics.utils" +import { isEmptyArray } from "../utils/array.utils" import moment from "moment" import { createManagedGrantContractInstance, @@ -38,7 +41,7 @@ import Button from "../components/Button" import { useModal } from "../hooks/useModal" import CopyStakePage from "./CopyStakePage" -const TokensPageContainer = () => { +const TokensPageContainer = ({ oldDelegations, fetchOldDelegations }) => { useSubscribeToStakedEvent() useSubscribeToUndelegatedEvent() useSubscribeToRecoveredStakeEvent() @@ -55,24 +58,30 @@ const TokensPageContainer = () => { } }, [hash, dispatch]) + useEffect(() => { + fetchOldDelegations() + }, [fetchOldDelegations]) + const { openModal } = useModal() return ( <> - - - + + + )} @@ -83,14 +92,6 @@ const TokensPageContainer = () => { ) } -const TokensPageContainerWithContext = () => ( - - - -) - -export default React.memo(TokensPageContainerWithContext) - const useSubscribeToStakedEvent = () => { const { initializationPeriod, @@ -450,3 +451,29 @@ const useSubscribeToTopUpsEvents = () => { subscribeToTopUpCompleted ) } + +const mapStateToProps = ({ copyStake }) => { + const { oldDelegations } = copyStake + + return { oldDelegations } +} + +const mapDispatchToProps = (dispatch) => { + return { + fetchOldDelegations: () => + dispatch({ type: FETCH_DELEGATIONS_FROM_OLD_STAKING_CONTRACT_REQUEST }), + } +} + +const TokensPageContainerWithRedux = connect( + mapStateToProps, + mapDispatchToProps +)(TokensPageContainer) + +const TokensPageContainerWithContext = () => ( + + + +) + +export default React.memo(TokensPageContainerWithContext) diff --git a/solidity/dashboard/src/reducers/copy-stake.js b/solidity/dashboard/src/reducers/copy-stake.js index c349a7b5f7..9cf9e323a3 100644 --- a/solidity/dashboard/src/reducers/copy-stake.js +++ b/solidity/dashboard/src/reducers/copy-stake.js @@ -8,6 +8,7 @@ import { FETCH_DELEGATIONS_FROM_OLD_STAKING_CONTRACT_REQUEST, RESET_COPY_STAKE_FLOW, } from "../actions" +import { isSameEthAddress } from "../utils/general.utils" export const copyStakeInitialData = { oldDelegations: [], @@ -90,6 +91,15 @@ const copyStakeReducer = (state = copyStakeInitialData, action) => { isProcessing: false, error: action.payload, } + case "copy-stake/remove_old_delegation": { + return { + ...state, + oldDelegations: state.oldDelegations.filter( + (delegation) => + !isSameEthAddress(delegation.operatorAddress, action.payload) + ), + } + } default: return state } diff --git a/solidity/dashboard/src/sagas/copy-stake.js b/solidity/dashboard/src/sagas/copy-stake.js index 1386b1a12f..9589365bc5 100644 --- a/solidity/dashboard/src/sagas/copy-stake.js +++ b/solidity/dashboard/src/sagas/copy-stake.js @@ -1,4 +1,4 @@ -import { takeLatest, call, put, delay } from "redux-saga/effects" +import { takeLatest, take, call, put, delay, fork } from "redux-saga/effects" import { FETCH_DELEGATIONS_FROM_OLD_STAKING_CONTRACT_REQUEST, FETCH_DELEGATIONS_FROM_OLD_STAKING_CONTRACT_SUCCESS, @@ -7,7 +7,7 @@ import { } from "../actions" import { fetchOldDelegations } from "../services/staking-port-backer.service" import { getContractsContext } from "./utils" -import { sendTransaction } from "./web3" +import { sendTransaction, createSubcribeToContractEventChannel } from "./web3" import { CONTRACT_DEPLOY_BLOCK_NUMBER } from "../contracts" import { showMessage, showCreatedMessage } from "../actions/messages" import { isEmptyArray } from "../utils/array.utils" @@ -29,6 +29,8 @@ function* fetchOldStakingDelegations() { payload: error, }) } + + yield fork(observeEvents) } export function* watchFetchOldStakingContract() { @@ -186,3 +188,39 @@ export function* watchUndelegateOldStakeRequest() { export function* watchRecovereOldStakeRequest() { yield takeLatest("copy-stake/recover_request", recoverFromOldStakingContract) } + +function* observeEvents() { + const { oldTokenStakingContract, stakingPortBackerContract } = yield call( + getContractsContext + ) + + yield fork(removeOldDelegationWatcher, oldTokenStakingContract, "Undelegated") + yield fork( + removeOldDelegationWatcher, + stakingPortBackerContract, + "StakeCopied" + ) + yield fork(removeOldDelegationWatcher, oldTokenStakingContract, "Recovered") +} + +function* removeOldDelegationWatcher(contract, eventName) { + // Create subscription channel. + const contractEventCahnnel = yield call( + createSubcribeToContractEventChannel, + contract, + eventName + ) + + // Observe and dispatch an action that updates copy-stake reducer. + while (true) { + try { + const { + returnValues: { operator }, + } = yield take(contractEventCahnnel) + yield put({ type: "copy-stake/remove_old_delegation", payload: operator }) + } catch (error) { + console.error(`Failed subscribing to ${eventName} event`, error) + contractEventCahnnel.close() + } + } +} diff --git a/solidity/dashboard/src/sagas/web3.js b/solidity/dashboard/src/sagas/web3.js index 3f3944a7b6..2cef877454 100644 --- a/solidity/dashboard/src/sagas/web3.js +++ b/solidity/dashboard/src/sagas/web3.js @@ -74,6 +74,40 @@ function createTransactionEventChannel(contract, method, args, options) { }, buffers.expanding()) } +export function createSubcribeToContractEventChannel(contract, eventName) { + const contractHasEvent = contract.options.jsonInterface.find( + (entry) => entry.type === "event" && entry.name === eventName + ) + if (!contractHasEvent) { + return eventChannel((emit) => { + emit(END) + + return () => {} + }, buffers.expanding()) + } + + const eventEmitter = contract.events[eventName]() + let eventTxCache = null + + return eventChannel((emit) => { + eventEmitter + .on("data", (event) => { + if (eventTxCache !== event.transactionHash) { + eventTxCache = event.transactionHash + emit(event) + } + }) + .on("error", () => { + emit(new Error()) + emit(END) + }) + + return () => { + eventEmitter.unsubscribe() + } + }) +} + export function* sendTransaction(action) { const { contract, methodName, args, options } = action.payload