raiden-ts
Version:
Raiden Light Client Typescript/Javascript SDK
121 lines • 6.37 kB
JavaScript
;
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