UNPKG

raiden-ts

Version:

Raiden Light Client Typescript/Javascript SDK

253 lines 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const constants_1 = require("@ethersproject/constants"); const actions_1 = require("../actions"); const state_1 = require("../state"); const actions_2 = require("../transfers/actions"); const state_2 = require("../transfers/state"); const actions_3 = require("../utils/actions"); const redux_1 = require("../utils/redux"); const actions_4 = require("./actions"); const state_3 = require("./state"); const types_1 = require("./types"); const utils_1 = require("./utils"); // state.blockNumber specific reducer, handles only newBlock action const blockNumber = (0, actions_3.createReducer)(state_1.initialState.blockNumber).handle(actions_4.newBlock, ({}, { payload }) => payload.blockNumber); // state.tokens specific reducer, handles only tokenMonitored action const tokens = (0, actions_3.createReducer)(state_1.initialState.tokens).handle(actions_4.tokenMonitored, (state, { payload: { token, tokenNetwork } }) => state[token] === tokenNetwork ? state : { ...state, [token]: tokenNetwork }); function removeAction(pendingTxs, action) { return pendingTxs.filter((a) => a.type !== action.type || action.payload.txHash !== a.payload.txHash); } const pendingTxs = (state = state_1.initialState.pendingTxs, action) => { // filter out non-ConfirmableActions's if (!actions_1.ConfirmableAction.is(action)) return state; // if confirmed==undefined, deduplicate and add action to state else if (action.payload.confirmed === undefined) return [...removeAction(state, action), action]; // else (either confirmed or removed), remove from state else { const newState = removeAction(state, action); if (newState.length !== state.length) return newState; return state; } }; const emptyChannelEnd = { address: constants_1.AddressZero, deposit: constants_1.Zero, withdraw: constants_1.Zero, locks: [], balanceProof: types_1.BalanceProofZero, pendingWithdraws: [], nextNonce: constants_1.One, }; function channelOpenSuccessReducer(state, action) { const key = (0, utils_1.channelKey)(action.meta); // ignore if older than currently set channel, or unconfirmed or removed const prevChannel = state.channels[key]; if ((prevChannel?.openBlock ?? 0) >= action.payload.txBlock || (prevChannel?.id ?? 0) >= action.payload.id || !action.payload.confirmed) return state; const channel = { _id: (0, utils_1.channelUniqueKey)({ ...action.meta, id: action.payload.id }), id: action.payload.id, state: state_3.ChannelState.open, token: action.payload.token, tokenNetwork: action.meta.tokenNetwork, isFirstParticipant: action.payload.isFirstParticipant, openBlock: action.payload.txBlock, own: { ...emptyChannelEnd, address: state.address, }, partner: { ...emptyChannelEnd, address: action.meta.partner, }, }; return { ...state, channels: { ...state.channels, [key]: channel } }; } function channelUpdateOnchainBalanceStateReducer(state, action) { // ignore event if unconfirmed or removed if (!action.payload.confirmed) return state; const key = (0, utils_1.channelKey)(action.meta); let channel = state.channels[key]; if (channel?.state !== state_3.ChannelState.open || channel.id !== action.payload.id) return state; const end = action.payload.participant === channel.partner.address ? 'partner' : 'own'; const [prop, total, pendingWithdraws] = actions_4.channelWithdrawn.is(action) ? [ 'withdraw', action.payload.totalWithdraw, channel[end].pendingWithdraws.filter((req) => req.total_withdraw.gt(action.payload.totalWithdraw)), // on-chain withdraw clears <= withdraw messages, including the confirmed one ] : ['deposit', action.payload.totalDeposit, channel[end].pendingWithdraws]; if (total.lte(channel[end][prop])) return state; // ignore if past event channel = { ...channel, [end]: { ...channel[end], [prop]: total, pendingWithdraws, }, }; return { ...state, channels: { ...state.channels, [key]: channel } }; } function channelCloseSuccessReducer(state, action) { const key = (0, utils_1.channelKey)(action.meta); let channel = state.channels[key]; if (channel?.id !== action.payload.id) return state; // even on non-confirmed action, already set channel state as closing, so it can't be used for new transfers if (action.payload.confirmed === undefined && channel.state === state_3.ChannelState.open) channel = { ...channel, state: state_3.ChannelState.closing }; else if (!('closeBlock' in channel) && action.payload.confirmed) channel = { ...channel, state: state_3.ChannelState.closed, closeBlock: action.payload.txBlock, closeParticipant: action.payload.participant, }; else return state; return { ...state, channels: { ...state.channels, [key]: channel } }; } function channelUpdateStateReducer(state, action) { const key = (0, utils_1.channelKey)(action.meta); let channel = state.channels[key]; if (!channel) return state; if (actions_4.channelClose.request.is(action) && channel.state === state_3.ChannelState.open) { channel = { ...channel, state: state_3.ChannelState.closing }; } else if (actions_4.channelClose.failure.is(action) && channel.state === state_3.ChannelState.closing) { channel = { ...channel, state: state_3.ChannelState.open }; // rollback to open } else if (actions_4.channelSettle.request.is(action) && channel.state === state_3.ChannelState.settleable) { channel = { ...channel, state: state_3.ChannelState.settling }; } else if (actions_4.channelSettleable.is(action) && channel.state === state_3.ChannelState.closed) { channel = { ...channel, state: state_3.ChannelState.settleable }; } else { return state; } return { ...state, channels: { ...state.channels, [key]: channel } }; } function channelSettleSuccessReducer(state, action) { const key = (0, utils_1.channelKey)(action.meta); const channel = state.channels[key]; if (channel?.id !== action.payload.id) return state; // closeBlock,closeParticipant get defaults if channel was coop-settled const closeInfo = { closeBlock: 'closeBlock' in channel ? channel.closeBlock : 0, closeParticipant: 'closeParticipant' in channel ? channel.closeParticipant : constants_1.AddressZero, }; if (action.payload.confirmed === undefined && channel.state !== state_3.ChannelState.settling) // on non-confirmed action, set channel as settling return { ...state, channels: { ...state.channels, [key]: { ...channel, ...closeInfo, state: state_3.ChannelState.settling, }, }, }; else if (action.payload.confirmed) { const { [key]: _, ...channels } = state.channels; // pop [key] channel out of state.channels return { ...state, channels, oldChannels: { // persist popped channel on oldChannels with augmented channelKey ...state.oldChannels, [(0, utils_1.channelUniqueKey)(channel)]: { ...channel, ...closeInfo, state: state_3.ChannelState.settled, settleBlock: action.payload.txBlock, }, }, }; } return state; } /* Immutably mark locks as registed; returns reference to previous array if nothing changes */ function markLocksAsRegistered(locks, secrethash, registeredTimestamp) { let changed = false; const newLocks = locks.map((lock) => { if (lock.secrethash !== secrethash || lock.registered || lock.expiration.lte(registeredTimestamp)) return lock; changed = true; return { ...lock, registered: true }; }); if (changed) return newLocks; return locks; } function channelLockRegisteredReducer(state, action) { // now that secret is stored in transfer, if it's a confirmed on-chain registration, // also update channel's lock to reflect it if (!action.payload.confirmed) return state; const end = action.meta.direction === state_2.Direction.SENT ? 'own' : 'partner'; // iterate over channels and update any matching lock for (const [key, channel] of Object.entries(state.channels)) { const newLocks = markLocksAsRegistered(channel[end].locks, action.meta.secrethash, action.payload.txTimestamp); if (newLocks === channel[end].locks) continue; // only update state if locks changed state = { ...state, channels: { ...state.channels, [key]: { ...channel, [end]: { ...channel[end], locks: newLocks, }, }, }, }; } return state; } // handles actions which reducers need RaidenState const completeReducer = (0, actions_3.createReducer)(state_1.initialState) .handle(actions_4.channelOpen.success, channelOpenSuccessReducer) .handle([actions_4.channelDeposit.success, actions_4.channelWithdrawn], channelUpdateOnchainBalanceStateReducer) .handle([actions_4.channelClose.request, actions_4.channelClose.failure, actions_4.channelSettleable, actions_4.channelSettle.request], channelUpdateStateReducer) .handle(actions_4.channelClose.success, channelCloseSuccessReducer) .handle(actions_4.channelSettle.success, channelSettleSuccessReducer) .handle(actions_2.transferSecretRegister.success, channelLockRegisteredReducer); /** * Nested/combined reducer for channels * blockNumber, tokens & pendingTxs reducers get its own slice of the state, corresponding to the * name of the reducer. channels root reducer instead must be handled the complete state instead, * so it compose the output with each key/nested/combined state. */ const partialReducer = (0, redux_1.partialCombineReducers)({ blockNumber, tokens, pendingTxs }, state_1.initialState); /** * channelsReducer is a reduce-reducers like reducer; in contract with combineReducers, which * gives just a specific slice of the state to the reducer (like blockNumber above, which receives * only blockNumber), it actually act as a normal reducer by getting the whole state, but can do * it over several reducers, passing the output of one as the input for next * * @param state - previous root RaidenState * @param action - RaidenAction to try to handle * @returns - new RaidenState */ const channelsReducer = (state = state_1.initialState, action) => [partialReducer, completeReducer].reduce((s, reducer) => reducer(s, action), state); exports.default = channelsReducer; //# sourceMappingURL=reducer.js.map