mirror of
https://github.com/apotdevin/thunderhub.git
synced 2025-02-21 22:11:37 +01:00
Merge remote-tracking branch 'thunderhub-server/master'
This commit is contained in:
commit
15caa283cf
89 changed files with 9175 additions and 0 deletions
2
.ebignore
Normal file
2
.ebignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
/node_modules
|
||||
.env
|
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
#webpack
|
||||
/dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
# Elastic Beanstalk Files
|
||||
.elasticbeanstalk/*
|
||||
!.elasticbeanstalk/*.cfg.yml
|
||||
!.elasticbeanstalk/*.global.yml
|
2
.npmrc
Normal file
2
.npmrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Force npm to run node-gyp also as root, preventing permission denied errors in AWS with npm@5
|
||||
unsafe-perm=true
|
7
.prettierrc
Normal file
7
.prettierrc
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"printWidth": 80,
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": true
|
||||
}
|
4
nodemon.json
Normal file
4
nodemon.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"ignore": ["src"],
|
||||
"watch": ["dist/"]
|
||||
}
|
56
package.json
Normal file
56
package.json
Normal file
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"name": "thunderhub",
|
||||
"version": "0.1.3",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "webpack --config webpack.production.js",
|
||||
"build:dev": "webpack --config webpack.development.js",
|
||||
"start": "node dist/server",
|
||||
"dev": "nodemon dist/server",
|
||||
"deploy": "yarn build && eb deploy"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/apotdevin/thunderhub.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/graphql-depth-limit": "^1.1.2",
|
||||
"@types/graphql-iso-date": "^3.3.3",
|
||||
"@types/node-fetch": "^2.5.3",
|
||||
"@types/underscore": "^1.9.4",
|
||||
"apollo-server": "^2.10.1",
|
||||
"base64url": "^3.0.1",
|
||||
"date-fns": "^2.8.1",
|
||||
"dotenv": "^8.2.0",
|
||||
"graphql": "^14.6.0",
|
||||
"graphql-depth-limit": "^1.1.0",
|
||||
"graphql-iso-date": "^3.6.1",
|
||||
"graphql-rate-limit": "^2.0.1",
|
||||
"ln-service": "^47.14.7",
|
||||
"underscore": "^1.9.1",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/webpack-env": "^1.15.1",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"husky": "^4.2.3",
|
||||
"prettier": "^1.18.2",
|
||||
"pretty-quick": "^2.0.0",
|
||||
"ts-loader": "^6.2.1",
|
||||
"typescript": "^3.7.5",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-merge": "^4.2.2",
|
||||
"webpack-node-externals": "^1.7.2"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "pretty-quick --staged"
|
||||
}
|
||||
}
|
||||
}
|
7
src/helpers/defaultProps.ts
Normal file
7
src/helpers/defaultProps.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { GraphQLNonNull, GraphQLBoolean } from 'graphql';
|
||||
import { AuthType } from '../schemaTypes/Auth';
|
||||
|
||||
export const defaultParams = {
|
||||
auth: { type: new GraphQLNonNull(AuthType) },
|
||||
logger: { type: GraphQLBoolean },
|
||||
};
|
62
src/helpers/helpers.ts
Normal file
62
src/helpers/helpers.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import base64url from 'base64url';
|
||||
import { authenticatedLndGrpc } from 'ln-service';
|
||||
|
||||
export const getIp = (req: any) => {
|
||||
if (!req || !req.headers) {
|
||||
return '';
|
||||
}
|
||||
const forwarded = req.headers['x-forwarded-for'];
|
||||
const before = forwarded
|
||||
? forwarded.split(/, /)[0]
|
||||
: req.connection.remoteAddress;
|
||||
const ip = process.env.NODE_ENV === 'development' ? '1.2.3.4' : before;
|
||||
return ip;
|
||||
};
|
||||
|
||||
export const getAuthLnd = (auth: {
|
||||
cert: string;
|
||||
macaroon: string;
|
||||
host: string;
|
||||
}) => {
|
||||
const encodedCert = auth.cert || '';
|
||||
const encodedMacaroon = auth.macaroon || '';
|
||||
const socket = auth.host || '';
|
||||
|
||||
const cert = encodedCert;
|
||||
const macaroon = encodedMacaroon;
|
||||
|
||||
const params =
|
||||
encodedCert !== ''
|
||||
? {
|
||||
cert,
|
||||
macaroon,
|
||||
socket,
|
||||
}
|
||||
: { macaroon, socket };
|
||||
|
||||
const { lnd } = authenticatedLndGrpc(params);
|
||||
|
||||
return lnd;
|
||||
};
|
||||
|
||||
export const getErrorDetails = (error: any[]): string => {
|
||||
let details = '';
|
||||
if (error.length > 2) {
|
||||
if (error[2].err) {
|
||||
details = error[2].err.details;
|
||||
} else if (error[2].details) {
|
||||
details = error[2].details;
|
||||
}
|
||||
}
|
||||
|
||||
return details;
|
||||
};
|
||||
|
||||
export const getErrorMsg = (error: any[]): string => {
|
||||
const code = error[0];
|
||||
const msg = error[1];
|
||||
|
||||
let details = getErrorDetails(error);
|
||||
|
||||
return JSON.stringify({ code, msg, details });
|
||||
};
|
38
src/helpers/logger.ts
Normal file
38
src/helpers/logger.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { createLogger, format, transports } from "winston";
|
||||
import path from "path";
|
||||
|
||||
const combinedFormat =
|
||||
process.env.NODE_ENV === "development"
|
||||
? format.combine(
|
||||
format.label({
|
||||
label: path.basename(
|
||||
process && process.mainModule ? process.mainModule.filename : ""
|
||||
)
|
||||
}),
|
||||
format.splat(),
|
||||
format.colorize(),
|
||||
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
||||
format.printf(
|
||||
(info: any) =>
|
||||
`${info.timestamp} ${info.level} [${info.label}]: ${info.message}`
|
||||
)
|
||||
)
|
||||
: format.combine(
|
||||
format.label({
|
||||
label: path.basename(
|
||||
process && process.mainModule ? process.mainModule.filename : ""
|
||||
)
|
||||
}),
|
||||
format.splat(),
|
||||
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
||||
format.printf(
|
||||
(info: any) =>
|
||||
`${info.timestamp} ${info.level} [${info.label}]: ${info.message}`
|
||||
)
|
||||
);
|
||||
|
||||
export const logger = createLogger({
|
||||
level: process.env.LOG_LEVEL || "silly",
|
||||
format: combinedFormat,
|
||||
transports: [new transports.Console()]
|
||||
});
|
22
src/helpers/rateLimiter.ts
Normal file
22
src/helpers/rateLimiter.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { getGraphQLRateLimiter } from 'graphql-rate-limit';
|
||||
import { RateConfig } from '../utils/rateLimitConfig';
|
||||
|
||||
const rateLimiter = getGraphQLRateLimiter({
|
||||
identifyContext: (ctx: string) => ctx,
|
||||
formatError: () => 'Rate Limit Reached',
|
||||
});
|
||||
|
||||
export const requestLimiter = async (rate: string, field: string) => {
|
||||
if (!RateConfig[field]) throw new Error('Invalid Rate Field');
|
||||
const { max, window } = RateConfig[field];
|
||||
const errorMessage = await rateLimiter(
|
||||
{
|
||||
parent: rate,
|
||||
args: {},
|
||||
context: rate,
|
||||
info: { fieldName: field } as any,
|
||||
},
|
||||
{ max, window },
|
||||
);
|
||||
if (errorMessage) throw new Error(errorMessage);
|
||||
};
|
25
src/main.ts
Normal file
25
src/main.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { ApolloServer } from 'apollo-server';
|
||||
import { thunderHubSchema } from './schemas';
|
||||
import { logger } from './helpers/logger';
|
||||
import { getIp } from './helpers/helpers';
|
||||
import depthLimit from 'graphql-depth-limit';
|
||||
|
||||
const server = new ApolloServer({
|
||||
schema: thunderHubSchema,
|
||||
context: async ({ req }: any) => {
|
||||
const ip = getIp(req);
|
||||
return { ip };
|
||||
},
|
||||
validationRules: [
|
||||
depthLimit(2, { ignore: [/_trusted$/, 'idontcare', 'whatever'] }),
|
||||
],
|
||||
});
|
||||
|
||||
server.listen({ port: process.env.PORT || 3001 }).then(({ url }: any) => {
|
||||
logger.info(`Server ready at ${url}`);
|
||||
});
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept();
|
||||
module.hot.dispose(() => server.stop());
|
||||
}
|
18
src/schemaTypes/Auth.ts
Normal file
18
src/schemaTypes/Auth.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { GraphQLInputObjectType, GraphQLString } from 'graphql';
|
||||
|
||||
export const AuthType = new GraphQLInputObjectType({
|
||||
name: 'authType',
|
||||
fields: () => {
|
||||
return {
|
||||
host: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
macaroon: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
cert: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
15
src/schemaTypes/mutation.ts/channels/closeChannel.ts
Normal file
15
src/schemaTypes/mutation.ts/channels/closeChannel.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { GraphQLObjectType, GraphQLString } from "graphql";
|
||||
|
||||
export const CloseChannelType = new GraphQLObjectType({
|
||||
name: "closeChannelType",
|
||||
fields: () => {
|
||||
return {
|
||||
transactionId: {
|
||||
type: GraphQLString
|
||||
},
|
||||
transactionOutputIndex: {
|
||||
type: GraphQLString
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
15
src/schemaTypes/mutation.ts/channels/openChannel.ts
Normal file
15
src/schemaTypes/mutation.ts/channels/openChannel.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { GraphQLObjectType, GraphQLString } from "graphql";
|
||||
|
||||
export const OpenChannelType = new GraphQLObjectType({
|
||||
name: "openChannelType",
|
||||
fields: () => {
|
||||
return {
|
||||
transactionId: {
|
||||
type: GraphQLString
|
||||
},
|
||||
transactionOutputIndex: {
|
||||
type: GraphQLString
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
17
src/schemaTypes/mutation.ts/invoice/createInvoice.ts
Normal file
17
src/schemaTypes/mutation.ts/invoice/createInvoice.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { GraphQLObjectType, GraphQLString, GraphQLInt } from "graphql";
|
||||
import { GraphQLDateTime } from "graphql-iso-date";
|
||||
|
||||
export const InvoiceType = new GraphQLObjectType({
|
||||
name: "invoiceType",
|
||||
fields: () => {
|
||||
return {
|
||||
chainAddress: { type: GraphQLString },
|
||||
createdAt: { type: GraphQLDateTime },
|
||||
description: { type: GraphQLString },
|
||||
id: { type: GraphQLString },
|
||||
request: { type: GraphQLString },
|
||||
secret: { type: GraphQLString },
|
||||
tokens: { type: GraphQLInt }
|
||||
};
|
||||
}
|
||||
});
|
34
src/schemaTypes/mutation.ts/invoice/decode.ts
Normal file
34
src/schemaTypes/mutation.ts/invoice/decode.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import {
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
GraphQLInt,
|
||||
GraphQLList,
|
||||
} from 'graphql';
|
||||
|
||||
const RoutesType = new GraphQLObjectType({
|
||||
name: 'routeType',
|
||||
fields: () => ({
|
||||
baseFeeMTokens: { type: GraphQLString },
|
||||
channel: { type: GraphQLString },
|
||||
cltvDelta: { type: GraphQLInt },
|
||||
feeRate: { type: GraphQLInt },
|
||||
publicKey: { type: GraphQLString },
|
||||
}),
|
||||
});
|
||||
|
||||
export const DecodeType = new GraphQLObjectType({
|
||||
name: 'decodeType',
|
||||
fields: () => {
|
||||
return {
|
||||
chainAddress: { type: GraphQLString },
|
||||
cltvDelta: { type: GraphQLInt },
|
||||
description: { type: GraphQLString },
|
||||
descriptionHash: { type: GraphQLString },
|
||||
destination: { type: GraphQLString },
|
||||
expiresAt: { type: GraphQLString },
|
||||
id: { type: GraphQLString },
|
||||
routes: { type: new GraphQLList(RoutesType) },
|
||||
tokens: { type: GraphQLInt },
|
||||
};
|
||||
},
|
||||
});
|
40
src/schemaTypes/mutation.ts/invoice/parsePayment.ts
Normal file
40
src/schemaTypes/mutation.ts/invoice/parsePayment.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import {
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
GraphQLInt,
|
||||
GraphQLBoolean,
|
||||
GraphQLList
|
||||
} from "graphql";
|
||||
import { GraphQLDateTime } from "graphql-iso-date";
|
||||
|
||||
const RouteType = new GraphQLObjectType({
|
||||
name: "RouteType",
|
||||
fields: () => ({
|
||||
mTokenFee: { type: GraphQLString },
|
||||
channel: { type: GraphQLString },
|
||||
cltvDelta: { type: GraphQLInt },
|
||||
feeRate: { type: GraphQLInt },
|
||||
publicKey: { type: GraphQLString }
|
||||
})
|
||||
});
|
||||
|
||||
export const ParsePaymentType = new GraphQLObjectType({
|
||||
name: "parsePaymentType",
|
||||
fields: () => {
|
||||
return {
|
||||
chainAddresses: { type: new GraphQLList(GraphQLString) },
|
||||
cltvDelta: { type: GraphQLInt },
|
||||
createdAt: { type: GraphQLDateTime },
|
||||
description: { type: GraphQLString },
|
||||
descriptionHash: { type: GraphQLString },
|
||||
destination: { type: GraphQLString },
|
||||
expiresAt: { type: GraphQLDateTime },
|
||||
id: { type: GraphQLString },
|
||||
isExpired: { type: GraphQLBoolean },
|
||||
mTokens: { type: GraphQLString },
|
||||
network: { type: GraphQLString },
|
||||
routes: { type: new GraphQLList(RouteType) },
|
||||
tokens: { type: GraphQLInt }
|
||||
};
|
||||
}
|
||||
});
|
35
src/schemaTypes/mutation.ts/invoice/pay.ts
Normal file
35
src/schemaTypes/mutation.ts/invoice/pay.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import {
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
GraphQLInt,
|
||||
GraphQLList,
|
||||
GraphQLBoolean
|
||||
} from "graphql";
|
||||
|
||||
const HopsType = new GraphQLObjectType({
|
||||
name: "hopsType",
|
||||
fields: () => ({
|
||||
channel: { type: GraphQLString },
|
||||
channelCapacity: { type: GraphQLInt },
|
||||
mTokenFee: { type: GraphQLString },
|
||||
forwardMTokens: { type: GraphQLString },
|
||||
timeout: { type: GraphQLInt }
|
||||
})
|
||||
});
|
||||
|
||||
export const PayType = new GraphQLObjectType({
|
||||
name: "payType",
|
||||
fields: () => {
|
||||
return {
|
||||
fee: { type: GraphQLInt },
|
||||
feeMTokens: { type: GraphQLString },
|
||||
hops: { type: new GraphQLList(HopsType) },
|
||||
id: { type: GraphQLString },
|
||||
isConfirmed: { type: GraphQLBoolean },
|
||||
isOutgoing: { type: GraphQLBoolean },
|
||||
mtokens: { type: GraphQLString },
|
||||
secret: { type: GraphQLString },
|
||||
tokens: { type: GraphQLInt }
|
||||
};
|
||||
}
|
||||
});
|
19
src/schemaTypes/mutation.ts/onchain/sentToAddress.ts
Normal file
19
src/schemaTypes/mutation.ts/onchain/sentToAddress.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import {
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
GraphQLInt,
|
||||
GraphQLBoolean,
|
||||
} from 'graphql';
|
||||
|
||||
export const SendToType = new GraphQLObjectType({
|
||||
name: 'sendToType',
|
||||
fields: () => {
|
||||
return {
|
||||
confirmationCount: { type: GraphQLString },
|
||||
id: { type: GraphQLString },
|
||||
isConfirmed: { type: GraphQLBoolean },
|
||||
isOutgoing: { type: GraphQLBoolean },
|
||||
tokens: { type: GraphQLInt },
|
||||
};
|
||||
},
|
||||
});
|
16
src/schemaTypes/query/channels/channelBalance.ts
Normal file
16
src/schemaTypes/query/channels/channelBalance.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { GraphQLObjectType } from 'graphql';
|
||||
import { GraphQLInt } from 'graphql';
|
||||
|
||||
export const ChannelBalanceType = new GraphQLObjectType({
|
||||
name: 'channelBalanceType',
|
||||
fields: () => {
|
||||
return {
|
||||
confirmedBalance: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
pendingBalance: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
27
src/schemaTypes/query/channels/channelFees.ts
Normal file
27
src/schemaTypes/query/channels/channelFees.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { GraphQLObjectType, GraphQLInt, GraphQLString } from 'graphql';
|
||||
|
||||
export const ChannelFeeType = new GraphQLObjectType({
|
||||
name: 'channelFeeType',
|
||||
fields: () => {
|
||||
return {
|
||||
alias: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
color: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
baseFee: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
feeRate: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
transactionId: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
transactionVout: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
21
src/schemaTypes/query/channels/channelReport.ts
Normal file
21
src/schemaTypes/query/channels/channelReport.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { GraphQLObjectType, GraphQLInt } from 'graphql';
|
||||
|
||||
export const ChannelReportType = new GraphQLObjectType({
|
||||
name: 'channelReportType',
|
||||
fields: () => {
|
||||
return {
|
||||
local: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
remote: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
maxIn: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
maxOut: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
46
src/schemaTypes/query/channels/channels.ts
Normal file
46
src/schemaTypes/query/channels/channels.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { GraphQLObjectType, GraphQLBoolean, GraphQLString } from 'graphql';
|
||||
import { GraphQLInt } from 'graphql';
|
||||
|
||||
export const PartnerNodeType = new GraphQLObjectType({
|
||||
name: 'partnerNodeType',
|
||||
fields: () => {
|
||||
return {
|
||||
alias: { type: GraphQLString },
|
||||
capacity: { type: GraphQLString },
|
||||
channel_count: { type: GraphQLInt },
|
||||
color: { type: GraphQLString },
|
||||
updated_at: { type: GraphQLString },
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const ChannelType = new GraphQLObjectType({
|
||||
name: 'channelType',
|
||||
fields: () => {
|
||||
return {
|
||||
capacity: { type: GraphQLInt },
|
||||
commit_transaction_fee: { type: GraphQLInt },
|
||||
commit_transaction_weight: { type: GraphQLInt },
|
||||
id: { type: GraphQLString },
|
||||
is_active: { type: GraphQLBoolean },
|
||||
is_closing: { type: GraphQLBoolean },
|
||||
is_opening: { type: GraphQLBoolean },
|
||||
is_partner_initiated: { type: GraphQLBoolean },
|
||||
is_private: { type: GraphQLBoolean },
|
||||
is_static_remote_key: { type: GraphQLBoolean },
|
||||
local_balance: { type: GraphQLInt },
|
||||
local_reserve: { type: GraphQLInt },
|
||||
partner_public_key: { type: GraphQLString },
|
||||
received: { type: GraphQLInt },
|
||||
remote_balance: { type: GraphQLInt },
|
||||
remote_reserve: { type: GraphQLInt },
|
||||
sent: { type: GraphQLInt },
|
||||
time_offline: { type: GraphQLInt },
|
||||
time_online: { type: GraphQLInt },
|
||||
transaction_id: { type: GraphQLString },
|
||||
transaction_vout: { type: GraphQLInt },
|
||||
unsettled_balance: { type: GraphQLInt },
|
||||
partner_node_info: { type: PartnerNodeType },
|
||||
};
|
||||
},
|
||||
});
|
26
src/schemaTypes/query/channels/closedChannels.ts
Normal file
26
src/schemaTypes/query/channels/closedChannels.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { GraphQLObjectType, GraphQLBoolean, GraphQLString } from 'graphql';
|
||||
import { GraphQLInt } from 'graphql';
|
||||
import { PartnerNodeType } from './channels';
|
||||
|
||||
export const ClosedChannelType = new GraphQLObjectType({
|
||||
name: 'closedChannelType',
|
||||
fields: () => {
|
||||
return {
|
||||
capacity: { type: GraphQLInt },
|
||||
close_confirm_height: { type: GraphQLInt },
|
||||
close_transaction_id: { type: GraphQLString },
|
||||
final_local_balance: { type: GraphQLInt },
|
||||
final_time_locked_balance: { type: GraphQLInt },
|
||||
id: { type: GraphQLString },
|
||||
is_breach_close: { type: GraphQLBoolean },
|
||||
is_cooperative_close: { type: GraphQLBoolean },
|
||||
is_funding_cancel: { type: GraphQLBoolean },
|
||||
is_local_force_close: { type: GraphQLBoolean },
|
||||
is_remote_force_close: { type: GraphQLBoolean },
|
||||
partner_public_key: { type: GraphQLString },
|
||||
transaction_id: { type: GraphQLString },
|
||||
transaction_vout: { type: GraphQLInt },
|
||||
partner_node_info: { type: PartnerNodeType },
|
||||
};
|
||||
},
|
||||
});
|
27
src/schemaTypes/query/channels/pendingChannels.ts
Normal file
27
src/schemaTypes/query/channels/pendingChannels.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { GraphQLObjectType, GraphQLBoolean, GraphQLString } from 'graphql';
|
||||
import { GraphQLInt } from 'graphql';
|
||||
import { PartnerNodeType } from './channels';
|
||||
|
||||
// TODO: INCOMPLETE TYPE
|
||||
export const PendingChannelType = new GraphQLObjectType({
|
||||
name: 'pendingChannelType',
|
||||
fields: () => {
|
||||
return {
|
||||
close_transaction_id: { type: GraphQLString },
|
||||
is_active: { type: GraphQLBoolean },
|
||||
is_closing: { type: GraphQLBoolean },
|
||||
is_opening: { type: GraphQLBoolean },
|
||||
local_balance: { type: GraphQLInt },
|
||||
local_reserve: { type: GraphQLInt },
|
||||
partner_public_key: { type: GraphQLString },
|
||||
received: { type: GraphQLInt },
|
||||
remote_balance: { type: GraphQLInt },
|
||||
remote_reserve: { type: GraphQLInt },
|
||||
sent: { type: GraphQLInt },
|
||||
transaction_fee: { type: GraphQLInt },
|
||||
transaction_id: { type: GraphQLString },
|
||||
transaction_vout: { type: GraphQLInt },
|
||||
partner_node_info: { type: PartnerNodeType },
|
||||
};
|
||||
},
|
||||
});
|
18
src/schemaTypes/query/data/bitcoinFee.ts
Normal file
18
src/schemaTypes/query/data/bitcoinFee.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { GraphQLObjectType, GraphQLInt } from 'graphql';
|
||||
|
||||
export const BitcoinFeeType = new GraphQLObjectType({
|
||||
name: 'bitcoinFeeType',
|
||||
fields: () => {
|
||||
return {
|
||||
fast: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
halfHour: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
hour: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
13
src/schemaTypes/query/flow/InOut.ts
Normal file
13
src/schemaTypes/query/flow/InOut.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { GraphQLObjectType, GraphQLString, GraphQLInt } from 'graphql';
|
||||
|
||||
export const InOutType = new GraphQLObjectType({
|
||||
name: 'InOutType',
|
||||
fields: () => {
|
||||
return {
|
||||
invoices: { type: GraphQLString },
|
||||
payments: { type: GraphQLString },
|
||||
confirmedInvoices: { type: GraphQLInt },
|
||||
unConfirmedInvoices: { type: GraphQLInt },
|
||||
};
|
||||
},
|
||||
});
|
34
src/schemaTypes/query/info/networkInfo.ts
Normal file
34
src/schemaTypes/query/info/networkInfo.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { GraphQLObjectType, GraphQLString } from "graphql";
|
||||
import { GraphQLInt } from "graphql";
|
||||
|
||||
export const NetworkInfoType = new GraphQLObjectType({
|
||||
name: "networkInfoType",
|
||||
fields: () => {
|
||||
return {
|
||||
averageChannelSize: {
|
||||
type: GraphQLString
|
||||
},
|
||||
channelCount: {
|
||||
type: GraphQLInt
|
||||
},
|
||||
maxChannelSize: {
|
||||
type: GraphQLString
|
||||
},
|
||||
medianChannelSize: {
|
||||
type: GraphQLString
|
||||
},
|
||||
minChannelSize: {
|
||||
type: GraphQLInt
|
||||
},
|
||||
nodeCount: {
|
||||
type: GraphQLInt
|
||||
},
|
||||
notRecentlyUpdatedPolicyCount: {
|
||||
type: GraphQLInt
|
||||
},
|
||||
totalCapacity: {
|
||||
type: GraphQLString
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
30
src/schemaTypes/query/info/nodeInfo.ts
Normal file
30
src/schemaTypes/query/info/nodeInfo.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import {
|
||||
GraphQLObjectType,
|
||||
GraphQLBoolean,
|
||||
GraphQLString,
|
||||
GraphQLList,
|
||||
} from 'graphql';
|
||||
import { GraphQLInt } from 'graphql';
|
||||
|
||||
export const NodeInfoType = new GraphQLObjectType({
|
||||
name: 'nodeInfoType',
|
||||
fields: () => {
|
||||
return {
|
||||
chains: { type: new GraphQLList(GraphQLString) },
|
||||
color: { type: GraphQLString },
|
||||
active_channels_count: { type: GraphQLInt },
|
||||
closed_channels_count: { type: GraphQLInt },
|
||||
alias: { type: GraphQLString },
|
||||
current_block_hash: { type: GraphQLString },
|
||||
current_block_height: { type: GraphQLBoolean },
|
||||
is_synced_to_chain: { type: GraphQLBoolean },
|
||||
is_synced_to_graph: { type: GraphQLBoolean },
|
||||
latest_block_at: { type: GraphQLString },
|
||||
peers_count: { type: GraphQLInt },
|
||||
pending_channels_count: { type: GraphQLInt },
|
||||
public_key: { type: GraphQLString },
|
||||
uris: { type: new GraphQLList(GraphQLString) },
|
||||
version: { type: GraphQLString },
|
||||
};
|
||||
},
|
||||
});
|
16
src/schemaTypes/query/transactions/chainTransactions.ts
Normal file
16
src/schemaTypes/query/transactions/chainTransactions.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { GraphQLObjectType, GraphQLString, GraphQLList } from 'graphql';
|
||||
import { GraphQLInt } from 'graphql';
|
||||
|
||||
export const GetChainTransactionsType = new GraphQLObjectType({
|
||||
name: 'getTransactionsType',
|
||||
fields: () => ({
|
||||
block_id: { type: GraphQLString },
|
||||
confirmation_count: { type: GraphQLInt },
|
||||
confirmation_height: { type: GraphQLInt },
|
||||
created_at: { type: GraphQLString },
|
||||
fee: { type: GraphQLInt },
|
||||
id: { type: GraphQLString },
|
||||
output_addresses: { type: new GraphQLList(GraphQLString) },
|
||||
tokens: { type: GraphQLInt },
|
||||
}),
|
||||
});
|
27
src/schemaTypes/query/transactions/forwards.ts
Normal file
27
src/schemaTypes/query/transactions/forwards.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { GraphQLObjectType, GraphQLString, GraphQLList } from 'graphql';
|
||||
import { GraphQLInt } from 'graphql';
|
||||
|
||||
export const ForwardType = new GraphQLObjectType({
|
||||
name: 'forwardType',
|
||||
fields: () => ({
|
||||
created_at: { type: GraphQLString },
|
||||
fee: { type: GraphQLInt },
|
||||
fee_mtokens: { type: GraphQLString },
|
||||
incoming_channel: { type: GraphQLString },
|
||||
incoming_alias: { type: GraphQLString },
|
||||
incoming_color: { type: GraphQLString },
|
||||
mtokens: { type: GraphQLString },
|
||||
outgoing_channel: { type: GraphQLString },
|
||||
outgoing_alias: { type: GraphQLString },
|
||||
outgoing_color: { type: GraphQLString },
|
||||
tokens: { type: GraphQLInt },
|
||||
}),
|
||||
});
|
||||
|
||||
export const GetForwardType = new GraphQLObjectType({
|
||||
name: 'getForwardType',
|
||||
fields: () => ({
|
||||
token: { type: GraphQLString },
|
||||
forwards: { type: new GraphQLList(ForwardType) },
|
||||
}),
|
||||
});
|
9
src/schemaTypes/query/transactions/resume.ts
Normal file
9
src/schemaTypes/query/transactions/resume.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { GraphQLObjectType, GraphQLString } from 'graphql';
|
||||
|
||||
export const GetResumeType = new GraphQLObjectType({
|
||||
name: 'getResumeType',
|
||||
fields: () => ({
|
||||
token: { type: GraphQLString },
|
||||
resume: { type: GraphQLString },
|
||||
}),
|
||||
});
|
14
src/schemas/index.ts
Normal file
14
src/schemas/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { GraphQLSchema, GraphQLObjectType } from "graphql";
|
||||
import { query } from "./query";
|
||||
import { mutation } from "./mutations";
|
||||
|
||||
export const thunderHubSchema = new GraphQLSchema({
|
||||
query: new GraphQLObjectType({
|
||||
name: "Query",
|
||||
fields: query
|
||||
}),
|
||||
mutation: new GraphQLObjectType({
|
||||
name: "Mutation",
|
||||
fields: mutation
|
||||
})
|
||||
});
|
49
src/schemas/mutations/channels/closeChannel.ts
Normal file
49
src/schemas/mutations/channels/closeChannel.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { closeChannel as lnCloseChannel } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import {
|
||||
GraphQLBoolean,
|
||||
GraphQLString,
|
||||
GraphQLInt,
|
||||
GraphQLNonNull,
|
||||
} from 'graphql';
|
||||
import { CloseChannelType } from '../../../schemaTypes/mutation.ts/channels/closeChannel';
|
||||
import { getErrorMsg, getAuthLnd } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface CloseChannelProps {
|
||||
transaction_id: string;
|
||||
transaction_vout: string;
|
||||
}
|
||||
|
||||
export const closeChannel = {
|
||||
type: CloseChannelType,
|
||||
args: {
|
||||
...defaultParams,
|
||||
id: { type: new GraphQLNonNull(GraphQLString) },
|
||||
forceClose: { type: GraphQLBoolean },
|
||||
targetConfirmations: { type: GraphQLInt },
|
||||
tokensPerVByte: { type: GraphQLInt },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'closeChannel');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const info: CloseChannelProps = await lnCloseChannel({
|
||||
lnd,
|
||||
id: params.id,
|
||||
target_confirmations: params.targetConfirmations,
|
||||
tokens_per_vbyte: params.tokensPerVByte,
|
||||
});
|
||||
return {
|
||||
transactionId: info.transaction_id,
|
||||
transactionOutputIndex: info.transaction_vout,
|
||||
};
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error closing channel: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
9
src/schemas/mutations/channels/index.ts
Normal file
9
src/schemas/mutations/channels/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { closeChannel } from './closeChannel';
|
||||
import { openChannel } from './openChannel';
|
||||
import { updateFees } from './updateFees';
|
||||
|
||||
export const channels = {
|
||||
closeChannel,
|
||||
openChannel,
|
||||
updateFees,
|
||||
};
|
50
src/schemas/mutations/channels/openChannel.ts
Normal file
50
src/schemas/mutations/channels/openChannel.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { openChannel as lnOpenChannel } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import {
|
||||
GraphQLBoolean,
|
||||
GraphQLString,
|
||||
GraphQLInt,
|
||||
GraphQLNonNull,
|
||||
} from 'graphql';
|
||||
import { OpenChannelType } from '../../../schemaTypes/mutation.ts/channels/openChannel';
|
||||
import { getErrorMsg, getAuthLnd } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface OpenChannelProps {
|
||||
transaction_id: string;
|
||||
transaction_vout: string;
|
||||
}
|
||||
|
||||
export const openChannel = {
|
||||
type: OpenChannelType,
|
||||
args: {
|
||||
...defaultParams,
|
||||
amount: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
partnerPublicKey: { type: new GraphQLNonNull(GraphQLString) },
|
||||
tokensPerVByte: { type: GraphQLInt },
|
||||
isPrivate: { type: GraphQLBoolean },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'openChannel');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const info: OpenChannelProps = await lnOpenChannel({
|
||||
lnd,
|
||||
is_private: params.isPrivate,
|
||||
local_tokens: params.amount,
|
||||
partner_public_key: params.partnerPublicKey,
|
||||
chain_fee_tokens_per_vbyte: params.tokensPerVByte,
|
||||
});
|
||||
return {
|
||||
transactionId: info.transaction_id,
|
||||
transactionOutputIndex: info.transaction_vout,
|
||||
};
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error opening channel: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
51
src/schemas/mutations/channels/updateFees.ts
Normal file
51
src/schemas/mutations/channels/updateFees.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { updateRoutingFees } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLBoolean, GraphQLString, GraphQLInt } from 'graphql';
|
||||
import { getErrorMsg, getAuthLnd } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
export const updateFees = {
|
||||
type: GraphQLBoolean,
|
||||
args: {
|
||||
...defaultParams,
|
||||
transactionId: { type: GraphQLString },
|
||||
transactionVout: { type: GraphQLInt },
|
||||
baseFee: { type: GraphQLInt },
|
||||
feeRate: { type: GraphQLInt },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'updateFees');
|
||||
|
||||
const {
|
||||
auth,
|
||||
transactionId,
|
||||
transactionVout,
|
||||
baseFee,
|
||||
feeRate,
|
||||
} = params;
|
||||
|
||||
const lnd = getAuthLnd(auth);
|
||||
|
||||
if (!baseFee && !feeRate) {
|
||||
throw new Error('No Base Fee or Fee Rate to update channels');
|
||||
}
|
||||
|
||||
const props = {
|
||||
lnd,
|
||||
transaction_id: transactionId,
|
||||
transaction_vout: transactionVout,
|
||||
...(params.baseFee && { base_fee_tokens: params.baseFee }),
|
||||
...(params.feeRate && { fee_rate: params.feeRate }),
|
||||
};
|
||||
|
||||
try {
|
||||
await updateRoutingFees(props);
|
||||
return true;
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error updating routing fees: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
5
src/schemas/mutations/index.ts
Normal file
5
src/schemas/mutations/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { channels } from './channels';
|
||||
import { invoices } from './invoices';
|
||||
import { onChain } from './onchain';
|
||||
|
||||
export const mutation = { ...channels, ...invoices, ...onChain };
|
50
src/schemas/mutations/invoices/createInvoice.ts
Normal file
50
src/schemas/mutations/invoices/createInvoice.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { createInvoice as createInvoiceRequest } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLNonNull, GraphQLInt } from 'graphql';
|
||||
import { InvoiceType } from '../../../schemaTypes/mutation.ts/invoice/createInvoice';
|
||||
import { getErrorMsg, getAuthLnd } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface InvoiceProps {
|
||||
chain_address: string;
|
||||
created_at: string;
|
||||
description: string;
|
||||
id: string;
|
||||
request: string;
|
||||
secret: string;
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
export const createInvoice = {
|
||||
type: InvoiceType,
|
||||
args: {
|
||||
...defaultParams,
|
||||
amount: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'createInvoice');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const invoice: InvoiceProps = await createInvoiceRequest({
|
||||
lnd,
|
||||
tokens: params.amount,
|
||||
});
|
||||
|
||||
return {
|
||||
chainAddress: invoice.chain_address,
|
||||
createdAt: invoice.created_at,
|
||||
description: invoice.description,
|
||||
id: invoice.id,
|
||||
request: invoice.request,
|
||||
secret: invoice.secret,
|
||||
tokens: invoice.tokens,
|
||||
};
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error creating invoice: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
92
src/schemas/mutations/invoices/decode.ts
Normal file
92
src/schemas/mutations/invoices/decode.ts
Normal file
|
@ -0,0 +1,92 @@
|
|||
import { decodePaymentRequest } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLString, GraphQLNonNull } from 'graphql';
|
||||
import { getErrorMsg, getAuthLnd } from '../../../helpers/helpers';
|
||||
import { DecodeType } from '../../../schemaTypes/mutation.ts/invoice/decode';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface RouteProps {
|
||||
base_fee_mtokens: string;
|
||||
channel: string;
|
||||
cltv_delta: number;
|
||||
fee_rate: number;
|
||||
public_key: string;
|
||||
}
|
||||
|
||||
interface DecodeProps {
|
||||
chain_address: string;
|
||||
cltv_delta: number;
|
||||
description: string;
|
||||
description_hash: string;
|
||||
destination: string;
|
||||
expires_at: string;
|
||||
id: string;
|
||||
routes: RouteProps[][];
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
export const decodeRequest = {
|
||||
type: DecodeType,
|
||||
args: {
|
||||
...defaultParams,
|
||||
request: { type: new GraphQLNonNull(GraphQLString) },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'decode');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const decode: DecodeProps = await decodePaymentRequest({
|
||||
lnd,
|
||||
request: params.request,
|
||||
});
|
||||
|
||||
const routes = decode.routes.map(route => {
|
||||
route.map(nodeChannel => {
|
||||
const {
|
||||
base_fee_mtokens,
|
||||
channel,
|
||||
cltv_delta,
|
||||
fee_rate,
|
||||
public_key,
|
||||
} = nodeChannel;
|
||||
return {
|
||||
baseFeeTokens: base_fee_mtokens,
|
||||
channel,
|
||||
cltvDelta: cltv_delta,
|
||||
feeRate: fee_rate,
|
||||
publicKey: public_key,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const {
|
||||
chain_address,
|
||||
cltv_delta,
|
||||
description,
|
||||
description_hash,
|
||||
destination,
|
||||
expires_at,
|
||||
id,
|
||||
tokens,
|
||||
} = decode;
|
||||
|
||||
return {
|
||||
chainAddress: chain_address,
|
||||
cltvDelta: cltv_delta,
|
||||
description,
|
||||
descriptionHash: description_hash,
|
||||
destination,
|
||||
expiresAt: expires_at,
|
||||
id,
|
||||
routes,
|
||||
tokens,
|
||||
};
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error paying request: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
13
src/schemas/mutations/invoices/index.ts
Normal file
13
src/schemas/mutations/invoices/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { parsePayment } from './parsePayment';
|
||||
import { pay } from './pay';
|
||||
import { createInvoice } from './createInvoice';
|
||||
import { decodeRequest } from './decode';
|
||||
import { payViaRoute } from './payViaRoute';
|
||||
|
||||
export const invoices = {
|
||||
parsePayment,
|
||||
pay,
|
||||
createInvoice,
|
||||
decodeRequest,
|
||||
payViaRoute,
|
||||
};
|
80
src/schemas/mutations/invoices/parsePayment.ts
Normal file
80
src/schemas/mutations/invoices/parsePayment.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
import { parsePaymentRequest } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLString, GraphQLNonNull } from 'graphql';
|
||||
import { ParsePaymentType } from '../../../schemaTypes/mutation.ts/invoice/parsePayment';
|
||||
import { getErrorMsg, getAuthLnd } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface RouteProps {
|
||||
base_fee_mtokens: string;
|
||||
channel: string;
|
||||
cltv_delta: number;
|
||||
fee_rate: number;
|
||||
public_key: string;
|
||||
}
|
||||
|
||||
interface RequestProps {
|
||||
chain_addresses: string[];
|
||||
cltv_delta: number;
|
||||
created_at: string;
|
||||
description: string;
|
||||
description_hash: string;
|
||||
destination: string;
|
||||
expires_at: string;
|
||||
id: string;
|
||||
is_expired: string;
|
||||
mtokens: string;
|
||||
network: string;
|
||||
routes: RouteProps[];
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
export const parsePayment = {
|
||||
type: ParsePaymentType,
|
||||
args: {
|
||||
...defaultParams,
|
||||
request: { type: new GraphQLNonNull(GraphQLString) },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'parsePayment');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const request: RequestProps = await parsePaymentRequest({
|
||||
lnd,
|
||||
request: params.request,
|
||||
});
|
||||
|
||||
const routes = request.routes.map(route => {
|
||||
return {
|
||||
mTokenFee: route.base_fee_mtokens,
|
||||
channel: route.channel,
|
||||
cltvDelta: route.cltv_delta,
|
||||
feeRate: route.fee_rate,
|
||||
publicKey: route.public_key,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
chainAddresses: request.chain_addresses,
|
||||
cltvDelta: request.cltv_delta,
|
||||
createdAt: request.created_at,
|
||||
description: request.description,
|
||||
descriptionHash: request.description_hash,
|
||||
destination: request.destination,
|
||||
expiresAt: request.expires_at,
|
||||
id: request.id,
|
||||
isExpired: request.is_expired,
|
||||
mTokens: request.mtokens,
|
||||
network: request.network,
|
||||
routes: routes,
|
||||
tokens: request.tokens,
|
||||
};
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error decoding request: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
73
src/schemas/mutations/invoices/pay.ts
Normal file
73
src/schemas/mutations/invoices/pay.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { pay as payRequest } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLString, GraphQLNonNull } from 'graphql';
|
||||
import { PayType } from '../../../schemaTypes/mutation.ts/invoice/pay';
|
||||
import { getErrorMsg, getAuthLnd } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface HopProps {
|
||||
channel: string;
|
||||
channel_capacity: number;
|
||||
fee_mtokens: string;
|
||||
forward_mtokens: string;
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
interface RequestProps {
|
||||
fee: number;
|
||||
fee_mtokens: string;
|
||||
hops: HopProps[];
|
||||
id: string;
|
||||
is_confirmed: boolean;
|
||||
is_outgoing: boolean;
|
||||
mtokens: string;
|
||||
secret: string;
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
// TODO: Allow path payments as well
|
||||
export const pay = {
|
||||
type: PayType,
|
||||
args: {
|
||||
...defaultParams,
|
||||
request: { type: new GraphQLNonNull(GraphQLString) },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'pay');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const payment: RequestProps = await payRequest({
|
||||
lnd,
|
||||
request: params.request,
|
||||
});
|
||||
|
||||
const hops = payment.hops.map(hop => {
|
||||
return {
|
||||
channel: hop.channel,
|
||||
channelCapacity: hop.channel_capacity,
|
||||
mTokenFee: hop.fee_mtokens,
|
||||
forwardMTokens: hop.forward_mtokens,
|
||||
timeout: hop.timeout,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
fee: payment.fee,
|
||||
feeMTokens: payment.fee_mtokens,
|
||||
hops: hops,
|
||||
id: payment.id,
|
||||
isConfirmed: payment.is_confirmed,
|
||||
isOutgoing: payment.is_outgoing,
|
||||
mtokens: payment.mtokens,
|
||||
secret: payment.secret,
|
||||
tokens: payment.tokens,
|
||||
};
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error paying request: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
43
src/schemas/mutations/invoices/payViaRoute.ts
Normal file
43
src/schemas/mutations/invoices/payViaRoute.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { GraphQLNonNull, GraphQLBoolean, GraphQLString } from 'graphql';
|
||||
import { payViaRoutes, createInvoice } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
export const payViaRoute = {
|
||||
type: GraphQLBoolean,
|
||||
args: {
|
||||
...defaultParams,
|
||||
route: { type: new GraphQLNonNull(GraphQLString) },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'payViaRoute');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
let route;
|
||||
try {
|
||||
route = JSON.parse(params.route);
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Corrupt route json: %o', error);
|
||||
throw new Error('Corrupt Route JSON');
|
||||
}
|
||||
|
||||
const { id } = await createInvoice({
|
||||
lnd,
|
||||
tokens: params.tokens,
|
||||
description: 'Balancing Channel',
|
||||
}).catch((error: any) => {
|
||||
params.logger && logger.error('Error getting invoice: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
});
|
||||
|
||||
await payViaRoutes({ lnd, routes: [route], id }).catch((error: any) => {
|
||||
params.logger && logger.error('Error making payment: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
38
src/schemas/mutations/onchain/getAddress.ts
Normal file
38
src/schemas/mutations/onchain/getAddress.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { createChainAddress } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLString, GraphQLBoolean } from 'graphql';
|
||||
import { getErrorMsg, getAuthLnd } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface AddressProps {
|
||||
address: string;
|
||||
}
|
||||
|
||||
export const createAddress = {
|
||||
type: GraphQLString,
|
||||
args: {
|
||||
...defaultParams,
|
||||
nested: { type: GraphQLBoolean },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'getAddress');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
const format = params.nested ? 'np2wpkh' : 'p2wpkh';
|
||||
|
||||
try {
|
||||
const address: AddressProps = await createChainAddress({
|
||||
lnd,
|
||||
is_unused: true,
|
||||
format,
|
||||
});
|
||||
|
||||
return address.address;
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error creating address: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
7
src/schemas/mutations/onchain/index.ts
Normal file
7
src/schemas/mutations/onchain/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { createAddress } from './getAddress';
|
||||
import { sendToAddress } from './sendToAddress';
|
||||
|
||||
export const onChain = {
|
||||
createAddress,
|
||||
sendToAddress,
|
||||
};
|
66
src/schemas/mutations/onchain/sendToAddress.ts
Normal file
66
src/schemas/mutations/onchain/sendToAddress.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { sendToChainAddress } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import {
|
||||
GraphQLNonNull,
|
||||
GraphQLString,
|
||||
GraphQLBoolean,
|
||||
GraphQLInt,
|
||||
} from 'graphql';
|
||||
import { getErrorMsg, getAuthLnd } from '../../../helpers/helpers';
|
||||
import { SendToType } from '../../../schemaTypes/mutation.ts/onchain/sentToAddress';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface SendProps {
|
||||
confirmation_count: number;
|
||||
id: string;
|
||||
is_confirmed: boolean;
|
||||
is_outgoing: boolean;
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
export const sendToAddress = {
|
||||
type: SendToType,
|
||||
args: {
|
||||
...defaultParams,
|
||||
address: { type: new GraphQLNonNull(GraphQLString) },
|
||||
tokens: { type: GraphQLInt },
|
||||
fee: { type: GraphQLInt },
|
||||
target: { type: GraphQLInt },
|
||||
sendAll: { type: GraphQLBoolean },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'sendToAddress');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
const props = params.fee
|
||||
? { fee_tokens_per_vbyte: params.fee }
|
||||
: params.target
|
||||
? { target_confirmations: params.target }
|
||||
: {};
|
||||
|
||||
const sendAll = params.sendAll ? { is_send_all: true } : {};
|
||||
|
||||
try {
|
||||
const send: SendProps = await sendToChainAddress({
|
||||
lnd,
|
||||
address: params.address,
|
||||
tokens: params.tokens,
|
||||
...props,
|
||||
...sendAll,
|
||||
});
|
||||
|
||||
return {
|
||||
confirmationCount: send.confirmation_count,
|
||||
id: send.id,
|
||||
isConfirmed: send.is_confirmed,
|
||||
isOutgoing: send.is_outgoing,
|
||||
tokens: send.tokens,
|
||||
};
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error creating address: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
26
src/schemas/query/backup/getBackups.ts
Normal file
26
src/schemas/query/backup/getBackups.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { getBackups as getLnBackups } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLString } from 'graphql';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
export const getBackups = {
|
||||
type: GraphQLString,
|
||||
args: defaultParams,
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'getBackups');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const backups = await getLnBackups({
|
||||
lnd,
|
||||
});
|
||||
return JSON.stringify(backups);
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error getting backups: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
9
src/schemas/query/backup/index.ts
Normal file
9
src/schemas/query/backup/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { getBackups } from './getBackups';
|
||||
import { verifyBackups } from './verifyBackups';
|
||||
import { recoverFunds } from './recoverFunds';
|
||||
|
||||
export const backupQueries = {
|
||||
getBackups,
|
||||
verifyBackups,
|
||||
recoverFunds,
|
||||
};
|
45
src/schemas/query/backup/recoverFunds.ts
Normal file
45
src/schemas/query/backup/recoverFunds.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { recoverFundsFromChannels } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLNonNull, GraphQLString, GraphQLBoolean } from 'graphql';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface BackupProps {
|
||||
backup: string;
|
||||
}
|
||||
|
||||
export const recoverFunds = {
|
||||
type: GraphQLBoolean,
|
||||
args: {
|
||||
...defaultParams,
|
||||
backup: { type: new GraphQLNonNull(GraphQLString) },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'recoverFunds');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
let backupObj: BackupProps = { backup: '' };
|
||||
try {
|
||||
backupObj = JSON.parse(params.backup);
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Corrupt backup file: %o', error);
|
||||
throw new Error('Corrupt backup file');
|
||||
}
|
||||
|
||||
const { backup } = backupObj;
|
||||
|
||||
try {
|
||||
await recoverFundsFromChannels({
|
||||
lnd,
|
||||
backup,
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error recovering funds from channels: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
46
src/schemas/query/backup/verifyBackups.ts
Normal file
46
src/schemas/query/backup/verifyBackups.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { verifyBackups as verifyLnBackups } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLNonNull, GraphQLString, GraphQLBoolean } from 'graphql';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface BackupProps {
|
||||
backup: string;
|
||||
channels: {}[];
|
||||
}
|
||||
|
||||
export const verifyBackups = {
|
||||
type: GraphQLBoolean,
|
||||
args: {
|
||||
...defaultParams,
|
||||
backup: { type: new GraphQLNonNull(GraphQLString) },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'verifyBackups');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
let backupObj: BackupProps = { backup: '', channels: [] };
|
||||
try {
|
||||
backupObj = JSON.parse(params.backup);
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Corrupt backup file: %o', error);
|
||||
throw new Error('Corrupt backup file');
|
||||
}
|
||||
|
||||
const { backup, channels } = backupObj;
|
||||
|
||||
try {
|
||||
const { is_valid } = await verifyLnBackups({
|
||||
lnd,
|
||||
backup,
|
||||
channels,
|
||||
});
|
||||
return is_valid;
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error verifying backups: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
37
src/schemas/query/channels/channelBalance.ts
Normal file
37
src/schemas/query/channels/channelBalance.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { getChannelBalance as getLnChannelBalance } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { ChannelBalanceType } from '../../../schemaTypes/query/channels/channelBalance';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface ChannelBalanceProps {
|
||||
channel_balance: number;
|
||||
pending_balance: number;
|
||||
}
|
||||
|
||||
export const getChannelBalance = {
|
||||
type: ChannelBalanceType,
|
||||
args: defaultParams,
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'channelBalance');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const channelBalance: ChannelBalanceProps = await getLnChannelBalance(
|
||||
{
|
||||
lnd,
|
||||
},
|
||||
);
|
||||
return {
|
||||
confirmedBalance: channelBalance.channel_balance,
|
||||
pendingBalance: channelBalance.pending_balance,
|
||||
};
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting channel balance: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
89
src/schemas/query/channels/channelFees.ts
Normal file
89
src/schemas/query/channels/channelFees.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
import { getFeeRates, getChannels, getNode } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLList } from 'graphql';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { ChannelFeeType } from '../../../schemaTypes/query/channels/channelFees';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface GetChannelsProps {
|
||||
channels: ChannelsProps[];
|
||||
}
|
||||
|
||||
interface GetFeeRatesProps {
|
||||
channels: ChannelFeesProps[];
|
||||
}
|
||||
|
||||
interface ChannelsProps {
|
||||
partner_public_key: number;
|
||||
transaction_id: string;
|
||||
}
|
||||
|
||||
interface ChannelFeesProps {
|
||||
base_fee: number;
|
||||
fee_rate: number;
|
||||
transaction_id: string;
|
||||
transaction_vout: number;
|
||||
}
|
||||
|
||||
interface NodeProps {
|
||||
alias: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export const getChannelFees = {
|
||||
type: new GraphQLList(ChannelFeeType),
|
||||
args: defaultParams,
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'channelFees');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const channels: GetChannelsProps = await getChannels({ lnd });
|
||||
|
||||
const channelFees: GetFeeRatesProps = await getFeeRates({ lnd });
|
||||
|
||||
const getConsolidated = () =>
|
||||
Promise.all(
|
||||
channels.channels.map(async channel => {
|
||||
const nodeInfo: NodeProps = await getNode({
|
||||
lnd,
|
||||
is_omitting_channels: true,
|
||||
public_key: channel.partner_public_key,
|
||||
});
|
||||
|
||||
const fees = channelFees.channels.find(
|
||||
channelFee =>
|
||||
channelFee.transaction_id ===
|
||||
channel.transaction_id,
|
||||
);
|
||||
if (!fees) return;
|
||||
const {
|
||||
base_fee,
|
||||
fee_rate,
|
||||
transaction_id,
|
||||
transaction_vout,
|
||||
} = fees;
|
||||
|
||||
return {
|
||||
alias: nodeInfo.alias,
|
||||
color: nodeInfo.color,
|
||||
baseFee: base_fee,
|
||||
feeRate: fee_rate,
|
||||
transactionId: transaction_id,
|
||||
transactionVout: transaction_vout,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const consolidated = await getConsolidated();
|
||||
|
||||
return consolidated;
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting channel fees: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
65
src/schemas/query/channels/channelReport.ts
Normal file
65
src/schemas/query/channels/channelReport.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { getChannels } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { ChannelReportType } from '../../../schemaTypes/query/channels/channelReport';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface GetChannelsProps {
|
||||
channels: ChannelsProps[];
|
||||
}
|
||||
|
||||
interface ChannelsProps {
|
||||
remote_balance: number;
|
||||
local_balance: number;
|
||||
}
|
||||
|
||||
export const getChannelReport = {
|
||||
type: ChannelReportType,
|
||||
args: defaultParams,
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'channelReport');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const channels: GetChannelsProps = await getChannels({ lnd });
|
||||
|
||||
if (channels.channels.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxOutgoing = Math.max.apply(
|
||||
Math,
|
||||
channels.channels.map(o => {
|
||||
return o.local_balance;
|
||||
}),
|
||||
);
|
||||
|
||||
const maxIncoming = Math.max.apply(
|
||||
Math,
|
||||
channels.channels.map(o => {
|
||||
return o.remote_balance;
|
||||
}),
|
||||
);
|
||||
|
||||
const consolidated = channels.channels.reduce((p, c) => {
|
||||
return {
|
||||
remote_balance: p.remote_balance + c.remote_balance,
|
||||
local_balance: p.local_balance + c.local_balance,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
local: consolidated.local_balance,
|
||||
remote: consolidated.remote_balance,
|
||||
maxIn: maxIncoming,
|
||||
maxOut: maxOutgoing,
|
||||
};
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting channel report: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
81
src/schemas/query/channels/channels.ts
Normal file
81
src/schemas/query/channels/channels.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { GraphQLList, GraphQLBoolean } from 'graphql';
|
||||
import { getChannels as getLnChannels, getNode } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { ChannelType } from '../../../schemaTypes/query/channels/channels';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface ChannelListProps {
|
||||
channels: ChannelProps[];
|
||||
}
|
||||
|
||||
interface ChannelProps {
|
||||
capacity: number;
|
||||
commit_transaction_fee: number;
|
||||
commit_transaction_weight: number;
|
||||
id: string;
|
||||
is_active: boolean;
|
||||
is_closing: boolean;
|
||||
is_opening: boolean;
|
||||
is_partner_initiated: boolean;
|
||||
is_private: boolean;
|
||||
is_static_remote_key: boolean;
|
||||
local_balance: number;
|
||||
local_reserve: number;
|
||||
partner_public_key: string;
|
||||
pending_payments: [];
|
||||
received: number;
|
||||
remote_balance: number;
|
||||
remote_reserve: number;
|
||||
sent: number;
|
||||
time_offline: number;
|
||||
time_online: number;
|
||||
transaction_id: string;
|
||||
transaction_vout: number;
|
||||
unsettled_balance: number;
|
||||
}
|
||||
|
||||
export const getChannels = {
|
||||
type: new GraphQLList(ChannelType),
|
||||
args: {
|
||||
...defaultParams,
|
||||
active: { type: GraphQLBoolean },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'channels');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const channelList: ChannelListProps = await getLnChannels({
|
||||
lnd,
|
||||
is_active: params.active,
|
||||
});
|
||||
|
||||
const getChannelList = () =>
|
||||
Promise.all(
|
||||
channelList.channels.map(async channel => {
|
||||
const nodeInfo = await getNode({
|
||||
lnd,
|
||||
is_omitting_channels: true,
|
||||
public_key: channel.partner_public_key,
|
||||
});
|
||||
|
||||
return {
|
||||
...channel,
|
||||
partner_node_info: {
|
||||
...nodeInfo,
|
||||
},
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const channels = await getChannelList();
|
||||
return channels;
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error getting channels: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
67
src/schemas/query/channels/closedChannels.ts
Normal file
67
src/schemas/query/channels/closedChannels.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { GraphQLList, GraphQLString } from 'graphql';
|
||||
import { getClosedChannels as getLnClosedChannels, getNode } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { ClosedChannelType } from '../../../schemaTypes/query/channels/closedChannels';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { getErrorMsg, getAuthLnd } from '../../../helpers/helpers';
|
||||
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface ChannelListProps {
|
||||
channels: ChannelProps[];
|
||||
}
|
||||
|
||||
interface ChannelProps {
|
||||
capacity: number;
|
||||
close_confirm_height: number;
|
||||
close_transaction_id: string;
|
||||
final_local_balance: number;
|
||||
final_time_locked_balance: number;
|
||||
id: string;
|
||||
is_breach_close: boolean;
|
||||
is_cooperative_close: boolean;
|
||||
is_funding_cancel: boolean;
|
||||
is_local_force_close: boolean;
|
||||
is_remote_force_close: boolean;
|
||||
partner_public_key: string;
|
||||
transaction_id: string;
|
||||
transaction_vout: number;
|
||||
}
|
||||
|
||||
export const getClosedChannels = {
|
||||
type: new GraphQLList(ClosedChannelType),
|
||||
args: {
|
||||
...defaultParams,
|
||||
type: { type: GraphQLString },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'closedChannels');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const closedChannels: ChannelListProps = await getLnClosedChannels({
|
||||
lnd,
|
||||
});
|
||||
const channels = closedChannels.channels.map(async channel => {
|
||||
const nodeInfo = await getNode({
|
||||
lnd,
|
||||
is_omitting_channels: true,
|
||||
public_key: channel.partner_public_key,
|
||||
});
|
||||
|
||||
return {
|
||||
...channel,
|
||||
partner_node_info: {
|
||||
...nodeInfo,
|
||||
},
|
||||
};
|
||||
});
|
||||
return channels;
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting closed channels: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
15
src/schemas/query/channels/index.ts
Normal file
15
src/schemas/query/channels/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { getChannelBalance } from './channelBalance';
|
||||
import { getChannels } from './channels';
|
||||
import { getClosedChannels } from './closedChannels';
|
||||
import { getPendingChannels } from './pendingChannels';
|
||||
import { getChannelFees } from './channelFees';
|
||||
import { getChannelReport } from './channelReport';
|
||||
|
||||
export const channelQueries = {
|
||||
getChannelBalance,
|
||||
getChannels,
|
||||
getClosedChannels,
|
||||
getPendingChannels,
|
||||
getChannelFees,
|
||||
getChannelReport,
|
||||
};
|
71
src/schemas/query/channels/pendingChannels.ts
Normal file
71
src/schemas/query/channels/pendingChannels.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import {
|
||||
getPendingChannels as getLnPendingChannels,
|
||||
getNode,
|
||||
} from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { PendingChannelType } from '../../../schemaTypes/query/channels/pendingChannels';
|
||||
import { GraphQLList } from 'graphql';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface PendingChannelListProps {
|
||||
pending_channels: PendingChannelProps[];
|
||||
}
|
||||
|
||||
interface PendingChannelProps {
|
||||
close_transaction_id: string;
|
||||
is_active: boolean;
|
||||
is_closing: boolean;
|
||||
is_opening: boolean;
|
||||
local_balance: number;
|
||||
local_reserve: number;
|
||||
partner_public_key: string;
|
||||
received: number;
|
||||
remote_balance: number;
|
||||
remote_reserve: number;
|
||||
sent: number;
|
||||
transaction_fee: number;
|
||||
transaction_id: string;
|
||||
transaction_vout: number;
|
||||
}
|
||||
|
||||
export const getPendingChannels = {
|
||||
type: new GraphQLList(PendingChannelType),
|
||||
args: defaultParams,
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'pendingChannels');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const pendingChannels: PendingChannelListProps = await getLnPendingChannels(
|
||||
{
|
||||
lnd,
|
||||
},
|
||||
);
|
||||
|
||||
const channels = pendingChannels.pending_channels.map(
|
||||
async channel => {
|
||||
const nodeInfo = await getNode({
|
||||
lnd,
|
||||
is_omitting_channels: true,
|
||||
public_key: channel.partner_public_key,
|
||||
});
|
||||
|
||||
return {
|
||||
...channel,
|
||||
partner_node_info: {
|
||||
...nodeInfo,
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
return channels;
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting pending channels: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
37
src/schemas/query/data/bitcoinFee.ts
Normal file
37
src/schemas/query/data/bitcoinFee.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { BitcoinFeeType } from '../../../schemaTypes/query/data/bitcoinFee';
|
||||
import { GraphQLBoolean } from 'graphql';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
const url = 'https://bitcoinfees.earn.com/api/v1/fees/recommended';
|
||||
|
||||
export const getBitcoinFees = {
|
||||
type: BitcoinFeeType,
|
||||
args: {
|
||||
logger: { type: GraphQLBoolean },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'bitcoinFee');
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const json = await response.json();
|
||||
|
||||
if (json) {
|
||||
const { fastestFee, halfHourFee, hourFee } = json;
|
||||
return {
|
||||
fast: fastestFee,
|
||||
halfHour: halfHourFee,
|
||||
hour: hourFee,
|
||||
};
|
||||
} else {
|
||||
throw new Error('Problem getting Bitcoin fees.');
|
||||
}
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting bitcoin fees: %o', error);
|
||||
throw new Error('Problem getting Bitcoin fees.');
|
||||
}
|
||||
},
|
||||
};
|
30
src/schemas/query/data/bitcoinPrice.ts
Normal file
30
src/schemas/query/data/bitcoinPrice.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLString, GraphQLBoolean } from 'graphql';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
const url = 'https://blockchain.info/ticker';
|
||||
|
||||
export const getBitcoinPrice = {
|
||||
type: GraphQLString,
|
||||
args: {
|
||||
logger: { type: GraphQLBoolean },
|
||||
currency: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'bitcoinPrice');
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const json = await response.json();
|
||||
|
||||
return JSON.stringify(json);
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting bitcoin price: %o', error);
|
||||
throw new Error('Problem getting Bitcoin price.');
|
||||
}
|
||||
},
|
||||
};
|
7
src/schemas/query/data/index.ts
Normal file
7
src/schemas/query/data/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { getBitcoinPrice } from './bitcoinPrice';
|
||||
import { getBitcoinFees } from './bitcoinFee';
|
||||
|
||||
export const dataQueries = {
|
||||
getBitcoinPrice,
|
||||
getBitcoinFees,
|
||||
};
|
28
src/schemas/query/flow/getInOut.interface.ts
Normal file
28
src/schemas/query/flow/getInOut.interface.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
export interface InOutProps {
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
export interface InOutListProps {
|
||||
[key: string]: InOutProps[];
|
||||
}
|
||||
|
||||
export interface PaymentProps {
|
||||
created_at: string;
|
||||
is_confirmed: boolean;
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
export interface PaymentsProps {
|
||||
payments: PaymentProps[];
|
||||
}
|
||||
|
||||
export interface InvoiceProps {
|
||||
created_at: string;
|
||||
is_confirmed: boolean;
|
||||
received: number;
|
||||
}
|
||||
|
||||
export interface InvoicesProps {
|
||||
invoices: InvoiceProps[];
|
||||
next: string;
|
||||
}
|
99
src/schemas/query/flow/getInOut.ts
Normal file
99
src/schemas/query/flow/getInOut.ts
Normal file
|
@ -0,0 +1,99 @@
|
|||
import { GraphQLString } from 'graphql';
|
||||
import {
|
||||
getInvoices as getLnInvoices,
|
||||
getPayments as getLnPayments,
|
||||
} from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { differenceInHours, differenceInCalendarDays } from 'date-fns';
|
||||
import { groupBy } from 'underscore';
|
||||
import { reduceInOutArray } from '../report/Helpers';
|
||||
import { InOutType } from '../../../schemaTypes/query/flow/InOut';
|
||||
import { InvoicesProps, PaymentsProps } from './getInOut.interface';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
export const getInOut = {
|
||||
type: InOutType,
|
||||
args: {
|
||||
...defaultParams,
|
||||
time: { type: GraphQLString },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'getInOut');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
const endDate = new Date();
|
||||
let periods = 7;
|
||||
let differenceFn = differenceInCalendarDays;
|
||||
|
||||
if (params.time === 'month') {
|
||||
periods = 30;
|
||||
} else if (params.time === 'day') {
|
||||
periods = 24;
|
||||
differenceFn = differenceInHours;
|
||||
}
|
||||
|
||||
let invoiceList: InvoicesProps;
|
||||
let paymentList: PaymentsProps;
|
||||
|
||||
try {
|
||||
invoiceList = await getLnInvoices({
|
||||
lnd,
|
||||
});
|
||||
paymentList = await getLnPayments({
|
||||
lnd,
|
||||
});
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error getting invoices: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
|
||||
const invoices = invoiceList.invoices.map(invoice => ({
|
||||
createdAt: invoice.created_at,
|
||||
isConfirmed: invoice.is_confirmed,
|
||||
tokens: invoice.received,
|
||||
}));
|
||||
|
||||
const payments = paymentList.payments.map(payment => ({
|
||||
createdAt: payment.created_at,
|
||||
isConfirmed: payment.is_confirmed,
|
||||
tokens: payment.tokens,
|
||||
}));
|
||||
|
||||
const confirmedInvoices = invoices.filter(invoice => {
|
||||
const dif = differenceFn(endDate, new Date(invoice.createdAt));
|
||||
return invoice.isConfirmed && dif < periods;
|
||||
});
|
||||
const confirmedPayments = payments.filter(payment => {
|
||||
const dif = differenceFn(endDate, new Date(payment.createdAt));
|
||||
return payment.isConfirmed && dif < periods;
|
||||
});
|
||||
|
||||
const allInvoices = invoices.filter(invoice => {
|
||||
const dif = differenceFn(endDate, new Date(invoice.createdAt));
|
||||
return dif < periods;
|
||||
});
|
||||
|
||||
const totalConfirmed = confirmedInvoices.length;
|
||||
const totalUnConfirmed = allInvoices.length - totalConfirmed;
|
||||
|
||||
const orderedInvoices = groupBy(confirmedInvoices, invoice => {
|
||||
return periods - differenceFn(endDate, new Date(invoice.createdAt));
|
||||
});
|
||||
const orderedPayments = groupBy(confirmedPayments, payment => {
|
||||
return periods - differenceFn(endDate, new Date(payment.createdAt));
|
||||
});
|
||||
|
||||
const reducedInvoices = reduceInOutArray(orderedInvoices);
|
||||
const reducedPayments = reduceInOutArray(orderedPayments);
|
||||
|
||||
return {
|
||||
invoices: JSON.stringify(reducedInvoices),
|
||||
payments: JSON.stringify(reducedPayments),
|
||||
confirmedInvoices: totalConfirmed,
|
||||
unConfirmedInvoices: totalUnConfirmed,
|
||||
};
|
||||
},
|
||||
};
|
5
src/schemas/query/flow/index.ts
Normal file
5
src/schemas/query/flow/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { getInOut } from './getInOut';
|
||||
|
||||
export const flowQueries = {
|
||||
getInOut,
|
||||
};
|
29
src/schemas/query/general/adminCheck.ts
Normal file
29
src/schemas/query/general/adminCheck.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { pay as payRequest } from 'ln-service';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLBoolean } from 'graphql';
|
||||
import { getAuthLnd, getErrorDetails } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
export const adminCheck = {
|
||||
type: GraphQLBoolean,
|
||||
args: {
|
||||
...defaultParams,
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'adminCheck');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
await payRequest({
|
||||
lnd,
|
||||
request: 'admin check',
|
||||
});
|
||||
} catch (error) {
|
||||
const details = getErrorDetails(error);
|
||||
if (details.includes('invalid character in string')) return true;
|
||||
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
};
|
59
src/schemas/query/general/chainBalance.ts
Normal file
59
src/schemas/query/general/chainBalance.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import {
|
||||
getChainBalance as getBalance,
|
||||
getPendingChainBalance as getPending,
|
||||
} from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GraphQLInt } from 'graphql';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface ChainBalanceProps {
|
||||
chain_balance: number;
|
||||
}
|
||||
|
||||
interface PendingChainBalanceProps {
|
||||
pending_chain_balance: number;
|
||||
}
|
||||
|
||||
export const getChainBalance = {
|
||||
type: GraphQLInt,
|
||||
args: defaultParams,
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'chainBalance');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const value: ChainBalanceProps = await getBalance({
|
||||
lnd,
|
||||
});
|
||||
return value.chain_balance;
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting chain balance: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const getPendingChainBalance = {
|
||||
type: GraphQLInt,
|
||||
args: defaultParams,
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'pendingChainBalance');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const pendingValue: PendingChainBalanceProps = await getPending({
|
||||
lnd,
|
||||
});
|
||||
return pendingValue.pending_chain_balance;
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting pending chain balance: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
12
src/schemas/query/general/index.ts
Normal file
12
src/schemas/query/general/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { getChainBalance, getPendingChainBalance } from './chainBalance';
|
||||
import { getNetworkInfo } from './networkInfo';
|
||||
import { getNodeInfo } from './nodeInfo';
|
||||
import { adminCheck } from './adminCheck';
|
||||
|
||||
export const generalQueries = {
|
||||
getChainBalance,
|
||||
getPendingChainBalance,
|
||||
getNetworkInfo,
|
||||
getNodeInfo,
|
||||
adminCheck,
|
||||
};
|
48
src/schemas/query/general/networkInfo.ts
Normal file
48
src/schemas/query/general/networkInfo.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { getNetworkInfo as getLnNetworkInfo } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { NetworkInfoType } from '../../../schemaTypes/query/info/networkInfo';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface NetworkInfoProps {
|
||||
average_channel_size: number;
|
||||
channel_count: number;
|
||||
max_channel_size: number;
|
||||
median_channel_size: number;
|
||||
min_channel_size: number;
|
||||
node_count: number;
|
||||
not_recently_updated_policy_count: number;
|
||||
total_capacity: number;
|
||||
}
|
||||
|
||||
export const getNetworkInfo = {
|
||||
type: NetworkInfoType,
|
||||
args: defaultParams,
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'networkInfo');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const info: NetworkInfoProps = await getLnNetworkInfo({ lnd });
|
||||
|
||||
return {
|
||||
averageChannelSize: info.average_channel_size,
|
||||
channelCount: info.channel_count,
|
||||
maxChannelSize: info.max_channel_size,
|
||||
medianChannelSize: info.median_channel_size,
|
||||
minChannelSize: info.min_channel_size,
|
||||
nodeCount: info.node_count,
|
||||
notRecentlyUpdatedPolicyCount:
|
||||
info.not_recently_updated_policy_count,
|
||||
totalCapacity: info.total_capacity,
|
||||
};
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting network info: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
49
src/schemas/query/general/nodeInfo.ts
Normal file
49
src/schemas/query/general/nodeInfo.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { getWalletInfo, getClosedChannels } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { NodeInfoType } from '../../../schemaTypes/query/info/nodeInfo';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface NodeInfoProps {
|
||||
chains: string[];
|
||||
color: string;
|
||||
active_channels_count: number;
|
||||
alias: string;
|
||||
current_block_hash: string;
|
||||
current_block_height: number;
|
||||
is_synced_to_chain: boolean;
|
||||
is_synced_to_graph: boolean;
|
||||
latest_block_at: string;
|
||||
peers_count: number;
|
||||
pending_channels_count: number;
|
||||
public_key: string;
|
||||
uris: string[];
|
||||
version: string;
|
||||
}
|
||||
|
||||
export const getNodeInfo = {
|
||||
type: NodeInfoType,
|
||||
args: defaultParams,
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'nodeInfo');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const info: NodeInfoProps = await getWalletInfo({
|
||||
lnd,
|
||||
});
|
||||
const closedChannels: { channels: [] } = await getClosedChannels({
|
||||
lnd,
|
||||
});
|
||||
return {
|
||||
...info,
|
||||
closed_channels_count: closedChannels.channels.length,
|
||||
};
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error getting node info: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
19
src/schemas/query/index.ts
Normal file
19
src/schemas/query/index.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { channelQueries } from './channels';
|
||||
import { generalQueries } from './general';
|
||||
import { invoiceQueries } from './transactions';
|
||||
import { dataQueries } from './data';
|
||||
import { reportQueries } from './report';
|
||||
import { flowQueries } from './flow';
|
||||
import { backupQueries } from './backup';
|
||||
import { routeQueries } from './route';
|
||||
|
||||
export const query = {
|
||||
...channelQueries,
|
||||
...generalQueries,
|
||||
...invoiceQueries,
|
||||
...dataQueries,
|
||||
...reportQueries,
|
||||
...flowQueries,
|
||||
...backupQueries,
|
||||
...routeQueries,
|
||||
};
|
167
src/schemas/query/report/ForwardChannels.ts
Normal file
167
src/schemas/query/report/ForwardChannels.ts
Normal file
|
@ -0,0 +1,167 @@
|
|||
import { GraphQLString } from 'graphql';
|
||||
import {
|
||||
getForwards as getLnForwards,
|
||||
getNode,
|
||||
getChannel,
|
||||
getWalletInfo,
|
||||
} from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { subHours, subDays } from 'date-fns';
|
||||
import { countArray, countRoutes } from './Helpers';
|
||||
import { ForwardCompleteProps } from './ForwardReport.interface';
|
||||
import { sortBy } from 'underscore';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface NodeProps {
|
||||
alias: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
interface ChannelsProps {
|
||||
policies: { public_key: string }[];
|
||||
}
|
||||
|
||||
export const getForwardChannelsReport = {
|
||||
type: GraphQLString,
|
||||
args: {
|
||||
...defaultParams,
|
||||
time: { type: GraphQLString },
|
||||
order: { type: GraphQLString },
|
||||
type: { type: GraphQLString },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'forwardChannels');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
let startDate = new Date();
|
||||
const endDate = new Date();
|
||||
|
||||
if (params.time === 'month') {
|
||||
startDate = subDays(endDate, 30);
|
||||
} else if (params.time === 'week') {
|
||||
startDate = subDays(endDate, 7);
|
||||
} else {
|
||||
startDate = subHours(endDate, 24);
|
||||
}
|
||||
|
||||
const getNodeAlias = async (id: string, publicKey: string) => {
|
||||
const channelInfo: ChannelsProps = await getChannel({
|
||||
lnd,
|
||||
id,
|
||||
});
|
||||
|
||||
const partnerPublicKey =
|
||||
channelInfo.policies[0].public_key !== publicKey
|
||||
? channelInfo.policies[0].public_key
|
||||
: channelInfo.policies[1].public_key;
|
||||
|
||||
const nodeInfo: NodeProps = await getNode({
|
||||
lnd,
|
||||
is_omitting_channels: true,
|
||||
public_key: partnerPublicKey,
|
||||
});
|
||||
|
||||
return {
|
||||
alias: nodeInfo.alias,
|
||||
color: nodeInfo.color,
|
||||
};
|
||||
};
|
||||
|
||||
const getRouteAlias = (array: any[], publicKey: string) =>
|
||||
Promise.all(
|
||||
array.map(async channel => {
|
||||
const nodeAliasIn = await getNodeAlias(
|
||||
channel.in,
|
||||
publicKey,
|
||||
);
|
||||
const nodeAliasOut = await getNodeAlias(
|
||||
channel.out,
|
||||
publicKey,
|
||||
);
|
||||
|
||||
return {
|
||||
aliasIn: nodeAliasIn.alias,
|
||||
colorIn: nodeAliasIn.color,
|
||||
aliasOut: nodeAliasOut.alias,
|
||||
colorOut: nodeAliasOut.color,
|
||||
...channel,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const getAlias = (array: any[], publicKey: string) =>
|
||||
Promise.all(
|
||||
array.map(async channel => {
|
||||
const nodeAlias = await getNodeAlias(
|
||||
channel.name,
|
||||
publicKey,
|
||||
);
|
||||
return {
|
||||
alias: nodeAlias.alias,
|
||||
color: nodeAlias.color,
|
||||
...channel,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
try {
|
||||
const forwardsList: ForwardCompleteProps = await getLnForwards({
|
||||
lnd,
|
||||
after: startDate,
|
||||
before: endDate,
|
||||
limit: 10000,
|
||||
});
|
||||
|
||||
const walletInfo: { public_key: string } = await getWalletInfo({
|
||||
lnd,
|
||||
});
|
||||
|
||||
if (params.type === 'route') {
|
||||
const mapped = forwardsList.forwards.map(forward => {
|
||||
return {
|
||||
route: `${forward.incoming_channel} - ${forward.outgoing_channel}`,
|
||||
...forward,
|
||||
};
|
||||
});
|
||||
const grouped = countRoutes(mapped);
|
||||
|
||||
const routeAlias = await getRouteAlias(
|
||||
grouped,
|
||||
walletInfo.public_key,
|
||||
);
|
||||
|
||||
const sortedRoute = sortBy(routeAlias, params.order)
|
||||
.reverse()
|
||||
.slice(0, 10);
|
||||
return JSON.stringify(sortedRoute);
|
||||
} else if (params.type === 'incoming') {
|
||||
const incomingCount = countArray(forwardsList.forwards, true);
|
||||
const incomingAlias = await getAlias(
|
||||
incomingCount,
|
||||
walletInfo.public_key,
|
||||
);
|
||||
const sortedInCount = sortBy(incomingAlias, params.order)
|
||||
.reverse()
|
||||
.slice(0, 10);
|
||||
return JSON.stringify(sortedInCount);
|
||||
} else {
|
||||
const outgoingCount = countArray(forwardsList.forwards, false);
|
||||
const outgoingAlias = await getAlias(
|
||||
outgoingCount,
|
||||
walletInfo.public_key,
|
||||
);
|
||||
const sortedOutCount = sortBy(outgoingAlias, params.order)
|
||||
.reverse()
|
||||
.slice(0, 10);
|
||||
return JSON.stringify(sortedOutCount);
|
||||
}
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting forward channel report: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
42
src/schemas/query/report/ForwardReport.interface.ts
Normal file
42
src/schemas/query/report/ForwardReport.interface.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
export interface ForwardProps {
|
||||
created_at: string;
|
||||
fee: number;
|
||||
fee_mtokens: string;
|
||||
incoming_channel: string;
|
||||
mtokens: string;
|
||||
outgoing_channel: string;
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
export interface ForwardCompleteProps {
|
||||
forwards: ForwardProps[];
|
||||
next: string;
|
||||
}
|
||||
|
||||
export interface ListProps {
|
||||
[key: string]: ForwardProps[];
|
||||
}
|
||||
|
||||
export interface ReduceObjectProps {
|
||||
fee: number;
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
export interface FinalProps {
|
||||
fee: number;
|
||||
tokens: number;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface FinalList {
|
||||
[key: string]: FinalProps;
|
||||
}
|
||||
|
||||
export interface CountProps {
|
||||
[key: string]: number;
|
||||
}
|
||||
|
||||
export interface ChannelCounts {
|
||||
name: string;
|
||||
count: number;
|
||||
}
|
80
src/schemas/query/report/ForwardReport.ts
Normal file
80
src/schemas/query/report/ForwardReport.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
import { GraphQLString } from 'graphql';
|
||||
import { getForwards as getLnForwards } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { groupBy } from 'underscore';
|
||||
import {
|
||||
subHours,
|
||||
subDays,
|
||||
differenceInHours,
|
||||
differenceInCalendarDays,
|
||||
} from 'date-fns';
|
||||
import { reduceForwardArray } from './Helpers';
|
||||
import { ForwardCompleteProps } from './ForwardReport.interface';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
export const getForwardReport = {
|
||||
type: GraphQLString,
|
||||
args: {
|
||||
...defaultParams,
|
||||
time: { type: GraphQLString },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'forwardReport');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
let startDate = new Date();
|
||||
const endDate = new Date();
|
||||
let days = 7;
|
||||
|
||||
if (params.time === 'month') {
|
||||
startDate = subDays(endDate, 30);
|
||||
days = 30;
|
||||
} else if (params.time === 'week') {
|
||||
startDate = subDays(endDate, 7);
|
||||
} else {
|
||||
startDate = subHours(endDate, 24);
|
||||
}
|
||||
|
||||
try {
|
||||
const forwardsList: ForwardCompleteProps = await getLnForwards({
|
||||
lnd,
|
||||
after: startDate,
|
||||
before: endDate,
|
||||
});
|
||||
|
||||
if (params.time === 'month' || params.time === 'week') {
|
||||
const orderedDay = groupBy(forwardsList.forwards, item => {
|
||||
return (
|
||||
days -
|
||||
differenceInCalendarDays(
|
||||
endDate,
|
||||
new Date(item.created_at),
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const reducedOrderedDay = reduceForwardArray(orderedDay);
|
||||
|
||||
return JSON.stringify(reducedOrderedDay);
|
||||
} else {
|
||||
const orderedHour = groupBy(forwardsList.forwards, item => {
|
||||
return (
|
||||
24 -
|
||||
differenceInHours(endDate, new Date(item.created_at))
|
||||
);
|
||||
});
|
||||
|
||||
const reducedOrderedHour = reduceForwardArray(orderedHour);
|
||||
|
||||
return JSON.stringify(reducedOrderedHour);
|
||||
}
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting forward report: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
106
src/schemas/query/report/Helpers.ts
Normal file
106
src/schemas/query/report/Helpers.ts
Normal file
|
@ -0,0 +1,106 @@
|
|||
import { reduce, groupBy } from 'underscore';
|
||||
import {
|
||||
ForwardProps,
|
||||
ReduceObjectProps,
|
||||
ListProps,
|
||||
} from './ForwardReport.interface';
|
||||
import { InOutListProps, InOutProps } from '../flow/getInOut.interface';
|
||||
|
||||
export const reduceForwardArray = (list: ListProps) => {
|
||||
const reducedOrder = [];
|
||||
for (const key in list) {
|
||||
if (list.hasOwnProperty(key)) {
|
||||
const element: ForwardProps[] = list[key];
|
||||
const reducedArray: ReduceObjectProps = reduce(element, (a, b) => {
|
||||
return {
|
||||
fee: a.fee + b.fee,
|
||||
tokens: a.tokens + b.tokens,
|
||||
};
|
||||
});
|
||||
reducedOrder.push({
|
||||
period: parseInt(key),
|
||||
amount: element.length,
|
||||
...reducedArray,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return reducedOrder;
|
||||
};
|
||||
|
||||
export const reduceInOutArray = (list: InOutListProps) => {
|
||||
const reducedOrder = [];
|
||||
for (const key in list) {
|
||||
if (list.hasOwnProperty(key)) {
|
||||
const element: InOutProps[] = list[key];
|
||||
const reducedArray: InOutProps = reduce(element, (a, b) => ({
|
||||
tokens: a.tokens + b.tokens,
|
||||
}));
|
||||
reducedOrder.push({
|
||||
period: parseInt(key),
|
||||
amount: element.length,
|
||||
tokens: reducedArray.tokens,
|
||||
});
|
||||
}
|
||||
}
|
||||
return reducedOrder;
|
||||
};
|
||||
|
||||
export const countArray = (list: ForwardProps[], type: boolean) => {
|
||||
const inOrOut = type ? 'incoming_channel' : 'outgoing_channel';
|
||||
const grouped = groupBy(list, inOrOut);
|
||||
|
||||
const channelInfo = [];
|
||||
for (const key in grouped) {
|
||||
if (grouped.hasOwnProperty(key)) {
|
||||
const element = grouped[key];
|
||||
|
||||
const fee = element
|
||||
.map(forward => forward.fee)
|
||||
.reduce((p, c) => p + c);
|
||||
|
||||
const tokens = element
|
||||
.map(forward => forward.tokens)
|
||||
.reduce((p, c) => p + c);
|
||||
|
||||
channelInfo.push({
|
||||
name: key,
|
||||
amount: element.length,
|
||||
fee: fee,
|
||||
tokens: tokens,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return channelInfo;
|
||||
};
|
||||
|
||||
export const countRoutes = (list: ForwardProps[]) => {
|
||||
const grouped = groupBy(list, 'route');
|
||||
|
||||
const channelInfo = [];
|
||||
for (const key in grouped) {
|
||||
if (grouped.hasOwnProperty(key)) {
|
||||
const element = grouped[key];
|
||||
|
||||
const fee = element
|
||||
.map(forward => forward.fee)
|
||||
.reduce((p, c) => p + c);
|
||||
|
||||
const tokens = element
|
||||
.map(forward => forward.tokens)
|
||||
.reduce((p, c) => p + c);
|
||||
|
||||
channelInfo.push({
|
||||
route: key,
|
||||
in: element[0].incoming_channel,
|
||||
out: element[0].outgoing_channel,
|
||||
amount: element.length,
|
||||
fee: fee,
|
||||
tokens: tokens,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return channelInfo;
|
||||
};
|
7
src/schemas/query/report/index.ts
Normal file
7
src/schemas/query/report/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { getForwardReport } from "./ForwardReport";
|
||||
import { getForwardChannelsReport } from "./ForwardChannels";
|
||||
|
||||
export const reportQueries = {
|
||||
getForwardReport,
|
||||
getForwardChannelsReport
|
||||
};
|
42
src/schemas/query/route/getRoutes.ts
Normal file
42
src/schemas/query/route/getRoutes.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { GraphQLNonNull, GraphQLString, GraphQLInt } from 'graphql';
|
||||
import { getRouteToDestination, getWalletInfo } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
export const getRoutes = {
|
||||
type: GraphQLString,
|
||||
args: {
|
||||
...defaultParams,
|
||||
outgoing: { type: new GraphQLNonNull(GraphQLString) },
|
||||
incoming: { type: new GraphQLNonNull(GraphQLString) },
|
||||
tokens: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
maxFee: { type: GraphQLInt },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'getRoutes');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
const { public_key } = await getWalletInfo({ lnd });
|
||||
|
||||
const { route } = await getRouteToDestination({
|
||||
lnd,
|
||||
outgoing_channel: params.outgoing,
|
||||
incoming_peer: params.incoming,
|
||||
destination: public_key,
|
||||
tokens: params.tokens,
|
||||
...(params.maxFee && { max_fee: params.maxFee }),
|
||||
}).catch((error: any) => {
|
||||
params.logger && logger.error('Error getting routes: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
});
|
||||
|
||||
if (!route) {
|
||||
throw new Error('No route found.');
|
||||
}
|
||||
|
||||
return JSON.stringify(route);
|
||||
},
|
||||
};
|
5
src/schemas/query/route/index.ts
Normal file
5
src/schemas/query/route/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { getRoutes } from './getRoutes';
|
||||
|
||||
export const routeQueries = {
|
||||
getRoutes,
|
||||
};
|
51
src/schemas/query/transactions/chainTransactions.ts
Normal file
51
src/schemas/query/transactions/chainTransactions.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { GraphQLList } from 'graphql';
|
||||
import { getChainTransactions as getLnChainTransactions } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { getErrorMsg, getAuthLnd } from '../../../helpers/helpers';
|
||||
import { GetChainTransactionsType } from '../../../schemaTypes/query/transactions/chainTransactions';
|
||||
import { sortBy } from 'underscore';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface TransactionProps {
|
||||
block_id: string;
|
||||
confirmation_count: number;
|
||||
confirmation_height: number;
|
||||
created_at: string;
|
||||
fee: number;
|
||||
id: string;
|
||||
output_addresses: string[];
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
interface TransactionsProps {
|
||||
transactions: TransactionProps[];
|
||||
}
|
||||
|
||||
export const getChainTransactions = {
|
||||
type: new GraphQLList(GetChainTransactionsType),
|
||||
args: defaultParams,
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'chainTransactions');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
try {
|
||||
const transactionList: TransactionsProps = await getLnChainTransactions(
|
||||
{
|
||||
lnd,
|
||||
},
|
||||
);
|
||||
|
||||
const transactions = sortBy(
|
||||
transactionList.transactions,
|
||||
'created_at',
|
||||
).reverse();
|
||||
return transactions;
|
||||
} catch (error) {
|
||||
params.logger &&
|
||||
logger.error('Error getting chain transactions: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
124
src/schemas/query/transactions/forwards.ts
Normal file
124
src/schemas/query/transactions/forwards.ts
Normal file
|
@ -0,0 +1,124 @@
|
|||
import { GraphQLString } from 'graphql';
|
||||
import {
|
||||
getForwards as getLnForwards,
|
||||
getChannel,
|
||||
getNode,
|
||||
getWalletInfo,
|
||||
} from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { GetForwardType } from '../../../schemaTypes/query/transactions/forwards';
|
||||
import { getErrorMsg, getAuthLnd } from '../../../helpers/helpers';
|
||||
import { sortBy } from 'underscore';
|
||||
import { ForwardCompleteProps } from '../report/ForwardReport.interface';
|
||||
import { subHours, subDays, subMonths, subYears } from 'date-fns';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
interface NodeProps {
|
||||
alias: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
interface ChannelsProps {
|
||||
policies: { public_key: string }[];
|
||||
}
|
||||
|
||||
export const getForwards = {
|
||||
type: GetForwardType,
|
||||
args: {
|
||||
...defaultParams,
|
||||
time: { type: GraphQLString },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'forwards');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
let startDate = new Date();
|
||||
const endDate = new Date();
|
||||
|
||||
if (params.time === 'oneYear') {
|
||||
startDate = subYears(endDate, 1);
|
||||
} else if (params.time === 'sixMonths') {
|
||||
startDate = subMonths(endDate, 6);
|
||||
} else if (params.time === 'threeMonths') {
|
||||
startDate = subMonths(endDate, 3);
|
||||
} else if (params.time === 'month') {
|
||||
startDate = subMonths(endDate, 1);
|
||||
} else if (params.time === 'week') {
|
||||
startDate = subDays(endDate, 7);
|
||||
} else {
|
||||
startDate = subHours(endDate, 24);
|
||||
}
|
||||
|
||||
const walletInfo: { public_key: string } = await getWalletInfo({
|
||||
lnd,
|
||||
});
|
||||
|
||||
const getNodeAlias = async (id: string, publicKey: string) => {
|
||||
const channelInfo: ChannelsProps = await getChannel({
|
||||
lnd,
|
||||
id,
|
||||
});
|
||||
|
||||
const partnerPublicKey =
|
||||
channelInfo.policies[0].public_key !== publicKey
|
||||
? channelInfo.policies[0].public_key
|
||||
: channelInfo.policies[1].public_key;
|
||||
|
||||
const nodeInfo: NodeProps = await getNode({
|
||||
lnd,
|
||||
is_omitting_channels: true,
|
||||
public_key: partnerPublicKey,
|
||||
});
|
||||
|
||||
return {
|
||||
alias: nodeInfo.alias,
|
||||
color: nodeInfo.color,
|
||||
};
|
||||
};
|
||||
|
||||
const getAlias = (array: any[], publicKey: string) =>
|
||||
Promise.all(
|
||||
array.map(async forward => {
|
||||
const inNodeAlias = await getNodeAlias(
|
||||
forward.incoming_channel,
|
||||
publicKey,
|
||||
);
|
||||
const outNodeAlias = await getNodeAlias(
|
||||
forward.outgoing_channel,
|
||||
publicKey,
|
||||
);
|
||||
return {
|
||||
incoming_alias: inNodeAlias.alias,
|
||||
incoming_color: inNodeAlias.color,
|
||||
outgoing_alias: outNodeAlias.alias,
|
||||
outgoing_color: outNodeAlias.color,
|
||||
...forward,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
try {
|
||||
const forwardsList: ForwardCompleteProps = await getLnForwards({
|
||||
lnd,
|
||||
after: startDate,
|
||||
before: endDate,
|
||||
});
|
||||
|
||||
const list = await getAlias(
|
||||
forwardsList.forwards,
|
||||
walletInfo.public_key,
|
||||
);
|
||||
|
||||
const forwards = sortBy(list, 'created_at').reverse();
|
||||
return {
|
||||
token: forwardsList.next,
|
||||
forwards,
|
||||
};
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error getting forwards: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
},
|
||||
};
|
9
src/schemas/query/transactions/index.ts
Normal file
9
src/schemas/query/transactions/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { getForwards } from './Forwards';
|
||||
import { getResume } from './resume';
|
||||
import { getChainTransactions } from './ChainTransactions';
|
||||
|
||||
export const invoiceQueries = {
|
||||
getResume,
|
||||
getForwards,
|
||||
getChainTransactions,
|
||||
};
|
61
src/schemas/query/transactions/resume.interface.ts
Normal file
61
src/schemas/query/transactions/resume.interface.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
export interface PaymentProps {
|
||||
created_at: string;
|
||||
destination: string;
|
||||
fee: number;
|
||||
fee_mtokens: string;
|
||||
hops: string[];
|
||||
id: string;
|
||||
is_confirmed: boolean;
|
||||
is_outgoing: boolean;
|
||||
mtokens: string;
|
||||
request: string;
|
||||
secret: string;
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
export interface PaymentsProps {
|
||||
payments: PaymentProps[];
|
||||
}
|
||||
|
||||
export interface InvoicePaymentProps {
|
||||
confirmed_at: string;
|
||||
created_at: string;
|
||||
created_height: number;
|
||||
in_channel: string;
|
||||
is_canceled: boolean;
|
||||
is_confirmed: boolean;
|
||||
is_held: boolean;
|
||||
mtokens: string;
|
||||
pending_index: number;
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
export interface InvoiceProps {
|
||||
chain_address: string;
|
||||
confirmed_at: string;
|
||||
created_at: string;
|
||||
description: string;
|
||||
description_hash: string;
|
||||
expires_at: string;
|
||||
id: string;
|
||||
is_canceled: boolean;
|
||||
is_confirmed: boolean;
|
||||
is_held: boolean;
|
||||
is_outgoing: boolean;
|
||||
is_private: boolean;
|
||||
payments: InvoicePaymentProps[];
|
||||
received: number;
|
||||
received_mtokens: string;
|
||||
request: string;
|
||||
secret: string;
|
||||
tokens: number;
|
||||
}
|
||||
|
||||
export interface InvoicesProps {
|
||||
invoices: InvoiceProps[];
|
||||
next: string;
|
||||
}
|
||||
|
||||
export interface NodeProps {
|
||||
alias: string;
|
||||
}
|
121
src/schemas/query/transactions/resume.ts
Normal file
121
src/schemas/query/transactions/resume.ts
Normal file
|
@ -0,0 +1,121 @@
|
|||
import { GraphQLString } from 'graphql';
|
||||
import { getPayments, getInvoices, getNode } from 'ln-service';
|
||||
import { logger } from '../../../helpers/logger';
|
||||
import { requestLimiter } from '../../../helpers/rateLimiter';
|
||||
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
|
||||
import { PaymentsProps, InvoicesProps, NodeProps } from './Resume.interface';
|
||||
import { compareDesc } from 'date-fns';
|
||||
import { sortBy } from 'underscore';
|
||||
import { GetResumeType } from '../../../schemaTypes/query/transactions/resume';
|
||||
import { defaultParams } from '../../../helpers/defaultProps';
|
||||
|
||||
export const getResume = {
|
||||
type: GetResumeType,
|
||||
args: {
|
||||
...defaultParams,
|
||||
token: { type: GraphQLString },
|
||||
},
|
||||
resolve: async (root: any, params: any, context: any) => {
|
||||
await requestLimiter(context.ip, 'payments');
|
||||
|
||||
const lnd = getAuthLnd(params.auth);
|
||||
|
||||
let payments;
|
||||
let invoices;
|
||||
|
||||
try {
|
||||
const paymentList: PaymentsProps = await getPayments({
|
||||
lnd,
|
||||
});
|
||||
|
||||
const getMappedPayments = () =>
|
||||
Promise.all(
|
||||
paymentList.payments.map(async payment => {
|
||||
let nodeInfo: NodeProps;
|
||||
try {
|
||||
nodeInfo = await getNode({
|
||||
lnd,
|
||||
is_omitting_channels: true,
|
||||
public_key: payment.destination,
|
||||
});
|
||||
} catch (error) {
|
||||
nodeInfo = { alias: 'unknown' };
|
||||
}
|
||||
return {
|
||||
type: 'payment',
|
||||
alias: nodeInfo.alias,
|
||||
date: payment.created_at,
|
||||
...payment,
|
||||
};
|
||||
}),
|
||||
);
|
||||
payments = await getMappedPayments();
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error getting payments: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
|
||||
const invoiceProps = params.token
|
||||
? { token: params.token }
|
||||
: { limit: 25 };
|
||||
|
||||
let lastInvoiceDate = '';
|
||||
let firstInvoiceDate = '';
|
||||
let token = '';
|
||||
let withInvoices = true;
|
||||
|
||||
try {
|
||||
const invoiceList: InvoicesProps = await getInvoices({
|
||||
lnd,
|
||||
...invoiceProps,
|
||||
});
|
||||
|
||||
invoices = invoiceList.invoices.map(invoice => {
|
||||
return {
|
||||
type: 'invoice',
|
||||
date: invoice.confirmed_at || invoice.created_at,
|
||||
...invoice,
|
||||
};
|
||||
});
|
||||
|
||||
if (invoices.length <= 0) {
|
||||
withInvoices = false;
|
||||
} else {
|
||||
const { date } = invoices[invoices.length - 1];
|
||||
firstInvoiceDate = invoices[0].date;
|
||||
lastInvoiceDate = date;
|
||||
token = invoiceList.next;
|
||||
}
|
||||
} catch (error) {
|
||||
params.logger && logger.error('Error getting invoices: %o', error);
|
||||
throw new Error(getErrorMsg(error));
|
||||
}
|
||||
|
||||
const filteredPayments = withInvoices
|
||||
? payments.filter(payment => {
|
||||
const last =
|
||||
compareDesc(
|
||||
new Date(lastInvoiceDate),
|
||||
new Date(payment.date),
|
||||
) === 1;
|
||||
const first = params.token
|
||||
? compareDesc(
|
||||
new Date(payment.date),
|
||||
new Date(firstInvoiceDate),
|
||||
) === 1
|
||||
: true;
|
||||
return last && first;
|
||||
})
|
||||
: payments;
|
||||
|
||||
const resumeArray = sortBy(
|
||||
[...invoices, ...filteredPayments],
|
||||
'date',
|
||||
).reverse();
|
||||
|
||||
return {
|
||||
token,
|
||||
resume: JSON.stringify(resumeArray),
|
||||
};
|
||||
},
|
||||
};
|
44
src/utils/rateLimitConfig.ts
Normal file
44
src/utils/rateLimitConfig.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
interface RateConfigProps {
|
||||
[key: string]: {
|
||||
max: number;
|
||||
window: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const RateConfig: RateConfigProps = {
|
||||
channelBalance: { max: 3, window: '1s' },
|
||||
channelFees: { max: 3, window: '1s' },
|
||||
channels: { max: 3, window: '1s' },
|
||||
channelReport: { max: 3, window: '1s' },
|
||||
closedChannels: { max: 3, window: '1s' },
|
||||
pendingChannels: { max: 3, window: '1s' },
|
||||
bitcoinFee: { max: 3, window: '1s' },
|
||||
bitcoinPrice: { max: 3, window: '1s' },
|
||||
getInOut: { max: 3, window: '1s' },
|
||||
chainBalance: { max: 3, window: '1s' },
|
||||
pendingChainBalance: { max: 3, window: '1s' },
|
||||
networkInfo: { max: 3, window: '1s' },
|
||||
nodeInfo: { max: 3, window: '1s' },
|
||||
forwards: { max: 3, window: '1s' },
|
||||
invoices: { max: 3, window: '1s' },
|
||||
payments: { max: 3, window: '1s' },
|
||||
forwardChannels: { max: 3, window: '1s' },
|
||||
forwardReport: { max: 3, window: '1s' },
|
||||
getRoute: { max: 3, window: '1s' },
|
||||
closeChannel: { max: 3, window: '1s' },
|
||||
openChannel: { max: 3, window: '1s' },
|
||||
createInvoice: { max: 3, window: '1s' },
|
||||
decode: { max: 3, window: '1s' },
|
||||
parsePayment: { max: 3, window: '1s' },
|
||||
pay: { max: 3, window: '1s' },
|
||||
getAddress: { max: 3, window: '1s' },
|
||||
sendToAddress: { max: 3, window: '1s' },
|
||||
getBackups: { max: 3, window: '1s' },
|
||||
verifyBackups: { max: 3, window: '1s' },
|
||||
recoverFunds: { max: 3, window: '1s' },
|
||||
updateFees: { max: 3, window: '1s' },
|
||||
chainTransactions: { max: 3, window: '1s' },
|
||||
getRoutes: { max: 3, window: '1s' },
|
||||
payViaRoute: { max: 3, window: '1s' },
|
||||
adminCheck: { max: 3, window: '1s' },
|
||||
};
|
66
tsconfig.json
Normal file
66
tsconfig.json
Normal file
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
|
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es6"
|
||||
] /* Specify library files to be included in the compilation. */,
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "dist" /* Redirect output structure to the directory. */,
|
||||
"rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
"removeComments": true /* Do not emit comments to output. */,
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
}
|
||||
}
|
1
types/ln-service.d.ts
vendored
Normal file
1
types/ln-service.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
declare module "ln-service";
|
21
webpack.common.js
Normal file
21
webpack.common.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
exclude: [path.resolve(__dirname, "node_modules")],
|
||||
test: /\.ts$/,
|
||||
use: "ts-loader"
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: "server.js",
|
||||
path: path.resolve(__dirname, "dist")
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".js"]
|
||||
},
|
||||
target: "node"
|
||||
};
|
20
webpack.development.js
Normal file
20
webpack.development.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const merge = require("webpack-merge");
|
||||
const nodeExternals = require("webpack-node-externals");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
|
||||
const common = require("./webpack.common.js");
|
||||
|
||||
module.exports = merge.smart(common, {
|
||||
devtool: "inline-source-map",
|
||||
entry: ["webpack/hot/poll?1000", path.join(__dirname, "src/main.ts")],
|
||||
externals: [
|
||||
nodeExternals({
|
||||
whitelist: ["webpack/hot/poll?1000"]
|
||||
})
|
||||
],
|
||||
mode: "development",
|
||||
plugins: [new CleanWebpackPlugin(), new webpack.HotModuleReplacementPlugin()],
|
||||
watch: true
|
||||
});
|
14
webpack.production.js
Normal file
14
webpack.production.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const merge = require('webpack-merge');
|
||||
const nodeExternals = require('webpack-node-externals');
|
||||
const path = require('path');
|
||||
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
devtool: 'source-map',
|
||||
entry: [path.join(__dirname, 'src/main.ts')],
|
||||
externals: [nodeExternals({})],
|
||||
mode: 'production',
|
||||
plugins: [new CleanWebpackPlugin()],
|
||||
});
|
Loading…
Add table
Reference in a new issue