Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: verify is tx is confirmed before getting nc state #695

Merged
merged 10 commits into from
Dec 11, 2024
35 changes: 35 additions & 0 deletions src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export const types = {
NANOCONTRACT_EDIT_ADDRESS: 'NANOCONTRACT_EDIT_ADDRESS',
NANOCONTRACT_UNREGISTER: 'NANOCONTRACT_UNREGISTER',
BLUEPRINT_ADD_INFORMATION: 'BLUEPRINT_ADD_INFORMATION',
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',
};

/**
Expand Down Expand Up @@ -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_LOAD_DETAILS_REQUESTED,
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_LOAD_DETAILS_STATUS_UPDATE,
payload: { status, error },
});

/**
* Set nano contract detail state in redux
*
* @param {hathorLib.nano_contracts.types.NanoContractStateAPIResponse} ncState
*/
export const nanoContractDetailLoaded = (ncState) => ({
type: types.NANOCONTRACT_LOAD_DETAILS_SUCCESS,
state: ncState,
});
19 changes: 19 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 = {
...BASE_STATUS,
WAITING_TX_CONFIRMATION: 'waitingTxConfirmation',
}
65 changes: 64 additions & 1 deletion src/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -395,6 +409,10 @@ const rootReducer = (state = initialState, action) => {
return onNanoContractEditAddress(state, action);
case types.NANOCONTRACT_UNREGISTER:
return onNanoContractUnregister(state, action);
case types.NANOCONTRACT_LOAD_DETAILS_STATUS_UPDATE:
return onSetNanoContractDetailStatus(state, action);
case types.NANOCONTRACT_LOAD_DETAILS_SUCCESS:
return onNanoContractDetailLoaded(state, action);
default:
return state;
}
Expand Down Expand Up @@ -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;
108 changes: 107 additions & 1 deletion src/sagas/nanoContract.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,30 @@ 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';
import { all, call, delay, put, select, takeEvery } from 'redux-saga/effects';

export const NANOCONTRACT_REGISTER_STATUS = {
LOADING: 'loading',
ERROR: 'error',
SUCCESS: 'success',
};

const NANOCONTRACT_WAIT_TX_CONFIRMED_DELAY = 5000;

/**
* Process Nano Contract registration request.
* @param {{
Expand Down Expand Up @@ -124,9 +130,109 @@ 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 }) {
yield put(nanoContractDetailSetStatus({ status: NANO_CONTRACT_DETAIL_STATUS.LOADING }));
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(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
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_LOAD_DETAILS_REQUESTED, loadNanoContractDetail),
]);
}
Loading
Loading