diff --git a/__tests__/sagas/nanoContracts/fixtures.js b/__tests__/sagas/nanoContracts/fixtures.js index 0944f7716..fa6b6e4b4 100644 --- a/__tests__/sagas/nanoContracts/fixtures.js +++ b/__tests__/sagas/nanoContracts/fixtures.js @@ -81,6 +81,7 @@ export const fixtures = { ncState: { success: true, nc_id: '3cb032600bdf7db784800e4ea911b10676fa2f67591f82bb62628c234e771595', + blueprint_id: '000001342d3c5b858a4d4835baea93fcc683fa615ff5892bd044459621a0340a', blueprint_name: 'Bet', fields: { token_uid: { value: '00' }, diff --git a/__tests__/sagas/nanoContracts/historyNanoContract.test.js b/__tests__/sagas/nanoContracts/historyNanoContract.test.js index c7d3c277c..a234b0863 100644 --- a/__tests__/sagas/nanoContracts/historyNanoContract.test.js +++ b/__tests__/sagas/nanoContracts/historyNanoContract.test.js @@ -27,9 +27,7 @@ describe('sagas/nanoContract/fetchHistory', () => { // arrange wallet mock const mockedWallet = { getNetworkObject: jest.fn(), - storage: { - isAddressMine: jest.fn(), - }, + isAddressMine: jest.fn(), }; // arrange ncApi mock const mockedNcApi = jest.mocked(ncApi); @@ -115,10 +113,10 @@ describe('sagas/nanoContract/requestHistoryNanoContract', () => { // call effect to request history const gen = requestHistoryNanoContract(nanoContractHistoryRequest({ ncId })); - // select wallet + // select historyMeta gen.next(); // feed back historyMeta - gen.next({}); + gen.next({ [ncId]: { isLoading: false, after: null } }); // feed back wallet gen.next(fixtures.wallet.readyAndMine); // feed back isNanoContractRegistered diff --git a/__tests__/sagas/nanoContracts/registerNanoContract.test.js b/__tests__/sagas/nanoContracts/registerNanoContract.test.js index 2a2eb385d..a35e1571d 100644 --- a/__tests__/sagas/nanoContracts/registerNanoContract.test.js +++ b/__tests__/sagas/nanoContracts/registerNanoContract.test.js @@ -123,6 +123,7 @@ describe('sagas/nanoContract/registerNanoContract', () => { expect(actionResult.payload.action.payload.entryValue).toEqual(expect.objectContaining({ address, ncId, + blueprintId: expect.any(String), blueprintName: expect.any(String), })); // assert termination diff --git a/src/actions.js b/src/actions.js index 2347b759f..decb0476f 100644 --- a/src/actions.js +++ b/src/actions.js @@ -149,6 +149,12 @@ export const types = { NANOCONTRACT_HISTORY_SUCCESS: 'NANOCONTRACT_HISTORY_SUCCESS', /* It indicates a Nano Contract history failed to load. */ NANOCONTRACT_HISTORY_FAILURE: 'NANOCONTRACT_HISTORY_FAILURE', + /* It initiates an unregistration process of a Nano Contract. */ + NANOCONTRACT_UNREGISTER_REQUEST: 'NANOCONTRACT_UNREGISTER_REQUEST', + /* It signals a successful completion of unregistration process. */ + NANOCONTRACT_UNREGISTER_SUCCESS: 'NANOCONTRACT_UNREGISTER_SUCCESS', + /* It initiates a process to change the address on registered Nano Contract. */ + NANOCONTRACT_ADDRESS_CHANGE_REQUEST: 'NANOCONTRACT_ADDRESS_CHANGE_REQUEST', }; export const featureToggleInitialized = () => ({ @@ -1055,3 +1061,37 @@ export const nanoContractHistoryFailure = (payload) => ({ type: types.NANOCONTRACT_HISTORY_FAILURE, payload, }); + +/** + * Request unregistration of a Nano Contract by its key. + * @param {{ + * ncId: string, + * }} Nano Contract ID to unregister. + */ +export const nanoContractUnregisterRequest = (unregisterRequest) => ({ + type: types.NANOCONTRACT_UNREGISTER_REQUEST, + payload: unregisterRequest, +}); + +/** + * Unregistration of a Nano Contract has finished with success. + * @param {{ + * ncId: string, + * }} Nano Contract ID unregistered. + */ +export const nanoContractUnregisterSuccess = (unregistered) => ({ + type: types.NANOCONTRACT_UNREGISTER_SUCCESS, + payload: unregistered, +}); + +/** + * Request a change on the Nano Contract registered. + * @param {{ + * ncId: string; + * newAddress: string; + * }} changeAddressRequest + */ +export const nanoContractAddressChangeRequest = (changeAddressRequest) => ({ + type: types.NANOCONTRACT_ADDRESS_CHANGE_REQUEST, + payload: changeAddressRequest, +}); diff --git a/src/reducers/reducer.js b/src/reducers/reducer.js index f3537535d..16a7908d4 100644 --- a/src/reducers/reducer.js +++ b/src/reducers/reducer.js @@ -477,6 +477,10 @@ export const reducer = (state = initialState, action) => { return onNanoContractHistoryFailure(state, action); case types.NANOCONTRACT_HISTORY_SUCCESS: return onNanoContractHistorySuccess(state, action); + case types.NANOCONTRACT_UNREGISTER_SUCCESS: + return onNanoContractUnregisterSuccess(state, action); + case types.NANOCONTRACT_ADDRESS_CHANGE_REQUEST: + return onNanoContractAddressChangeRequest(state, action); default: return state; } @@ -1329,6 +1333,37 @@ export const onNanoContractRegisterSuccess = (state, { payload }) => ({ }, }); +/** + * @param {Object} state + * @param {{ + * payload: { + * ncId: string, + * } + * }} action + */ +export const onNanoContractUnregisterSuccess = (state, { payload }) => { + const { ncId } = payload; + + const newRegisteredContracts = { ...state.nanoContract.registered }; + delete newRegisteredContracts[ncId]; + + const newContractsHistory = { ...state.nanoContract.history }; + delete newContractsHistory[ncId]; + + const newContractsHistoryMeta = { ...state.nanoContract.historyMeta }; + delete newContractsHistoryMeta[ncId]; + + return ({ + ...state, + nanoContract: { + ...state.nanoContract, + registered: newRegisteredContracts, + history: newContractsHistory, + historyMeta: newContractsHistoryMeta, + }, + }); +}; + /** * @param {Object} state * @param {{ @@ -1407,3 +1442,29 @@ export const onNanoContractHistorySuccess = (state, { payload }) => ({ }, }, }); + +/** + * @param {Object} state + * @param {{ + * payload: { + * ncId: string; + * newAddress: string; + * } + * }} action + */ +export const onNanoContractAddressChangeRequest = (state, { payload }) => { + const newRegisteredNc = { + ...state.nanoContract.registered[payload.ncId], + address: payload.newAddress, + }; + return { + ...state, + nanoContract: { + ...state.nanoContract, + registered: { + ...state.nanoContract.registered, + [payload.ncId]: newRegisteredNc, + }, + }, + }; +}; diff --git a/src/sagas/nanoContract.js b/src/sagas/nanoContract.js index 6e9fd3e27..f4ec6a366 100644 --- a/src/sagas/nanoContract.js +++ b/src/sagas/nanoContract.js @@ -24,6 +24,7 @@ import { nanoContractHistorySuccess, nanoContractRegisterFailure, nanoContractRegisterSuccess, + nanoContractUnregisterSuccess, onExceptionCaptured, types, } from '../actions'; @@ -80,7 +81,8 @@ export function* registerNanoContract({ payload }) { let ncState = null; try { - ncState = yield call(ncApi.getNanoContractState.bind(ncApi), ncId); + const response = yield call([ncApi, ncApi.getNanoContractState], ncId); + ncState = response.ncState; } catch (error) { if (error instanceof NanoRequest404Error) { yield put(nanoContractRegisterFailure(failureMessage.nanoContractStateNotFound)); @@ -95,7 +97,7 @@ export function* registerNanoContract({ payload }) { address, ncId, blueprintId: ncState.blueprint_id, - blueprintName: ncState.blueprint_name + blueprintName: ncState.blueprint_name, }; yield call(wallet.storage.registerNanoContract.bind(wallet.storage), ncId, nc); @@ -254,9 +256,35 @@ export function* requestHistoryNanoContract({ payload }) { } } +/** + * Process Nano Contract unregister request. + * @param {{ + * payload: { + * ncId: string; + * } + * }} action with request payload. + */ +export function* unregisterNanoContract({ payload }) { + const { ncId } = payload; + + const wallet = yield select((state) => state.wallet); + if (!wallet.isReady()) { + log.error('Fail unregistering Nano Contract because wallet is not ready yet.'); + // This will show user an error modal with the option to send the error to sentry. + yield put(onExceptionCaptured(new Error(failureMessage.walletNotReadyError), true)); + return; + } + + yield call(wallet.storage.unregisterNanoContract.bind(wallet.storage), ncId); + + log.debug(`Success unregistering Nano Contract. ncId = ${ncId}`); + yield put(nanoContractUnregisterSuccess({ ncId })); +} + export function* saga() { yield all([ takeEvery(types.NANOCONTRACT_REGISTER_REQUEST, registerNanoContract), takeEvery(types.NANOCONTRACT_HISTORY_REQUEST, requestHistoryNanoContract), + takeEvery(types.NANOCONTRACT_UNREGISTER_REQUEST, unregisterNanoContract), ]); } diff --git a/src/store.js b/src/store.js index 937f65605..f39675617 100644 --- a/src/store.js +++ b/src/store.js @@ -145,7 +145,21 @@ class HybridStore extends MemoryStore { await super.registerNanoContract(ncId, ncValue); const contracts = STORE.getItem(REGISTERED_NANO_CONTRACTS_KEY) || {}; contracts[ncId] = ncValue; - STORE.setItem(REGISTERED_NANO_CONTRACTS_KEY, contracts) + STORE.setItem(REGISTERED_NANO_CONTRACTS_KEY, contracts); + } + + /** + * Unregister a nano contract. + * + * @param {string} ncId Nano Contract ID. + * @returns {Promise} + * @async + */ + async unregisterNanoContract(ncId) { + await super.unregisterNanoContract(ncId); + const contracts = STORE.getItem(REGISTERED_NANO_CONTRACTS_KEY) || {}; + delete contracts[ncId]; + STORE.setItem(REGISTERED_NANO_CONTRACTS_KEY, contracts); } /** @@ -161,6 +175,7 @@ class HybridStore extends MemoryStore { if (cleanTokens) { // Remove from the cache STORE.removeItem(REGISTERED_TOKENS_KEY); + STORE.removeItem(REGISTERED_NANO_CONTRACTS_KEY); } } }