raiden-ts
Version:
Raiden Light Client Typescript/Javascript SDK
115 lines • 7.32 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.channelDepositEpic = void 0;
const constants_1 = require("@ethersproject/constants");
const findKey_1 = __importDefault(require("lodash/findKey"));
const isEqual_1 = __importDefault(require("lodash/isEqual"));
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const config_1 = require("../../config");
const actions_1 = require("../../utils/actions");
const error_1 = require("../../utils/error");
const rx_1 = require("../../utils/rx");
const types_1 = require("../../utils/types");
const actions_2 = require("../actions");
const state_1 = require("../state");
const utils_1 = require("../utils");
// returns observable of channel states, or errors in case of channelOpen.failure
function getChannel$({ meta }, action$, { latest$ }) {
return latest$.pipe((0, operators_1.pluck)('state', 'channels', (0, utils_1.channelKey)(meta)), (0, operators_1.filter)(types_1.isntNil), (0, operators_1.raceWith)(action$.pipe((0, operators_1.filter)(actions_2.channelOpen.failure.is), (0, operators_1.filter)((failure) => (0, isEqual_1.default)(failure.meta, meta)), (0, operators_1.map)(() => {
throw new error_1.RaidenError(error_1.ErrorCodes.CNL_NO_OPEN_CHANNEL_FOUND, meta);
}))));
}
// returns tuple of [required funds/allowance, new total deposit to channel]
function getDeposits({ payload }, channel) {
let deposit;
let totalDeposit;
if (!channel) {
[deposit, totalDeposit] =
'totalDeposit' in payload
? [payload.totalDeposit, payload.totalDeposit]
: [payload.deposit, payload.deposit];
}
else if ('totalDeposit' in payload) {
[deposit, totalDeposit] = [
payload.totalDeposit.sub(channel.own.deposit),
payload.totalDeposit,
];
}
else
[deposit, totalDeposit] = [
payload.deposit,
channel.own.deposit.add(payload.deposit),
];
(0, error_1.assert)(deposit.gt(0), error_1.ErrorCodes.DTA_INVALID_DEPOSIT);
return [deposit, totalDeposit];
}
// actually performs a deposit to new [totalDeposit]
function makeDeposit$(request, channelId$, [deposit, totalDeposit], deps) {
const { address, log, getTokenContract, getTokenNetworkContract, config$, latest$ } = deps;
const { tokenNetwork, partner } = request.meta;
// retryWhile from here
return latest$.pipe((0, operators_1.first)(), (0, operators_1.mergeMap)(({ state }) => {
const token = (0, findKey_1.default)(state.tokens, (tn) => tn === tokenNetwork);
return (0, utils_1.ensureApprovedBalance$)(getTokenContract(token), tokenNetwork, deposit, deps);
}), (0, operators_1.mergeMapTo)(channelId$),
// send setTotalDeposit transaction
(0, operators_1.mergeMap)((id) => (0, utils_1.transact)(getTokenNetworkContract(tokenNetwork), 'setTotalDeposit', [id, address, totalDeposit, partner], deps, { error: error_1.ErrorCodes.CNL_SETTOTALDEPOSIT_FAILED })),
// retry also txFail errors, since estimateGas can lag behind just-opened channel or
// just-approved allowance
(0, rx_1.retryWhile)((0, config_1.intervalFromConfig)(config$), { onErrors: error_1.commonAndFailTxErrors, log: log.info }));
}
/**
* A channelDeposit action requested by user or by channelOpenEpic
* Needs to be called on a previously monitored channel. Calls Token.approve for TokenNetwork
* and then set respective setTotalDeposit. If all tx go through successfuly, stop as
* channelDeposit.success action will instead be detected and reacted by channelEventsEpic.
* If anything detectable goes wrong, fires channelDeposit.failure instead
* Fails immediately if channel doesn't exist or isn't open, unless payload.waitOpen is true, in
* which case 'approve' in paralle and wait for confirmed channelOpen.success to 'setTotalDeposit'
*
* @param action$ - Observable of channelDeposit.request|channelOpen.failure actions
* @param state$ - Observable of RaidenStates
* @param deps - RaidenEpicDeps members
* @param deps.log - Logger instance
* @param deps.signer - Signer instance
* @param deps.address - Our address
* @param deps.main - Main signer/address
* @param deps.getTokenContract - Token contract instance getter
* @param deps.getTokenNetworkContract - TokenNetwork contract instance getter
* @param deps.provider - Eth provider
* @param deps.config$ - Config observable
* @param deps.latest$ - Latest observable
* @returns Observable of channelDeposit.failure actions
*/
function channelDepositEpic(action$, {}, deps) {
return action$.pipe((0, operators_1.filter)(actions_2.channelDeposit.request.is), (0, operators_1.withLatestFrom)(deps.config$),
// if minimumAllowance is default=big, we can relax the serialization to be per channel,
// instead of per token, as parallel deposits in different channels won't conflict on allowance
(0, operators_1.groupBy)(([{ meta }, { minimumAllowance }]) => minimumAllowance.eq(constants_1.MaxUint256) ? (0, utils_1.channelKey)(meta) : meta.tokenNetwork), (0, operators_1.mergeMap)((grouped$) => grouped$.pipe((0, operators_1.pluck)(0),
// groupBy + concatMap ensure actions handling is serialized in a given tokenNetwork
(0, operators_1.concatMap)((action) => deps.latest$.pipe((0, operators_1.first)(), (0, operators_1.mergeMap)(({ state }) => {
const channel = state.channels[(0, utils_1.channelKey)(action.meta)];
(0, error_1.assert)((!channel && action.payload.waitOpen) || channel?.state === state_1.ChannelState.open, [error_1.ErrorCodes.CNL_NO_OPEN_CHANNEL_FOUND, { state: channel?.state, id: channel?.id }]);
const channel$ = getChannel$(action, action$, deps);
const [deposit, totalDeposit] = getDeposits(action, channel);
return channel$.pipe(
// 'cache' channelId$ (if needed) while waiting for 'approve';
// also, subscribe early to error if seeing channelOpen.failure
(0, operators_1.connect)((channel$) =>
// already start 'approve' even while waiting for 'channel$'
makeDeposit$(action, channel$.pipe((0, operators_1.pluck)('id'), (0, operators_1.take)(1)), [deposit, totalDeposit], deps).pipe(
// hold this _lock_ (concatMap) until deposit has been confirmed or failed
(0, operators_1.concatWith)(action$.pipe((0, operators_1.filter)((0, actions_1.isConfirmationResponseOf)(actions_2.channelDeposit, action.meta)), (0, operators_1.first)((a) => !actions_2.channelDeposit.success.is(a) ||
a.payload.totalDeposit.gte(totalDeposit)))),
// complete on confirmation
(0, operators_1.takeUntil)(channel$.pipe((0, operators_1.filter)((channel) => channel.own.deposit.gte(totalDeposit))))), { connector: () => new rxjs_1.ReplaySubject(1) }),
// ignore success tx so it's picked by channelEventsEpic
(0, operators_1.ignoreElements)());
}), (0, operators_1.catchError)((error) => (0, rxjs_1.of)(actions_2.channelDeposit.failure(error, action.meta))))))), (0, rx_1.completeWith)(action$));
}
exports.channelDepositEpic = channelDepositEpic;
//# sourceMappingURL=deposit.js.map