From 8e475036abc7c1c0965086de60735392154b9c86 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Thu, 14 Nov 2024 12:32:23 -0300 Subject: [PATCH 01/10] feat: verify is tx is confirmed before getting nc state --- .../nano-contract/NanoContractDetail.js | 66 +++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/src/screens/nano-contract/NanoContractDetail.js b/src/screens/nano-contract/NanoContractDetail.js index 9eaa7fa3..f9b838f9 100644 --- a/src/screens/nano-contract/NanoContractDetail.js +++ b/src/screens/nano-contract/NanoContractDetail.js @@ -14,6 +14,7 @@ import colors from '../../index.module.scss'; import ModalChangeAddress from '../../components/nano-contract/ModalChangeAddress'; import NanoContractHistory from '../../components/nano-contract/NanoContractHistory'; import helpers from '../../utils/helpers'; +import nanoUtils from '../../utils/nanoContracts'; import hathorLib from '@hathor/wallet-lib'; import path from 'path'; import { useDispatch, useSelector } from 'react-redux'; @@ -61,12 +62,16 @@ function NanoContractDetail() { const [blueprintInformation, setBlueprintInformation] = useState(blueprintInformationAux); // errorMessage {string} Message to show when error happens on the form const [errorMessage, setErrorMessage] = useState(''); + // waitingConfirmation {boolean} If transaction was loading and is waiting first block confirmation + const [waitingConfirmation, setWaitingConfirmation] = useState(false); + // txIsValid {boolean} If transaction is a valid nano contract creation tx that is already confirmed by a block + const [txIsValid, setTxIsValid] = useState(false); useEffect(() => { if (nc) { // Load data only if nano contract exists in redux, // otherwise it has just been unregistered - loadData(); + validateAndLoad(); } }, []); @@ -116,8 +121,55 @@ function NanoContractDetail() { }); } - const loadData = async () => { + /** + * Validates if the transaction is a valid nano contract creation tx + * and if it's confirmed by a block already. + * + * If everything is ok, calls loadData, otherwise calls itself again + * in 5s if it's waiting for a block confirmation + * + * We need this because the state and history are only loaded when the tx is confirmed + */ + const validateAndLoad = async () => { setLoading(true); + let response; + try { + response = await wallet.getFullTxById(ncId); + } catch (e) { + // invalid tx + setErrorMessage(t`Transaction is invalid.`); + setLoading(false); + return; + } + + const isVoided = response.meta.voided_by.length > 0; + if (isVoided) { + setErrorMessage(t`Transaction is voided.`); + setLoading(false); + return; + } + + const isNanoContractCreate = nanoUtils.isNanoContractCreate(response.tx); + if (!isNanoContractCreate) { + setErrorMessage(t`Transaction must be a nano contract creation.`); + setLoading(false); + return; + } + + const isConfirmed = response.meta.first_block !== null; + if (!isConfirmed) { + // Wait for transaction to be confirmed + setWaitingConfirmation(true); + setTimeout(validateAndLoad, 5000); + return; + } + + setWaitingConfirmation(false); + setTxIsValid(true); + return loadData(); + } + + const loadData = async () => { try { await loadBlueprintInformation(); await loadNCData(); @@ -158,7 +210,13 @@ function NanoContractDetail() { const renderBody = () => { if (loading) { - return ; + const message = waitingConfirmation ? t`Waiting for transaction to be confirmed...` : t`Loading data...` + return ( +
+ + {message} +
+ ); } if (errorMessage) { @@ -281,7 +339,7 @@ function NanoContractDetail() {
{renderBody()}
- + {txIsValid && } ); From 66066d47572f5a45d3e240052dc8d92cbb0ea1d4 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 4 Dec 2024 12:11:25 -0300 Subject: [PATCH 02/10] refactor: move load logic to the saga --- src/actions/index.js | 35 +++++ src/constants.js | 19 +++ src/reducers/index.js | 65 ++++++++- src/sagas/nanoContract.js | 103 +++++++++++++ .../nano-contract/NanoContractDetail.js | 136 +++++------------- 5 files changed, 260 insertions(+), 98 deletions(-) diff --git a/src/actions/index.js b/src/actions/index.js index 7e35fdb0..427351ac 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -63,6 +63,9 @@ export const types = { NANOCONTRACT_EDIT_ADDRESS: 'NANOCONTRACT_EDIT_ADDRESS', NANOCONTRACT_UNREGISTER: 'NANOCONTRACT_UNREGISTER', BLUEPRINT_ADD_INFORMATION: 'BLUEPRINT_ADD_INFORMATION', + NANOCONTRACT_DETAIL_REQUEST: 'NANO_CONTRACT_DETAIL_REQUEST', + NANOCONTRACT_DETAIL_SET_STATUS: 'NANO_CONTRACT_DETAIL_SET_STATUS', + NANOCONTRACT_DETAIL_LOADED: 'NANO_CONTRACT_DETAIL_LOADED', }; /** @@ -614,3 +617,35 @@ export const nanoContractUnregister = (ncId) => ({ type: types.NANOCONTRACT_UNREGISTER, payload: ncId, }); + +/** + * Start a request to load nano contract detail + * + * @param {string} ncId ID of nano contract to load the data + */ +export const nanoContractDetailRequest = (ncId) => ({ + type: types.NANOCONTRACT_DETAIL_REQUEST, + ncId, +}); + +/** + * Set status of a nano contract detail load + * + * @param {Object} payload + * @param {string} payload.status Status of the load to set in redux + * @param {string} payload.error Error when loading data + */ +export const nanoContractDetailSetStatus = ({ status, error }) => ({ + type: types.NANOCONTRACT_DETAIL_SET_STATUS, + payload: { status, error }, +}); + +/** + * Set nano contract detail state in redux + * + * @param {hathorLib.nano_contracts.types.NanoContractStateAPIResponse} state + */ +export const nanoContractDetailLoaded = (state) => ({ + type: types.NANOCONTRACT_DETAIL_LOADED, + state, +}); diff --git a/src/constants.js b/src/constants.js index a6ea946b..6afc20c2 100644 --- a/src/constants.js +++ b/src/constants.js @@ -291,3 +291,22 @@ export const NANO_UPDATE_ADDRESS_LIST_COUNT = 5; * Number of elements in the list of transactions of a nano contract */ export const NANO_CONTRACT_HISTORY_COUNT = 5; + +/** + * Base statuses for saga reducer handlers + * Used by all other statuses objects + */ +const SAGA_BASE_STATUS = { + READY: 'ready', + ERROR: 'error', + LOADING: 'loading', + SUCCESS: 'success', +} + +/** + * Nano contract detail load data statuses for saga reducer handlers + */ +export const NANO_CONTRACT_DETAIL_STATUS = { + ...SAGA_BASE_STATUS, + WAITING_TX_CONFIRMATION: 'waitingTxConfirmation', +} diff --git a/src/reducers/index.js b/src/reducers/index.js index 89eab0a0..070df94e 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { FEATURE_TOGGLE_DEFAULTS, VERSION } from '../constants'; +import { FEATURE_TOGGLE_DEFAULTS, NANO_CONTRACT_DETAIL_STATUS, VERSION } from '../constants'; import { types } from '../actions'; import { get, findIndex } from 'lodash'; import { TOKEN_DOWNLOAD_STATUS } from '../sagas/tokens'; @@ -251,6 +251,20 @@ const initialState = { * } */ blueprintsData: {}, + /** + * Stores the status for requesting data for the nano contract detail screen + * + * { + * state: hathorLib.nano_contracts.types.NanoContractStateAPIResponse, + * status: NANO_CONTRACT_DETAIL_STATUS, + * error: string | null, + * } + */ + nanoContractDetailState: { + state: null, + status: NANO_CONTRACT_DETAIL_STATUS.READY, + error: null, + }, }; const rootReducer = (state = initialState, action) => { @@ -395,6 +409,10 @@ const rootReducer = (state = initialState, action) => { return onNanoContractEditAddress(state, action); case types.NANOCONTRACT_UNREGISTER: return onNanoContractUnregister(state, action); + case types.NANOCONTRACT_DETAIL_SET_STATUS: + return onSetNanoContractDetailStatus(state, action); + case types.NANOCONTRACT_DETAIL_LOADED: + return onNanoContractDetailLoaded(state, action); default: return state; } @@ -1355,4 +1373,49 @@ export const onNanoContractUnregister = (state, { payload }) => { }; }; +/** + * Set nano contract details status + * + * @param {Object} state + * @param {Object} action + * @param {Object} action.payload + * @param {string} action.payload.status + * @param {string | null | undefined} action.payload.error + * + * @returns {Object} + */ +export const onSetNanoContractDetailStatus = (state, { payload }) => { + return { + ...state, + nanoContractDetailState: { + ...state.nanoContractDetailState, + status: payload.status, + error: payload.error, + state: null, + } + } +} + +/** + * Set nano contract loaded state and status to success + * + * @param {Object} state + * @param {Object} action + * @param {Object} action.payload + * @param {hathorLib.nano_contracts.types.NanoContractStateAPIResponse} action.payload.state + * + * @returns {Object} + */ +export const onNanoContractDetailLoaded = (state, payload) => { + return { + ...state, + nanoContractDetailState: { + ...state.nanoContractDetailState, + state: payload.state, + status: NANO_CONTRACT_DETAIL_STATUS.SUCCESS, + error: null, + } + } +} + export default rootReducer; diff --git a/src/sagas/nanoContract.js b/src/sagas/nanoContract.js index 2d2bfbdc..d6e00302 100644 --- a/src/sagas/nanoContract.js +++ b/src/sagas/nanoContract.js @@ -6,15 +6,19 @@ import { import { t } from 'ttag'; import { getGlobalWallet } from "../modules/wallet"; +import hathorLib from '@hathor/wallet-lib'; import { addBlueprintInformation, + nanoContractDetailLoaded, + nanoContractDetailSetStatus, nanoContractRegisterError, nanoContractRegisterSuccess, types, } from '../actions'; import nanoUtils from '../utils/nanoContracts'; +import { NANO_CONTRACT_DETAIL_STATUS } from '../constants'; import { all, call, put, select, takeEvery } from 'redux-saga/effects'; @@ -124,9 +128,108 @@ export function* updateNanoContractRegisteredAddress({ payload }) { yield call([wallet.storage, wallet.storage.updateNanoContractRegisteredAddress], payload.ncId, payload.address); } + +/** + * Load nano contract detail state + * @param {{ + * payload: { + * ncId: string, + * } + * }} action with request payload. + */ +export function* loadNanoContractDetail({ ncId }) { + const wallet = getGlobalWallet(); + let response; + try { + response = yield call([wallet, wallet.getFullTxById], ncId); + } catch (e) { + // invalid tx + console.debug(`Fail loading Nano Contract detail because [ncId=${ncId}] is an invalid tx.`); + yield put(nanoContractDetailSetStatus({ status: NANO_CONTRACT_DETAIL_STATUS.ERROR, error: t`Transaction is invalid.` })); + return; + } + + const isVoided = response.meta.voided_by.length > 0; + if (isVoided) { + console.debug(`Fail loading Nano Contract detail because [ncId=${ncId}] is a voided tx.`); + yield put(nanoContractDetailSetStatus({ status: NANO_CONTRACT_DETAIL_STATUS.ERROR, error: t`Transaction is voided.` })); + return; + } + + const isNanoContractCreate = nanoUtils.isNanoContractCreate(response.tx); + if (!isNanoContractCreate) { + console.debug(`Fail loading Nano Contract detail because [ncId=${ncId}] is not a nano contract creation tx.`); + yield put(nanoContractDetailSetStatus({ status: NANO_CONTRACT_DETAIL_STATUS.ERROR, error: t`Transaction must be a nano contract creation.` })); + return; + } + + const isConfirmed = response.meta.first_block !== null; + if (!isConfirmed) { + // Wait for transaction to be confirmed + yield put(nanoContractDetailSetStatus({ status: NANO_CONTRACT_DETAIL_STATUS.WAITING_TX_CONFIRMATION })); + + // This confirmation might take some minutes, depending on network mining power + // then it's ok to wait until it's confirmed, or until the user gives up and + // leaves the screen + while (true) { + // Wait 5s, then we fetch the data again to check if is has been confirmed + yield delay(5000); + const nanoContractDetailState = yield select((state) => state.nanoContractDetailState); + if (nanoContractDetailState.status !== NANO_CONTRACT_DETAIL_STATUS.WAITING_TX_CONFIRMATION) { + // User unmounted the screen, so we must stop the saga + return; + } + + try { + const responseFullTx = yield call([wallet, wallet.getFullTxById], ncId); + const isConfirmed = responseFullTx.meta.first_block !== null; + if (isConfirmed) { + break; + } + } catch (e) { + // error loading full tx + console.error('Error while loading full tx.', e); + yield put(nanoContractDetailSetStatus({ status: NANO_CONTRACT_DETAIL_STATUS.ERROR, error: t`Error loading transaction data.` })); + return; + } + } + } + + const nanoContracts = yield select((state) => state.nanoContracts); + const blueprintsData = yield select((state) => state.blueprintsData); + const nc = nanoContracts[ncId]; + let blueprintInformation = blueprintsData[nc.blueprintId]; + + if (!blueprintInformation) { + // If it hasn't been loaded, we load and store in redux + try { + blueprintInformation = yield call(hathorLib.ncApi.getBlueprintInformation, nc.blueprintId); + // We need this blueprint information response to call the following get state + // Store in redux, so it can be reused by other nano contracts + yield put(addBlueprintInformation(blueprintInformation)); + } catch(e) { + // Error in request + console.error('Error while loading blueprint information.', e); + yield put(nanoContractDetailSetStatus({ status: NANO_CONTRACT_DETAIL_STATUS.ERROR, error: t`Error getting blueprint details.` })); + return; + } + } + + try { + const state = yield call(hathorLib.ncApi.getNanoContractState, ncId, Object.keys(blueprintInformation.attributes), ['__all__'], []); + yield put(nanoContractDetailLoaded(state)); + } catch(e) { + // Error in request + console.error('Error while loading nano contract state.', e); + yield put(nanoContractDetailSetStatus({ status: NANO_CONTRACT_DETAIL_STATUS.ERROR, error: t`Error getting nano contract state.` })); + return; + } +} + export function* saga() { yield all([ takeEvery(types.NANOCONTRACT_REGISTER_REQUEST, registerNanoContract), takeEvery(types.NANOCONTRACT_EDIT_ADDRESS, updateNanoContractRegisteredAddress), + takeEvery(types.NANOCONTRACT_DETAIL_REQUEST, loadNanoContractDetail), ]); } \ No newline at end of file diff --git a/src/screens/nano-contract/NanoContractDetail.js b/src/screens/nano-contract/NanoContractDetail.js index f9b838f9..124e44c7 100644 --- a/src/screens/nano-contract/NanoContractDetail.js +++ b/src/screens/nano-contract/NanoContractDetail.js @@ -19,10 +19,11 @@ import hathorLib from '@hathor/wallet-lib'; import path from 'path'; import { useDispatch, useSelector } from 'react-redux'; import { useNavigate, useParams } from 'react-router-dom'; -import { addBlueprintInformation, nanoContractUnregister } from '../../actions'; +import { addBlueprintInformation, nanoContractDetailRequest, nanoContractDetailSetStatus, nanoContractUnregister } from '../../actions'; import { get } from 'lodash'; import { GlobalModalContext, MODAL_TYPES } from '../../components/GlobalModal'; -import { getGlobalWallet } from "../../modules/wallet"; +import { getGlobalWallet } from '../../modules/wallet'; +import { NANO_CONTRACT_DETAIL_STATUS } from '../../constants'; /** @@ -36,14 +37,14 @@ function NanoContractDetail() { const { nanoContracts, + nanoContractDetailState, blueprintsData, - tokenMetadata, decimalPlaces, } = useSelector((state) => { return { nanoContracts: state.nanoContracts, + nanoContractDetailState: state.nanoContractDetailState, blueprintsData: state.blueprintsData, - tokenMetadata: state.tokenMetadata, decimalPlaces: state.serverInfo.decimalPlaces, } }); @@ -52,29 +53,57 @@ function NanoContractDetail() { const navigate = useNavigate(); const { nc_id: ncId } = useParams(); const nc = nanoContracts[ncId]; - let blueprintInformationAux = nc != null ? blueprintsData[nc.blueprintId] : null; // loading {boolean} Bool to show/hide loading element const [loading, setLoading] = useState(true); // data {Object} Nano contract loaded data const [data, setData] = useState(null); // blueprintInformation {Object} Blueprint information data - const [blueprintInformation, setBlueprintInformation] = useState(blueprintInformationAux); + const [blueprintInformation, setBlueprintInformation] = useState(null); // errorMessage {string} Message to show when error happens on the form const [errorMessage, setErrorMessage] = useState(''); // waitingConfirmation {boolean} If transaction was loading and is waiting first block confirmation const [waitingConfirmation, setWaitingConfirmation] = useState(false); - // txIsValid {boolean} If transaction is a valid nano contract creation tx that is already confirmed by a block - const [txIsValid, setTxIsValid] = useState(false); useEffect(() => { if (nc) { // Load data only if nano contract exists in redux, // otherwise it has just been unregistered - validateAndLoad(); + dispatch(nanoContractDetailRequest(ncId)); + } + + return () => { + // Move state to ready when unmounting + dispatch(nanoContractDetailSetStatus({ status: NANO_CONTRACT_DETAIL_STATUS.READY })); } }, []); + useEffect(() => { + if (nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.LOADING) { + setLoading(true); + } + + if (nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.ERROR) { + setLoading(false); + setErrorMessage(nanoContractDetailState.error); + } + + if (nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.WAITING_TX_CONFIRMATION) { + setWaitingConfirmation(true); + } + + if (nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.SUCCESS) { + setWaitingConfirmation(false); + setLoading(false); + // We know that we will have the blueprint data in redux here + const blueprintInformation = blueprintsData[nc.blueprintId]; + setBlueprintInformation(blueprintInformation); + setData(nanoContractDetailState.state); + dispatch(nanoContractDetailSetStatus({ status: NANO_CONTRACT_DETAIL_STATUS.READY })); + } + + }, [nanoContractDetailState.status]); + /** * Method executed when link to execute a method is clicked * @@ -121,93 +150,6 @@ function NanoContractDetail() { }); } - /** - * Validates if the transaction is a valid nano contract creation tx - * and if it's confirmed by a block already. - * - * If everything is ok, calls loadData, otherwise calls itself again - * in 5s if it's waiting for a block confirmation - * - * We need this because the state and history are only loaded when the tx is confirmed - */ - const validateAndLoad = async () => { - setLoading(true); - let response; - try { - response = await wallet.getFullTxById(ncId); - } catch (e) { - // invalid tx - setErrorMessage(t`Transaction is invalid.`); - setLoading(false); - return; - } - - const isVoided = response.meta.voided_by.length > 0; - if (isVoided) { - setErrorMessage(t`Transaction is voided.`); - setLoading(false); - return; - } - - const isNanoContractCreate = nanoUtils.isNanoContractCreate(response.tx); - if (!isNanoContractCreate) { - setErrorMessage(t`Transaction must be a nano contract creation.`); - setLoading(false); - return; - } - - const isConfirmed = response.meta.first_block !== null; - if (!isConfirmed) { - // Wait for transaction to be confirmed - setWaitingConfirmation(true); - setTimeout(validateAndLoad, 5000); - return; - } - - setWaitingConfirmation(false); - setTxIsValid(true); - return loadData(); - } - - const loadData = async () => { - try { - await loadBlueprintInformation(); - await loadNCData(); - } finally { - setLoading(false); - } - } - - const loadBlueprintInformation = async () => { - if (blueprintInformationAux) { - return; - } - - try { - const blueprintInformationResponse = await hathorLib.ncApi.getBlueprintInformation(nc.blueprintId); - // We need this blueprint information response to call the following get state - // The set state is not sync, so we need to store it in a common variable to be used in the next call - blueprintInformationAux = blueprintInformationResponse; - setBlueprintInformation(blueprintInformationResponse); - // Store in redux, so it can be reused by other nano contracts - dispatch(addBlueprintInformation(blueprintInformationResponse)); - } catch(e) { - // Error in request - setErrorMessage(t`Error getting blueprint details.`); - }; - } - - const loadNCData = async () => { - setData(null); - try { - const state = await hathorLib.ncApi.getNanoContractState(ncId, Object.keys(blueprintInformationAux.attributes), ['__all__'], []); - setData(state); - } catch(e) { - // Error in request - setErrorMessage(t`Error getting nano contract state.`); - }; - } - const renderBody = () => { if (loading) { const message = waitingConfirmation ? t`Waiting for transaction to be confirmed...` : t`Loading data...` @@ -339,7 +281,7 @@ function NanoContractDetail() {
{renderBody()}
- {txIsValid && } + {data && } ); From 89e0eefc6da4b657a92d73e62caaa6e88cfaecd2 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 4 Dec 2024 12:18:04 -0300 Subject: [PATCH 03/10] fix: add missing import --- src/sagas/nanoContract.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sagas/nanoContract.js b/src/sagas/nanoContract.js index d6e00302..a37fe2b2 100644 --- a/src/sagas/nanoContract.js +++ b/src/sagas/nanoContract.js @@ -20,7 +20,7 @@ import { import nanoUtils from '../utils/nanoContracts'; import { NANO_CONTRACT_DETAIL_STATUS } from '../constants'; -import { all, call, put, select, takeEvery } from 'redux-saga/effects'; +import { all, call, delay, put, select, takeEvery } from 'redux-saga/effects'; export const NANOCONTRACT_REGISTER_STATUS = { LOADING: 'loading', From 35bfe1baa2f8b2262a7b03ae9c85f9b2fd06b057 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Tue, 10 Dec 2024 00:14:01 -0300 Subject: [PATCH 04/10] refactor: improve parameter name --- src/actions/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/actions/index.js b/src/actions/index.js index 427351ac..f80171dc 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -643,9 +643,9 @@ export const nanoContractDetailSetStatus = ({ status, error }) => ({ /** * Set nano contract detail state in redux * - * @param {hathorLib.nano_contracts.types.NanoContractStateAPIResponse} state + * @param {hathorLib.nano_contracts.types.NanoContractStateAPIResponse} ncState */ -export const nanoContractDetailLoaded = (state) => ({ - type: types.NANOCONTRACT_DETAIL_LOADED, - state, +export const nanoContractDetailLoaded = (ncState) => ({ + type: types.NANOCONTRACT_LOAD_DETAILS_SUCCESS, + state: ncState, }); From 920bc6696671bbd56091a730f120bec5e3136cb9 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Tue, 10 Dec 2024 00:14:56 -0300 Subject: [PATCH 05/10] refactor: improve action type name --- src/actions/index.js | 10 +++++----- src/reducers/index.js | 4 ++-- src/sagas/nanoContract.js | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/actions/index.js b/src/actions/index.js index f80171dc..eb6a7677 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -63,9 +63,9 @@ export const types = { NANOCONTRACT_EDIT_ADDRESS: 'NANOCONTRACT_EDIT_ADDRESS', NANOCONTRACT_UNREGISTER: 'NANOCONTRACT_UNREGISTER', BLUEPRINT_ADD_INFORMATION: 'BLUEPRINT_ADD_INFORMATION', - NANOCONTRACT_DETAIL_REQUEST: 'NANO_CONTRACT_DETAIL_REQUEST', - NANOCONTRACT_DETAIL_SET_STATUS: 'NANO_CONTRACT_DETAIL_SET_STATUS', - NANOCONTRACT_DETAIL_LOADED: 'NANO_CONTRACT_DETAIL_LOADED', + NANOCONTRACT_LOAD_DETAILS_REQUESTED: 'NANOCONTRACT_LOAD_DETAILS_REQUESTED', + NANOCONTRACT_LOAD_DETAILS_STATUS_UPDATE: 'NANOCONTRACT_LOAD_DETAILS_STATUS_UPDATE', + NANOCONTRACT_LOAD_DETAILS_SUCCESS: 'NANOCONTRACT_LOAD_DETAILS_SUCCESS', }; /** @@ -624,7 +624,7 @@ export const nanoContractUnregister = (ncId) => ({ * @param {string} ncId ID of nano contract to load the data */ export const nanoContractDetailRequest = (ncId) => ({ - type: types.NANOCONTRACT_DETAIL_REQUEST, + type: types.NANOCONTRACT_LOAD_DETAILS_REQUESTED, ncId, }); @@ -636,7 +636,7 @@ export const nanoContractDetailRequest = (ncId) => ({ * @param {string} payload.error Error when loading data */ export const nanoContractDetailSetStatus = ({ status, error }) => ({ - type: types.NANOCONTRACT_DETAIL_SET_STATUS, + type: types.NANOCONTRACT_LOAD_DETAILS_STATUS_UPDATE, payload: { status, error }, }); diff --git a/src/reducers/index.js b/src/reducers/index.js index 070df94e..b7e3dce9 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -409,9 +409,9 @@ const rootReducer = (state = initialState, action) => { return onNanoContractEditAddress(state, action); case types.NANOCONTRACT_UNREGISTER: return onNanoContractUnregister(state, action); - case types.NANOCONTRACT_DETAIL_SET_STATUS: + case types.NANOCONTRACT_LOAD_DETAILS_STATUS_UPDATE: return onSetNanoContractDetailStatus(state, action); - case types.NANOCONTRACT_DETAIL_LOADED: + case types.NANOCONTRACT_LOAD_DETAILS_SUCCESS: return onNanoContractDetailLoaded(state, action); default: return state; diff --git a/src/sagas/nanoContract.js b/src/sagas/nanoContract.js index a37fe2b2..f3220ba2 100644 --- a/src/sagas/nanoContract.js +++ b/src/sagas/nanoContract.js @@ -230,6 +230,6 @@ export function* saga() { yield all([ takeEvery(types.NANOCONTRACT_REGISTER_REQUEST, registerNanoContract), takeEvery(types.NANOCONTRACT_EDIT_ADDRESS, updateNanoContractRegisteredAddress), - takeEvery(types.NANOCONTRACT_DETAIL_REQUEST, loadNanoContractDetail), + takeEvery(types.NANOCONTRACT_LOAD_DETAILS_REQUESTED, loadNanoContractDetail), ]); } \ No newline at end of file From d949518411675076f59ed28c4f539cd3a0130166 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Tue, 10 Dec 2024 00:15:18 -0300 Subject: [PATCH 06/10] refactor: improve BASE_STATUS saga variable name --- src/constants.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/constants.js b/src/constants.js index 6afc20c2..d5257b54 100644 --- a/src/constants.js +++ b/src/constants.js @@ -296,7 +296,7 @@ export const NANO_CONTRACT_HISTORY_COUNT = 5; * Base statuses for saga reducer handlers * Used by all other statuses objects */ -const SAGA_BASE_STATUS = { +const BASE_STATUS = { READY: 'ready', ERROR: 'error', LOADING: 'loading', @@ -307,6 +307,6 @@ const SAGA_BASE_STATUS = { * Nano contract detail load data statuses for saga reducer handlers */ export const NANO_CONTRACT_DETAIL_STATUS = { - ...SAGA_BASE_STATUS, + ...BASE_STATUS, WAITING_TX_CONFIRMATION: 'waitingTxConfirmation', } From db360049c8ce852268b54f773827789847dbdcd6 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Tue, 10 Dec 2024 00:15:54 -0300 Subject: [PATCH 07/10] refactor: move delay time when waiting tx confirmation to a variable --- src/sagas/nanoContract.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sagas/nanoContract.js b/src/sagas/nanoContract.js index f3220ba2..0eb4c80d 100644 --- a/src/sagas/nanoContract.js +++ b/src/sagas/nanoContract.js @@ -28,6 +28,8 @@ export const NANOCONTRACT_REGISTER_STATUS = { SUCCESS: 'success', }; +const NANOCONTRACT_WAIT_TX_CONFIRMED_DELAY = 5000; + /** * Process Nano Contract registration request. * @param {{ @@ -173,7 +175,7 @@ export function* loadNanoContractDetail({ ncId }) { // leaves the screen while (true) { // Wait 5s, then we fetch the data again to check if is has been confirmed - yield delay(5000); + yield delay(NANOCONTRACT_WAIT_TX_CONFIRMED_DELAY); const nanoContractDetailState = yield select((state) => state.nanoContractDetailState); if (nanoContractDetailState.status !== NANO_CONTRACT_DETAIL_STATUS.WAITING_TX_CONFIRMATION) { // User unmounted the screen, so we must stop the saga From 0867356a5879c36f5bb35727afebbcd3298a1ac5 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Tue, 10 Dec 2024 00:17:01 -0300 Subject: [PATCH 08/10] docs: fix parameter type in docstring --- src/screens/nano-contract/NanoContractDetail.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/nano-contract/NanoContractDetail.js b/src/screens/nano-contract/NanoContractDetail.js index 124e44c7..7062fe8c 100644 --- a/src/screens/nano-contract/NanoContractDetail.js +++ b/src/screens/nano-contract/NanoContractDetail.js @@ -58,7 +58,7 @@ function NanoContractDetail() { const [loading, setLoading] = useState(true); // data {Object} Nano contract loaded data const [data, setData] = useState(null); - // blueprintInformation {Object} Blueprint information data + // blueprintInformation {Object | null} Blueprint information data const [blueprintInformation, setBlueprintInformation] = useState(null); // errorMessage {string} Message to show when error happens on the form const [errorMessage, setErrorMessage] = useState(''); From 7f288d2ffbb860354a8b26e00b6b1ea4ffa7633e Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Tue, 10 Dec 2024 00:29:35 -0300 Subject: [PATCH 09/10] feat: removing unneeded states from component --- src/sagas/nanoContract.js | 1 + .../nano-contract/NanoContractDetail.js | 31 ++++--------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/sagas/nanoContract.js b/src/sagas/nanoContract.js index 0eb4c80d..83e5a3a1 100644 --- a/src/sagas/nanoContract.js +++ b/src/sagas/nanoContract.js @@ -140,6 +140,7 @@ export function* updateNanoContractRegisteredAddress({ payload }) { * }} action with request payload. */ export function* loadNanoContractDetail({ ncId }) { + yield put(nanoContractDetailSetStatus({ status: NANO_CONTRACT_DETAIL_STATUS.LOADING })); const wallet = getGlobalWallet(); let response; try { diff --git a/src/screens/nano-contract/NanoContractDetail.js b/src/screens/nano-contract/NanoContractDetail.js index 7062fe8c..a304b105 100644 --- a/src/screens/nano-contract/NanoContractDetail.js +++ b/src/screens/nano-contract/NanoContractDetail.js @@ -54,16 +54,10 @@ function NanoContractDetail() { const { nc_id: ncId } = useParams(); const nc = nanoContracts[ncId]; - // loading {boolean} Bool to show/hide loading element - const [loading, setLoading] = useState(true); // data {Object} Nano contract loaded data const [data, setData] = useState(null); // blueprintInformation {Object | null} Blueprint information data const [blueprintInformation, setBlueprintInformation] = useState(null); - // errorMessage {string} Message to show when error happens on the form - const [errorMessage, setErrorMessage] = useState(''); - // waitingConfirmation {boolean} If transaction was loading and is waiting first block confirmation - const [waitingConfirmation, setWaitingConfirmation] = useState(false); useEffect(() => { if (nc) { @@ -79,22 +73,7 @@ function NanoContractDetail() { }, []); useEffect(() => { - if (nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.LOADING) { - setLoading(true); - } - - if (nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.ERROR) { - setLoading(false); - setErrorMessage(nanoContractDetailState.error); - } - - if (nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.WAITING_TX_CONFIRMATION) { - setWaitingConfirmation(true); - } - if (nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.SUCCESS) { - setWaitingConfirmation(false); - setLoading(false); // We know that we will have the blueprint data in redux here const blueprintInformation = blueprintsData[nc.blueprintId]; setBlueprintInformation(blueprintInformation); @@ -151,8 +130,8 @@ function NanoContractDetail() { } const renderBody = () => { - if (loading) { - const message = waitingConfirmation ? t`Waiting for transaction to be confirmed...` : t`Loading data...` + if (nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.LOADING || nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.WAITING_TX_CONFIRMATION) { + const message = nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.WAITING_TX_CONFIRMATION ? t`Waiting for transaction to be confirmed...` : t`Loading data...`; return (
@@ -161,10 +140,12 @@ function NanoContractDetail() { ); } - if (errorMessage) { - return

{errorMessage}

; + if (nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.ERROR) { + return

{nanoContractDetailState.error}

; } + if (!data) return null; + return renderNCData(); } From 2da7878cb467038dc3946ca352268faaff4a6e46 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Tue, 10 Dec 2024 21:47:41 -0300 Subject: [PATCH 10/10] refactor: remove component states to use redux state --- .../nano-contract/NanoContractDetail.js | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/screens/nano-contract/NanoContractDetail.js b/src/screens/nano-contract/NanoContractDetail.js index a304b105..40a0eec8 100644 --- a/src/screens/nano-contract/NanoContractDetail.js +++ b/src/screens/nano-contract/NanoContractDetail.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect } from 'react'; import { t } from 'ttag' import $ from 'jquery'; import ResetNavigationLink from '../../components/ResetNavigationLink'; @@ -19,7 +19,7 @@ import hathorLib from '@hathor/wallet-lib'; import path from 'path'; import { useDispatch, useSelector } from 'react-redux'; import { useNavigate, useParams } from 'react-router-dom'; -import { addBlueprintInformation, nanoContractDetailRequest, nanoContractDetailSetStatus, nanoContractUnregister } from '../../actions'; +import { nanoContractDetailRequest, nanoContractDetailSetStatus, nanoContractUnregister } from '../../actions'; import { get } from 'lodash'; import { GlobalModalContext, MODAL_TYPES } from '../../components/GlobalModal'; import { getGlobalWallet } from '../../modules/wallet'; @@ -54,11 +54,6 @@ function NanoContractDetail() { const { nc_id: ncId } = useParams(); const nc = nanoContracts[ncId]; - // data {Object} Nano contract loaded data - const [data, setData] = useState(null); - // blueprintInformation {Object | null} Blueprint information data - const [blueprintInformation, setBlueprintInformation] = useState(null); - useEffect(() => { if (nc) { // Load data only if nano contract exists in redux, @@ -72,17 +67,6 @@ function NanoContractDetail() { } }, []); - useEffect(() => { - if (nanoContractDetailState.status === NANO_CONTRACT_DETAIL_STATUS.SUCCESS) { - // We know that we will have the blueprint data in redux here - const blueprintInformation = blueprintsData[nc.blueprintId]; - setBlueprintInformation(blueprintInformation); - setData(nanoContractDetailState.state); - dispatch(nanoContractDetailSetStatus({ status: NANO_CONTRACT_DETAIL_STATUS.READY })); - } - - }, [nanoContractDetailState.status]); - /** * Method executed when link to execute a method is clicked * @@ -91,6 +75,7 @@ function NanoContractDetail() { */ const executeMethod = (e, method) => { e.preventDefault(); + const blueprintInformation = blueprintsData[nc.blueprintId]; navigate('/nano_contract/execute_method/', { state: { method, @@ -144,12 +129,15 @@ function NanoContractDetail() { return

{nanoContractDetailState.error}

; } - if (!data) return null; + if (nanoContractDetailState.status !== NANO_CONTRACT_DETAIL_STATUS.SUCCESS) { + return null; + } return renderNCData(); } const formatNCField = (field, value) => { + const blueprintInformation = blueprintsData[nc.blueprintId]; if (value === undefined) { // Error getting field // Since we are using the attributes from the blueprint information API to @@ -197,6 +185,7 @@ function NanoContractDetail() { } const renderNanoBalances = () => { + const data = nanoContractDetailState.state; return Object.entries(data.balances).map(([tokenUid, amount]) => { return (
@@ -208,6 +197,7 @@ function NanoContractDetail() { } const renderNanoAttributes = () => { + const data = nanoContractDetailState.state; return Object.keys(data.fields).map((field) => { const value = get(data.fields[field], 'value', undefined); return

{field}: {formatNCField(field, value)}

; @@ -215,6 +205,7 @@ function NanoContractDetail() { } const renderNanoMethods = () => { + const blueprintInformation = blueprintsData[nc.blueprintId]; const publicMethods = get(blueprintInformation, 'public_methods', {}); return Object.keys(publicMethods).filter((method) => method !== hathorLib.constants.NANO_CONTRACTS_INITIALIZE_METHOD @@ -228,6 +219,7 @@ function NanoContractDetail() { } const renderNCData = () => { + const blueprintInformation = blueprintsData[nc.blueprintId]; return (

Blueprint: {blueprintInformation.name}

@@ -262,7 +254,7 @@ function NanoContractDetail() {
{renderBody()}
- {data && } + {nanoContractDetailState.state && }
);