@raiden_network/raiden-cli
Version:
Raiden Light Client standalone app with a REST API via HTTP
198 lines (197 loc) • 9.44 kB
JavaScript
;
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;