UNPKG

@raiden_network/raiden-cli

Version:

Raiden Light Client standalone app with a REST API via HTTP

198 lines (197 loc) 9.44 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeChannelsRouter = exports.ApiChannelState = void 0; const express_1 = require("express"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const raiden_ts_1 = require("raiden-ts"); const validation_1 = require("../utils/validation"); var ApiChannelState; (function (ApiChannelState) { ApiChannelState["opened"] = "opened"; ApiChannelState["closed"] = "closed"; ApiChannelState["settled"] = "settled"; })(ApiChannelState = exports.ApiChannelState || (exports.ApiChannelState = {})); function flattenChannelDictionary(channelDict) { // To flatten structure {token: {partner: [channel..], partner:...}, token...} return Object.values(channelDict).reduce((allChannels, tokenPartners) => allChannels.concat(Object.values(tokenPartners)), []); } function transformChannelStateForApi(state) { let apiState; switch (state) { case raiden_ts_1.ChannelState.open: case raiden_ts_1.ChannelState.closing: apiState = ApiChannelState.opened; break; case raiden_ts_1.ChannelState.closed: case raiden_ts_1.ChannelState.settleable: case raiden_ts_1.ChannelState.settling: apiState = ApiChannelState.closed; break; case raiden_ts_1.ChannelState.settled: apiState = ApiChannelState.settled; break; } return apiState; } function transformChannelFormatForApi(channel) { return { channel_identifier: channel.id.toString(), token_network_address: channel.tokenNetwork, partner_address: channel.partner, token_address: channel.token, balance: channel.capacity.toString(), total_deposit: channel.ownDeposit.toString(), total_withdraw: channel.ownWithdraw.toString(), state: transformChannelStateForApi(channel.state), settle_timeout: this.raiden.settleTimeout.toString(), reveal_timeout: this.raiden.config.revealTimeout.toString(), }; } async function getChannels(request, response) { const channelsDict = await (0, rxjs_1.firstValueFrom)(this.raiden.channels$); const token = request.params.tokenAddress; const partner = request.params.partnerAddress; if (token && partner) { const channel = channelsDict[token]?.[partner]; if (channel) { response.json(transformChannelFormatForApi.call(this, channel)); } else { response.status(404).send('The channel does not exist'); } } else { let channelsList = flattenChannelDictionary(channelsDict); if (token) channelsList = channelsList.filter((channel) => channel.token === token); if (partner) channelsList = channelsList.filter((channel) => channel.token === partner); response.json(channelsList.map(transformChannelFormatForApi.bind(this))); } } async function openChannel(request, response, next) { const token = request.body.token_address; const partner = request.body.partner_address; try { // TODO: We ignore the provided `reveal_timeout` until #1656 provides // a better solution. await this.raiden.openChannel(token, partner, { deposit: request.body.total_deposit?.toString(), }); const channel = await (0, rxjs_1.firstValueFrom)(this.raiden.channels$.pipe((0, operators_1.pluck)(token, partner), (0, operators_1.first)(raiden_ts_1.isntNil))); response.status(201).json(transformChannelFormatForApi.call(this, channel)); } catch (error) { if ((0, validation_1.isInsuficientFundsError)(error)) { response.status(402).send(error.message); } else if ((0, validation_1.isInvalidParameterError)(error) || (0, validation_1.isConflictError)(error)) { response.status(409).send({ message: error.message }); } else { next(error); } } } const allowedUpdateKeys = ['state', 'total_deposit', 'total_withdraw']; const allowedUpdateStates = [ApiChannelState.closed, ApiChannelState.settled]; function validateChannelUpdateBody(request, response, next) { const intersec = Object.keys(request.body).filter((k) => allowedUpdateKeys.includes(k)); if (intersec.length < 1) return response.status(400).send(`one of [${allowedUpdateKeys}] operations required`); else if (intersec.length > 1) return response.status(409).send(`more than one of [${allowedUpdateKeys}] requested`); if (request.body.state && !allowedUpdateStates.includes(request.body.state)) return response .status(400) .send(`invalid "state" requested: must be one of [${allowedUpdateStates}]`); return next(); } const closedStates = [raiden_ts_1.ChannelState.closed, raiden_ts_1.ChannelState.settleable, raiden_ts_1.ChannelState.settling]; async function updateChannelState(channel, newState) { let sub; if (newState === ApiChannelState.closed) { const promise = new Promise((resolve) => { sub = this.raiden.channels$ .pipe((0, operators_1.pluck)(channel.token, channel.partner), (0, operators_1.takeWhile)((channel) => !!channel && !closedStates.includes(channel.state), true), (0, operators_1.filter)(raiden_ts_1.isntNil), (0, operators_1.finalize)(resolve)) .subscribe((c) => (channel = c)); }); try { await this.raiden.closeChannel(channel.token, channel.partner); await promise; } finally { sub.unsubscribe(); } } else if (newState === ApiChannelState.settled) { const promise = new Promise((resolve) => { sub = this.raiden.channels$ .pipe((0, operators_1.pluck)(channel.token, channel.partner), (0, operators_1.takeWhile)((channel) => !!channel && channel.state !== raiden_ts_1.ChannelState.settled), (0, operators_1.finalize)(resolve)) .subscribe((c) => (channel = c)); }); try { await this.raiden.settleChannel(channel.token, channel.partner); await promise; } finally { sub.unsubscribe(); } } return channel; } async function updateChannelDeposit(channel, totalDeposit) { // amount = new deposit - previous deposit == (previous deposit - new deposit) * -1 const depositAmount = channel.ownDeposit.sub(totalDeposit).mul(-1); await this.raiden.depositChannel(channel.token, channel.partner, depositAmount); return await (0, rxjs_1.firstValueFrom)(this.raiden.channels$.pipe((0, operators_1.pluck)(channel.token, channel.partner), (0, operators_1.first)((channel) => channel.ownDeposit.gte(totalDeposit)))); } async function updateChannelWithdraw(channel, totalWithdraw) { // amount = new withdraw - previous withdraw == (previous withdraw - new withdraw) * -1 const withdrawAmount = channel.ownWithdraw.sub(totalWithdraw).mul(-1); await this.raiden.withdrawChannel(channel.token, channel.partner, withdrawAmount); return await (0, rxjs_1.firstValueFrom)(this.raiden.channels$.pipe((0, operators_1.pluck)(channel.token, channel.partner), (0, operators_1.first)((channel) => channel.ownWithdraw.gte(totalWithdraw)))); } async function updateChannel(request, response, next) { const token = request.params.tokenAddress; const partner = request.params.partnerAddress; try { let channel = await (0, rxjs_1.firstValueFrom)(this.raiden.channels$.pipe((0, operators_1.pluck)(token, partner))); if (!channel) throw new raiden_ts_1.RaidenError(raiden_ts_1.ErrorCodes.CNL_NO_OPEN_CHANNEL_FOUND); // one of conflict errors if (request.body.state) { channel = await updateChannelState.call(this, channel, request.body.state); } else if (request.body.total_deposit) { channel = await updateChannelDeposit.call(this, channel, request.body.total_deposit.toString()); } else if (request.body.total_withdraw) { channel = await updateChannelWithdraw.call(this, channel, request.body.total_withdraw.toString()); } response.status(200).json(transformChannelFormatForApi.call(this, channel)); } catch (error) { if ((0, validation_1.isInsuficientFundsError)(error)) { response.status(402).send(error.message); } else if ((0, validation_1.isInvalidParameterError)(error) || (0, validation_1.isConflictError)(error)) { response.status(409).json({ message: error.message }); } else { next(error); } } } /** * @param this - Cli object * @returns Router instance */ function makeChannelsRouter() { const router = (0, express_1.Router)(); router.get('/:tokenAddress?/:partnerAddress?', validation_1.validateOptionalAddressParameter.bind('tokenAddress'), validation_1.validateOptionalAddressParameter.bind('partnerAddress'), getChannels.bind(this)); router.put('/', openChannel.bind(this)); router.patch('/:tokenAddress/:partnerAddress', validation_1.validateAddressParameter.bind('tokenAddress'), validation_1.validateAddressParameter.bind('partnerAddress'), validateChannelUpdateBody, updateChannel.bind(this)); return router; } exports.makeChannelsRouter = makeChannelsRouter;