raiden-ts
Version:
Raiden Light Client Typescript/Javascript SDK
114 lines • 7.37 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.coopSettleEpic = exports.coopSettleWithdrawReplyEpic = void 0;
const pick_1 = __importDefault(require("lodash/pick"));
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const channels_1 = require("../../channels");
const actions_1 = require("../../channels/actions");
const utils_1 = require("../../channels/utils");
const messages_1 = require("../../messages");
const error_1 = require("../../utils/error");
const ethers_1 = require("../../utils/ethers");
const rx_1 = require("../../utils/rx");
const actions_2 = require("../actions");
const state_1 = require("../state");
const utils_2 = require("./utils");
/**
* Upon valid [[WithdrawConfirmation]] for a [[WithdrawRequest]].coop_settle=true from partner,
* also send a [[WithdrawRequest]] with whole balance
*
* @param action$ - Observable of withdrawMessage.success actions
* @param state$ - Observable of RaidenStates
* @param deps - Epics dependencies
* @param deps.log - Logger instance
* @param deps.getTokenNetworkContract - TokenNetwork contract getter
* @returns Observable of withdraw.request(coop_settle=false) actions
*/
function coopSettleWithdrawReplyEpic(action$, state$, { log, getTokenNetworkContract }) {
return action$.pipe((0, operators_1.filter)(actions_2.withdrawMessage.success.is), (0, operators_1.filter)((action) => action.meta.direction === state_1.Direction.RECEIVED), (0, operators_1.withLatestFrom)(state$), (0, operators_1.mergeMap)(([action, state]) => {
const tokenNetworkContract = getTokenNetworkContract(action.meta.tokenNetwork);
return (0, ethers_1.checkContractHasMethod$)(tokenNetworkContract, 'cooperativeSettle').pipe((0, operators_1.mergeMap)(() => {
const channel = state.channels[(0, utils_1.channelKey)(action.meta)];
(0, error_1.assert)(channel?.state === channels_1.ChannelState.open, 'channel not open');
const req = channel.partner.pendingWithdraws.find((0, utils_2.matchWithdraw)(messages_1.MessageType.WITHDRAW_REQUEST, action.payload.message));
(0, error_1.assert)(req, 'no matching WithdrawRequest found'); // shouldn't happen
// only reply if this is a coop settle request from partner
if (!req.coop_settle)
return rxjs_1.EMPTY;
const { ownTotalWithdrawable } = (0, utils_1.channelAmounts)(channel);
return (0, rxjs_1.of)(actions_2.withdraw.request({ coopSettle: false }, {
...action.meta,
direction: state_1.Direction.SENT,
totalWithdraw: ownTotalWithdrawable,
}));
}), (0, operators_1.catchError)((error) => {
log.warn('Could not reply to CoopSettle request, ignoring', { action, error });
return rxjs_1.EMPTY;
}));
}));
}
exports.coopSettleWithdrawReplyEpic = coopSettleWithdrawReplyEpic;
/**
* When both valid [[WithdrawConfirmation]] for a [[WithdrawRequest]].coop_settle=true from us,
* send a channelSettle.request
*
* @param action$ - Observable of withdrawMessage.success actions
* @param state$ - Observable of RaidenStates
* @param deps - Epics dependencies
* @param deps.latest$ - Latest observable
* @param deps.config$ - Config observable
* @param deps.log - Logger instance
* @returns Observable of channelSettle.request|withdraw.failure|success|withdrawBusy actions
*/
function coopSettleEpic(action$, {}, { latest$, config$, log }) {
return action$.pipe((0, rx_1.dispatchRequestAndGetResponse)(actions_1.channelSettle, (requestSettle$) => action$.pipe((0, operators_1.filter)(actions_2.withdrawMessage.success.is), (0, operators_1.groupBy)((action) => (0, utils_1.channelKey)(action.meta)), (0, operators_1.mergeMap)((grouped$) => grouped$.pipe((0, operators_1.concatMap)((action) =>
// observable inside concatMap ensures the body is evaluated at subscription time
(0, rxjs_1.combineLatest)([latest$, config$]).pipe((0, operators_1.first)(), (0, operators_1.mergeMap)(([{ state }, { revealTimeout }]) => {
const channel = state.channels[(0, utils_1.channelKey)(action.meta)];
(0, error_1.assert)(channel?.state === channels_1.ChannelState.open, 'channel not open');
const { ownCapacity, partnerCapacity, ownTotalWithdrawable, partnerTotalWithdrawable, } = (0, utils_1.channelAmounts)(channel);
// when both capacities are zero, both sides should be ready; before that, just
// skip silently, a matching state may come later or withdraw will expire
(0, error_1.assert)((!channel.own.locks.length &&
!channel.partner.locks.length &&
ownCapacity.isZero()) ||
partnerCapacity.isZero(), '');
const ownReq = channel.own.pendingWithdraws.find((msg) => msg.type === messages_1.MessageType.WITHDRAW_REQUEST &&
msg.expiration.gte(Math.floor(Date.now() / 1e3 + revealTimeout)) &&
msg.total_withdraw.eq(ownTotalWithdrawable) &&
!!msg.coop_settle);
// not our request or expires too soon
(0, error_1.assert)(ownReq, 'own request not found');
const ownConfirmation = channel.own.pendingWithdraws.find((0, utils_2.matchWithdraw)(messages_1.MessageType.WITHDRAW_CONFIRMATION, ownReq));
const partnerReq = channel.partner.pendingWithdraws.find((msg) => msg.type === messages_1.MessageType.WITHDRAW_REQUEST &&
msg.expiration.gte(Math.floor(Date.now() / 1e3 + revealTimeout)) &&
msg.total_withdraw.eq(partnerTotalWithdrawable));
(0, error_1.assert)(partnerReq, 'partner request not found'); // shouldn't happen
const partnerConfirmation = channel.partner.pendingWithdraws.find((0, utils_2.matchWithdraw)(messages_1.MessageType.WITHDRAW_CONFIRMATION, partnerReq));
(0, error_1.assert)(ownConfirmation && partnerConfirmation, [
'no matching WithdrawConfirmations found',
{ ownConfirmation, partnerConfirmation },
]);
const withdrawMeta = (0, utils_2.withdrawMetaFromRequest)(ownReq, channel);
return requestSettle$(actions_1.channelSettle.request({
coopSettle: [
[ownReq, ownConfirmation],
[partnerReq, partnerConfirmation],
],
}, { tokenNetwork: withdrawMeta.tokenNetwork, partner: withdrawMeta.partner })).pipe((0, operators_1.map)((success) => actions_2.withdraw.success((0, pick_1.default)(success.payload, ['txBlock', 'txHash', 'confirmed']), withdrawMeta)), (0, operators_1.catchError)((err) => (0, rxjs_1.of)(actions_2.withdraw.failure(err, withdrawMeta))),
// prevents this withdraw from expire-failing while we're trying to settle
(0, operators_1.startWith)((0, actions_2.withdrawBusy)(undefined, withdrawMeta)));
}), (0, operators_1.catchError)((err) => {
if (err.message)
log.info(err.message, err.details);
// these errors are just the asserts, to be ignored;
// []actual errors are only the catched inside requestSettle$'s pipe
return rxjs_1.EMPTY;
}))))))));
}
exports.coopSettleEpic = coopSettleEpic;
//# sourceMappingURL=coopsettle.js.map