UNPKG

raiden-ts

Version:

Raiden Light Client Typescript/Javascript SDK

121 lines 6.37 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.transferMediateEpic = void 0; const operators_1 = require("rxjs/operators"); const state_1 = require("../../channels/state"); const utils_1 = require("../../channels/utils"); const constants_1 = require("../../constants"); const types_1 = require("../../messages/types"); const utils_2 = require("../../transport/utils"); const types_2 = require("../../utils/types"); const actions_1 = require("../actions"); const state_2 = require("../state"); const utils_3 = require("../utils"); function shouldMediate(action, address, { caps }) { const isMediationEnabled = (0, utils_2.getCap)(caps, constants_1.Capabilities.MEDIATE); const isntTarget = action.meta.direction === state_2.Direction.RECEIVED && action.payload.message.target !== address; return !!isMediationEnabled && isntTarget; } /** * A valid route is one with a partner with which we have an open channel, and we're either in the * route (and partner is next hop) or the first hop is our partner (we received a clean route); * we return a clean set of routes where all of them go through the same partner, since we need to * know the outbound channel in order to calculate the mediation fees. * * @param received - received transferSigned * @param state - Current state (to check for open channels) * @param config - Current config * @param deps - Epics dependencies subset * @param deps.address - Our address * @param deps.log - Logger instance * @param deps.mediationFeeCalculator - Calculator for mediating transfer's fee * @returns Filtered and cleaned routes array, or undefined if no valid route was found */ function findValidPartner(received, state, config, { address, log, mediationFeeCalculator, }) { const message = received.payload.message; const inPartner = received.payload.partner; const tokenNetwork = message.token_network_address; const partnersWithOpenChannels = new Set(Object.values(state.channels) .filter((channel) => channel.tokenNetwork === tokenNetwork) .filter((channel) => channel.state === state_1.ChannelState.open) .map(({ partner }) => partner.address)); let decodedMetadata = { routes: [] }; try { decodedMetadata = (0, types_2.decode)(types_1.Metadata, message.metadata); } catch (e) { } for (const { route, address_metadata } of decodedMetadata.routes) { // support not being first address in route const outPartner = route[route.indexOf(address) + 1]; if (!outPartner || !partnersWithOpenChannels.has(outPartner)) continue; const channelIn = state.channels[(0, utils_1.channelKey)({ tokenNetwork, partner: inPartner })]; const channelOut = state.channels[(0, utils_1.channelKey)({ tokenNetwork, partner: outPartner })]; let fee; try { fee = mediationFeeCalculator.fee(config.mediationFees, channelIn, channelOut)(message.lock.amount); } catch (error) { log.warn('Mediation: could not calculate mediation fee, ignoring', { error }); continue; } const outPartnerPresence = (0, utils_3.searchValidMetadata)(address_metadata, outPartner); let metadata = message.metadata; // pass through metadata // iff partner requires a clear route (to be first address), clear original metadata if (!(0, utils_2.getCap)(outPartnerPresence?.payload.caps, constants_1.Capabilities.IMMUTABLE_METADATA)) metadata = (0, utils_3.clearMetadataRoute)(address, metadata); return { resolved: true, partner: outPartner, // on a transfer.request, fee is *added* to the value to get final sent amount, // therefore here it needs to contain a negative fee, which we will "earn" instead of pay fee: fee.mul(-1), userId: outPartnerPresence?.payload.userId, metadata, }; } log.warn('Mediation: could not find a suitable route, ignoring', { inputRoutes: decodedMetadata.routes, openPartners: partnersWithOpenChannels, }); } /** * When receiving a transfer not targeting us, create an outgoing transfer to target * Mediated transfers are handled the same way as unrelated received & sent pairs. The difference * is that we don't request the secret (as initiator would only reveal to target), and instead, * wait for SecretReveal to cascade back from outbound partner, then we unlock it and reveal back * to inbound partner, to get its Unlock. * If it doesn't succeed, if we didn't get reveal, we'll accept LockExpired, if we did and know * the secret but partner didn't unlock, we register on-chain as usual. * * @param action$ - Observable of incoming transferSigned transfers * @param state$ - Observable of RaidenStates * @param deps - Raiden epic deps * @param deps.address - Our address * @param deps.config$ - Latest observable * @param deps.log - Logger instance * @param deps.mediationFeeCalculator - Calculator for mediating transfer's fee * @returns Observable of outbound transfer.request actions */ function transferMediateEpic(action$, state$, deps) { const { address, config$ } = deps; return action$.pipe((0, operators_1.filter)(actions_1.transferSigned.is), (0, operators_1.withLatestFrom)(config$, state$), (0, operators_1.filter)(([action, config]) => shouldMediate(action, address, config)), (0, operators_1.map)(([action, config, state]) => { const message = action.payload.message; const secrethash = action.meta.secrethash; const outVia = findValidPartner(action, state, config, deps); if (!outVia) return; // request an outbound transfer to target; will fail if already sent return actions_1.transfer.request({ tokenNetwork: message.token_network_address, target: message.target, paymentId: message.payment_identifier, value: message.lock.amount, expiration: message.lock.expiration.toNumber(), initiator: message.initiator, ...outVia, }, { secrethash, direction: state_2.Direction.SENT }); }), (0, operators_1.filter)(types_2.isntNil)); } exports.transferMediateEpic = transferMediateEpic; //# sourceMappingURL=epics.js.map