From fe62cadd6fb4a7e49f7d59401fc74ae0411e32a7 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 26 Aug 2024 21:17:43 -0700 Subject: typescript --- lib/api.js | 5 - lib/api.js.map | 1 + lib/api.ts | 5 + lib/app.js | 66 ----- lib/app.js.map | 1 + lib/app.ts | 66 +++++ lib/auth.js | 51 ---- lib/auth.js.map | 1 + lib/auth.ts | 51 ++++ lib/bitcoin.js | 3 - lib/bitcoin.js.map | 1 + lib/bitcoin.ts | 3 + lib/countries.js | 248 ----------------- lib/countries.js.map | 1 + lib/countries.ts | 248 +++++++++++++++++ lib/db.js | 142 ---------- lib/db.js.map | 1 + lib/db.ts | 142 ++++++++++ lib/ecash.js | 60 ----- lib/ecash.js.map | 1 + lib/ecash.ts | 60 +++++ lib/invoices.js | 128 --------- lib/invoices.js.map | 1 + lib/invoices.ts | 128 +++++++++ lib/lightning.js | 52 ---- lib/lightning.js.map | 1 + lib/lightning.ts | 52 ++++ lib/liquid.js | 3 - lib/liquid.js.map | 1 + lib/liquid.ts | 4 + lib/ln.js | 7 - lib/ln.js.map | 1 + lib/ln.ts | 7 + lib/locales/index.js | 5 - lib/locales/index.js.map | 1 + lib/locales/index.ts | 5 + lib/locations.js | 40 --- lib/locations.js.map | 1 + lib/locations.ts | 40 +++ lib/logging.js | 12 - lib/logging.js.map | 1 + lib/logging.ts | 12 + lib/mail.js | 44 --- lib/mail.js.map | 1 + lib/mail.ts | 44 +++ lib/migrate.js | 134 ---------- lib/migrate.js.map | 1 + lib/migrate.ts | 134 ++++++++++ lib/mqtt.js | 5 - lib/mqtt.js.map | 1 + lib/mqtt.ts | 5 + lib/nostr.js | 280 ------------------- lib/nostr.js.map | 1 + lib/nostr.ts | 280 +++++++++++++++++++ lib/notifications.js | 38 --- lib/notifications.js.map | 1 + lib/notifications.ts | 38 +++ lib/payments.js | 682 ----------------------------------------------- lib/payments.js.map | 1 + lib/payments.ts | 682 +++++++++++++++++++++++++++++++++++++++++++++++ lib/rates.js | 71 ----- lib/rates.js.map | 1 + lib/rates.ts | 71 +++++ lib/register.js | 62 ----- lib/register.js.map | 1 + lib/register.ts | 62 +++++ lib/rpc.js | 28 -- lib/rpc.js.map | 1 + lib/rpc.ts | 28 ++ lib/sockets.js | 165 ------------ lib/sockets.js.map | 1 + lib/sockets.ts | 161 +++++++++++ lib/store.js | 22 -- lib/store.js.map | 1 + lib/store.ts | 22 ++ lib/upload.js | 70 ----- lib/upload.js.map | 1 + lib/upload.ts | 70 +++++ lib/utils.js | 160 ----------- lib/utils.js.map | 1 + lib/utils.ts | 160 +++++++++++ lib/webhooks.js | 33 --- lib/webhooks.js.map | 1 + lib/webhooks.ts | 33 +++ lib/whitelist.js | 45 ---- lib/whitelist.js.map | 1 + lib/whitelist.ts | 45 ++++ 87 files changed, 2687 insertions(+), 2661 deletions(-) delete mode 100644 lib/api.js create mode 100644 lib/api.js.map create mode 100644 lib/api.ts delete mode 100644 lib/app.js create mode 100644 lib/app.js.map create mode 100644 lib/app.ts delete mode 100644 lib/auth.js create mode 100644 lib/auth.js.map create mode 100644 lib/auth.ts delete mode 100644 lib/bitcoin.js create mode 100644 lib/bitcoin.js.map create mode 100644 lib/bitcoin.ts delete mode 100644 lib/countries.js create mode 100644 lib/countries.js.map create mode 100644 lib/countries.ts delete mode 100644 lib/db.js create mode 100644 lib/db.js.map create mode 100644 lib/db.ts delete mode 100644 lib/ecash.js create mode 100644 lib/ecash.js.map create mode 100644 lib/ecash.ts delete mode 100644 lib/invoices.js create mode 100644 lib/invoices.js.map create mode 100644 lib/invoices.ts delete mode 100644 lib/lightning.js create mode 100644 lib/lightning.js.map create mode 100644 lib/lightning.ts delete mode 100644 lib/liquid.js create mode 100644 lib/liquid.js.map create mode 100644 lib/liquid.ts delete mode 100644 lib/ln.js create mode 100644 lib/ln.js.map create mode 100644 lib/ln.ts delete mode 100644 lib/locales/index.js create mode 100644 lib/locales/index.js.map create mode 100644 lib/locales/index.ts delete mode 100644 lib/locations.js create mode 100644 lib/locations.js.map create mode 100644 lib/locations.ts delete mode 100644 lib/logging.js create mode 100644 lib/logging.js.map create mode 100644 lib/logging.ts delete mode 100644 lib/mail.js create mode 100644 lib/mail.js.map create mode 100644 lib/mail.ts delete mode 100644 lib/migrate.js create mode 100644 lib/migrate.js.map create mode 100644 lib/migrate.ts delete mode 100644 lib/mqtt.js create mode 100644 lib/mqtt.js.map create mode 100644 lib/mqtt.ts delete mode 100644 lib/nostr.js create mode 100644 lib/nostr.js.map create mode 100644 lib/nostr.ts delete mode 100644 lib/notifications.js create mode 100644 lib/notifications.js.map create mode 100644 lib/notifications.ts delete mode 100644 lib/payments.js create mode 100644 lib/payments.js.map create mode 100644 lib/payments.ts delete mode 100644 lib/rates.js create mode 100644 lib/rates.js.map create mode 100644 lib/rates.ts delete mode 100644 lib/register.js create mode 100644 lib/register.js.map create mode 100644 lib/register.ts delete mode 100644 lib/rpc.js create mode 100644 lib/rpc.js.map create mode 100644 lib/rpc.ts delete mode 100644 lib/sockets.js create mode 100644 lib/sockets.js.map create mode 100644 lib/sockets.ts delete mode 100644 lib/store.js create mode 100644 lib/store.js.map create mode 100644 lib/store.ts delete mode 100644 lib/upload.js create mode 100644 lib/upload.js.map create mode 100644 lib/upload.ts delete mode 100644 lib/utils.js create mode 100644 lib/utils.js.map create mode 100644 lib/utils.ts delete mode 100644 lib/webhooks.js create mode 100644 lib/webhooks.js.map create mode 100644 lib/webhooks.ts delete mode 100644 lib/whitelist.js create mode 100644 lib/whitelist.js.map create mode 100644 lib/whitelist.ts (limited to 'lib') diff --git a/lib/api.js b/lib/api.js deleted file mode 100644 index 3fa8c35b..00000000 --- a/lib/api.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - bitcoin: "https://mempool.space/api/v1", - liquid: "https://liquid.network/api/v1", -}; - diff --git a/lib/api.js.map b/lib/api.js.map new file mode 100644 index 00000000..7b03f30a --- /dev/null +++ b/lib/api.js.map @@ -0,0 +1 @@ +{"version":3,"file":"api.js","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":";;AAAA,kBAAe;IACb,OAAO,EAAE,8BAA8B;IACvC,MAAM,EAAE,+BAA+B;CACxC,CAAC"} \ No newline at end of file diff --git a/lib/api.ts b/lib/api.ts new file mode 100644 index 00000000..3fa8c35b --- /dev/null +++ b/lib/api.ts @@ -0,0 +1,5 @@ +export default { + bitcoin: "https://mempool.space/api/v1", + liquid: "https://liquid.network/api/v1", +}; + diff --git a/lib/app.js b/lib/app.js deleted file mode 100644 index 03ed3bb0..00000000 --- a/lib/app.js +++ /dev/null @@ -1,66 +0,0 @@ -import config from "$config"; -import fastify from "fastify"; -import fastifyMultipart from "@fastify/multipart"; -import fastifyStatic from "@fastify/static"; -import fastifyPassport from "@fastify/passport"; -import fastifySecureSession from "@fastify/secure-session"; -import cors from "@fastify/cors"; - -import path from "path"; -import pino from "pino"; - -import { jwtStrategy } from "$lib/auth"; - -const app = fastify({ - logger: pino(), - disableRequestLogging: true, - maxParamLength: 500, -}); - -await app.register(import("@fastify/rate-limit"), { - allowList: (req, key) => req.raw.url.includes("public"), - max: 100, - timeWindow: 2000, - keyGenerator: (req) => req.headers["cf-connecting-ip"], - errorResponseBuilder: (req, context) => { - return { - statusCode: 429, - error: "Too Many Requests", - message: "Rate limit exceeded, retry in 2 seconds", - }; - }, -}); - -app.register(fastifySecureSession, { - key: Buffer.from(config.jwt, "hex"), -}); - -app.register(fastifyPassport.initialize()); -app.register(fastifyPassport.secureSession()); - -fastifyPassport.use("jwt", jwtStrategy); - -app.register(fastifyMultipart, { limits: { fileSize: 10 ** 7 } }); - -app.register(fastifyStatic, { - root: path.join("/home/bun/app/data/uploads"), - prefix: "/public/", -}); - -await app.register(cors, { - origin: true, -}); - -app.setErrorHandler((error, request, reply) => { - if (error instanceof fastify.errorCodes.FST_ERR_BAD_STATUS_CODE) { - reply.status(500).send({ ok: false }); - } else { - reply.send(error); - } -}); - -app.setNotFoundHandler((request, reply) => { - reply.code(404).type("text/html").send("Not Found"); -}); - -export default app; diff --git a/lib/app.js.map b/lib/app.js.map new file mode 100644 index 00000000..ca7f1a68 --- /dev/null +++ b/lib/app.js.map @@ -0,0 +1 @@ +{"version":3,"file":"app.js","sourceRoot":"","sources":["app.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAA6B;AAC7B,oDAA8B;AAC9B,iEAAkD;AAClD,2DAA4C;AAC5C,+DAAgD;AAChD,2EAA2D;AAC3D,uDAAiC;AAEjC,8CAAwB;AACxB,8CAAwB;AAExB,kCAAwC;AAExC,IAAM,GAAG,GAAG,IAAA,iBAAO,EAAC;IAClB,MAAM,EAAE,IAAA,cAAI,GAAE;IACd,qBAAqB,EAAE,IAAI;IAC3B,cAAc,EAAE,GAAG;CACpB,CAAC,CAAC;AAEH,MAAM,GAAG,CAAC,QAAQ,kEAAQ,qBAAqB,QAAG;IAChD,SAAS,EAAE,UAAC,GAAG,EAAE,GAAG,IAAK,OAAA,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAA9B,CAA8B;IACvD,GAAG,EAAE,GAAG;IACR,UAAU,EAAE,IAAI;IAChB,YAAY,EAAE,UAAC,GAAG,IAAK,OAAA,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAA/B,CAA+B;IACtD,oBAAoB,EAAE,UAAC,GAAG,EAAE,OAAO;QACjC,OAAO;YACL,UAAU,EAAE,GAAG;YACf,KAAK,EAAE,mBAAmB;YAC1B,OAAO,EAAE,yCAAyC;SACnD,CAAC;IACJ,CAAC;CACF,CAAC,CAAC;AAEH,GAAG,CAAC,QAAQ,CAAC,wBAAoB,EAAE;IACjC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,iBAAM,CAAC,GAAG,EAAE,KAAK,CAAC;CACpC,CAAC,CAAC;AAEH,GAAG,CAAC,QAAQ,CAAC,kBAAe,CAAC,UAAU,EAAE,CAAC,CAAC;AAC3C,GAAG,CAAC,QAAQ,CAAC,kBAAe,CAAC,aAAa,EAAE,CAAC,CAAC;AAE9C,kBAAe,CAAC,GAAG,CAAC,KAAK,EAAE,kBAAW,CAAC,CAAC;AAExC,GAAG,CAAC,QAAQ,CAAC,mBAAgB,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,SAAA,EAAE,EAAI,CAAC,CAAA,EAAE,EAAE,CAAC,CAAC;AAElE,GAAG,CAAC,QAAQ,CAAC,gBAAa,EAAE;IAC1B,IAAI,EAAE,cAAI,CAAC,IAAI,CAAC,4BAA4B,CAAC;IAC7C,MAAM,EAAE,UAAU;CACnB,CAAC,CAAC;AAEH,MAAM,GAAG,CAAC,QAAQ,CAAC,cAAI,EAAE;IACvB,MAAM,EAAE,IAAI;CACb,CAAC,CAAC;AAEH,GAAG,CAAC,eAAe,CAAC,UAAC,KAAK,EAAE,OAAO,EAAE,KAAK;IACxC,IAAI,KAAK,YAAY,iBAAO,CAAC,UAAU,CAAC,uBAAuB,EAAE,CAAC;QAChE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACxC,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,kBAAkB,CAAC,UAAC,OAAO,EAAE,KAAK;IACpC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC;AAEH,kBAAe,GAAG,CAAC"} \ No newline at end of file diff --git a/lib/app.ts b/lib/app.ts new file mode 100644 index 00000000..03ed3bb0 --- /dev/null +++ b/lib/app.ts @@ -0,0 +1,66 @@ +import config from "$config"; +import fastify from "fastify"; +import fastifyMultipart from "@fastify/multipart"; +import fastifyStatic from "@fastify/static"; +import fastifyPassport from "@fastify/passport"; +import fastifySecureSession from "@fastify/secure-session"; +import cors from "@fastify/cors"; + +import path from "path"; +import pino from "pino"; + +import { jwtStrategy } from "$lib/auth"; + +const app = fastify({ + logger: pino(), + disableRequestLogging: true, + maxParamLength: 500, +}); + +await app.register(import("@fastify/rate-limit"), { + allowList: (req, key) => req.raw.url.includes("public"), + max: 100, + timeWindow: 2000, + keyGenerator: (req) => req.headers["cf-connecting-ip"], + errorResponseBuilder: (req, context) => { + return { + statusCode: 429, + error: "Too Many Requests", + message: "Rate limit exceeded, retry in 2 seconds", + }; + }, +}); + +app.register(fastifySecureSession, { + key: Buffer.from(config.jwt, "hex"), +}); + +app.register(fastifyPassport.initialize()); +app.register(fastifyPassport.secureSession()); + +fastifyPassport.use("jwt", jwtStrategy); + +app.register(fastifyMultipart, { limits: { fileSize: 10 ** 7 } }); + +app.register(fastifyStatic, { + root: path.join("/home/bun/app/data/uploads"), + prefix: "/public/", +}); + +await app.register(cors, { + origin: true, +}); + +app.setErrorHandler((error, request, reply) => { + if (error instanceof fastify.errorCodes.FST_ERR_BAD_STATUS_CODE) { + reply.status(500).send({ ok: false }); + } else { + reply.send(error); + } +}); + +app.setNotFoundHandler((request, reply) => { + reply.code(404).type("text/html").send("Not Found"); +}); + +export default app; diff --git a/lib/auth.js b/lib/auth.js deleted file mode 100644 index 0a8d43d3..00000000 --- a/lib/auth.js +++ /dev/null @@ -1,51 +0,0 @@ -import fastifyPassport from "@fastify/passport"; -import { g } from "$lib/db"; -import { fail, getUser } from "$lib/utils"; -import config from "$config"; -import jwt from "passport-jwt"; - -export const admin = { - preValidation: fastifyPassport.authenticate( - "jwt", - { session: false }, - function (req, res, err, user, info) { - if (!user.admin) return res.code(401).send("unauthorized"); - req.user = user; - }, - ), -}; - -export const optional = { - preValidation: fastifyPassport.authenticate( - "jwt", - { session: false }, - function (req, res, err, user, info) { - req.user = user; - }, - ), -}; - -export const auth = { - preValidation: fastifyPassport.authenticate("jwt", { - authInfo: false, - session: false, - }), -}; - -export const requirePin = async ({ body, user }) => { - if (!user || (user.pin && user.pin !== body.pin)) fail("Invalid pin"); -}; - -export const jwtStrategy = new jwt.Strategy( - { - jwtFromRequest: jwt.ExtractJwt.fromExtractors([ - jwt.ExtractJwt.fromAuthHeaderAsBearerToken(), - (req) => (req.cookies ? req.cookies.token : null), - ]), - secretOrKey: config.jwt, - }, - async (payload, next) => { - let user = await getUser(payload.id); - next(null, user); - }, -); diff --git a/lib/auth.js.map b/lib/auth.js.map new file mode 100644 index 00000000..e539bafd --- /dev/null +++ b/lib/auth.js.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.js","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+DAAgD;AAEhD,oCAA2C;AAC3C,oDAA6B;AAC7B,8DAA+B;AAElB,QAAA,KAAK,GAAG;IACnB,aAAa,EAAE,kBAAe,CAAC,YAAY,CACzC,KAAK,EACL,EAAE,OAAO,EAAE,KAAK,EAAE,EAClB,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI;QACjC,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC3D,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC,CACF;CACF,CAAC;AAEW,QAAA,QAAQ,GAAG;IACtB,aAAa,EAAE,kBAAe,CAAC,YAAY,CACzC,KAAK,EACL,EAAE,OAAO,EAAE,KAAK,EAAE,EAClB,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI;QACjC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC,CACF;CACF,CAAC;AAEW,QAAA,IAAI,GAAG;IAClB,aAAa,EAAE,kBAAe,CAAC,YAAY,CAAC,KAAK,EAAE;QACjD,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,KAAK;KACf,CAAC;CACH,CAAC;AAEK,IAAM,UAAU,GAAG,iEAAO,EAAc;QAAZ,IAAI,UAAA,EAAE,IAAI,UAAA;;QAC3C,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,CAAC;YAAE,IAAA,YAAI,EAAC,aAAa,CAAC,CAAC;;;KACvE,CAAC;AAFW,QAAA,UAAU,cAErB;AAEW,QAAA,WAAW,GAAG,IAAI,sBAAG,CAAC,QAAQ,CACzC;IACE,cAAc,EAAE,sBAAG,CAAC,UAAU,CAAC,cAAc,CAAC;QAC5C,sBAAG,CAAC,UAAU,CAAC,2BAA2B,EAAE;QAC5C,UAAC,GAAG,IAAK,OAAA,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAxC,CAAwC;KAClD,CAAC;IACF,WAAW,EAAE,iBAAM,CAAC,GAAG;CACxB,EACD,UAAO,OAAO,EAAE,IAAI;;;;oBACP,qBAAM,IAAA,eAAO,EAAC,OAAO,CAAC,EAAE,CAAC,EAAA;;gBAAhC,IAAI,GAAG,SAAyB;gBACpC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;;;;KAClB,CACF,CAAC"} \ No newline at end of file diff --git a/lib/auth.ts b/lib/auth.ts new file mode 100644 index 00000000..0a8d43d3 --- /dev/null +++ b/lib/auth.ts @@ -0,0 +1,51 @@ +import fastifyPassport from "@fastify/passport"; +import { g } from "$lib/db"; +import { fail, getUser } from "$lib/utils"; +import config from "$config"; +import jwt from "passport-jwt"; + +export const admin = { + preValidation: fastifyPassport.authenticate( + "jwt", + { session: false }, + function (req, res, err, user, info) { + if (!user.admin) return res.code(401).send("unauthorized"); + req.user = user; + }, + ), +}; + +export const optional = { + preValidation: fastifyPassport.authenticate( + "jwt", + { session: false }, + function (req, res, err, user, info) { + req.user = user; + }, + ), +}; + +export const auth = { + preValidation: fastifyPassport.authenticate("jwt", { + authInfo: false, + session: false, + }), +}; + +export const requirePin = async ({ body, user }) => { + if (!user || (user.pin && user.pin !== body.pin)) fail("Invalid pin"); +}; + +export const jwtStrategy = new jwt.Strategy( + { + jwtFromRequest: jwt.ExtractJwt.fromExtractors([ + jwt.ExtractJwt.fromAuthHeaderAsBearerToken(), + (req) => (req.cookies ? req.cookies.token : null), + ]), + secretOrKey: config.jwt, + }, + async (payload, next) => { + let user = await getUser(payload.id); + next(null, user); + }, +); diff --git a/lib/bitcoin.js b/lib/bitcoin.js deleted file mode 100644 index a811dc14..00000000 --- a/lib/bitcoin.js +++ /dev/null @@ -1,3 +0,0 @@ -import config from "$config"; -import rpc from "$lib/rpc"; -export default rpc(config.bitcoin); diff --git a/lib/bitcoin.js.map b/lib/bitcoin.js.map new file mode 100644 index 00000000..b236de0d --- /dev/null +++ b/lib/bitcoin.js.map @@ -0,0 +1 @@ +{"version":3,"file":"bitcoin.js","sourceRoot":"","sources":["bitcoin.ts"],"names":[],"mappings":";;;;;AAAA,oDAA6B;AAC7B,iDAA2B;AAC3B,kBAAe,IAAA,aAAG,EAAC,iBAAM,CAAC,OAAO,CAAC,CAAC"} \ No newline at end of file diff --git a/lib/bitcoin.ts b/lib/bitcoin.ts new file mode 100644 index 00000000..a811dc14 --- /dev/null +++ b/lib/bitcoin.ts @@ -0,0 +1,3 @@ +import config from "$config"; +import rpc from "$lib/rpc"; +export default rpc(config.bitcoin); diff --git a/lib/countries.js b/lib/countries.js deleted file mode 100644 index 70f04c84..00000000 --- a/lib/countries.js +++ /dev/null @@ -1,248 +0,0 @@ -export default { - AF: "AFN", - AL: "ALL", - DZ: "DZD", - AD: "EUR", - AO: "AOA", - AI: "XCD", - AQ: "USD", - AR: "ARS", - AM: "AMD", - AW: "AWG", - AU: "AUD", - AT: "EUR", - AZ: "AZN", - BS: "BSD", - BH: "BHD", - BD: "BDT", - BB: "BBD", - BY: "BYN", - BE: "EUR", - BZ: "BZD", - BJ: "XOF", - BM: "BMD", - BT: "BTN", - BO: "BOB", - BW: "BWP", - BV: "NOK", - BR: "BRL", - IO: "USD", - VG: "USD", - BN: "BND", - BG: "BGN", - BF: "XOF", - BI: "BIF", - KH: "KHR", - CM: "XAF", - CA: "CAD", - IC: "EUR", - CV: "CVE", - KY: "KYD", - CF: "XAF", - TD: "XAF", - CL: "CLP", - CN: "CNY", - CX: "AUD", - CC: "AUD", - CO: "COP", - KM: "KMF", - CG: "XAF", - CD: "CDF", - CK: "NZD", - CR: "CRC", - CI: "XOF", - HR: "HRK", - CU: "CUP", - CW: "ANG", - CY: "EUR", - CZ: "CZK", - DK: "DKK", - DJ: "DJF", - DM: "XCD", - DO: "DOP", - TL: "USD", - EC: "USD", - EG: "EGP", - SV: "USD", - GQ: "XAF", - ER: "ERN", - EE: "EUR", - ET: "ETB", - FK: "FKP", - FO: "DKK", - FJ: "FJD", - FI: "EUR", - FR: "EUR", - GF: "EUR", - PF: "XPF", - TF: "EUR", - GA: "XAF", - GM: "GMD", - GE: "GEL", - DE: "EUR", - GH: "GHS", - GI: "GIP", - GR: "EUR", - GL: "DKK", - GD: "XCD", - GP: "EUR", - GU: "USD", - GT: "GTQ", - GG: "GBP", - GN: "GNF", - GW: "XOF", - GY: "GYD", - HT: "HTG", - HM: "AUD", - HN: "HNL", - HK: "HKD", - HU: "HUF", - IS: "ISK", - IN: "INR", - ID: "IDR", - IR: "IRR", - IQ: "IQD", - IE: "EUR", - IM: "GBP", - IL: "ILS", - IT: "EUR", - JM: "JMD", - JP: "JPY", - JE: "GBP", - JO: "JOD", - KZ: "KZT", - KE: "KES", - KI: "AUD", - XK: "EUR", - KW: "KWD", - KG: "KGS", - LA: "LAK", - LV: "EUR", - LB: "LBP", - LS: "LSL", - LR: "LRD", - LY: "LYD", - LI: "CHF", - LT: "EUR", - LU: "EUR", - MO: "MOP", - MK: "MKD", - MG: "MGA", - MW: "MWK", - MY: "MYR", - MV: "MVR", - ML: "XOF", - MT: "EUR", - MH: "USD", - MQ: "EUR", - MR: "MRO", - MU: "MUR", - YT: "EUR", - MX: "MXN", - FM: "USD", - MD: "MDL", - MC: "EUR", - MN: "MNT", - ME: "EUR", - MS: "XCD", - MA: "MAD", - MZ: "MZN", - MM: "MMK", - NA: "NAD", - NR: "AUD", - NP: "NPR", - NL: "EUR", - AN: "ANG", - NC: "XPF", - NZ: "NZD", - NI: "NIO", - NE: "XOF", - NG: "NGN", - NU: "NZD", - NF: "AUD", - KP: "KPW", - MP: "USD", - NO: "NOK", - OM: "OMR", - PK: "PKR", - PW: "USD", - PS: "ILS", - PA: "PAB", - PG: "PGK", - PY: "PYG", - PE: "PEN", - PH: "PHP", - PN: "NZD", - PL: "PLN", - PT: "EUR", - PR: "USD", - QA: "QAR", - RE: "EUR", - RO: "RON", - RU: "RUB", - RW: "RWF", - BL: "EUR", - SH: "SHP", - KN: "XCD", - LC: "XCD", - MF: "EUR", - PM: "EUR", - VC: "XCD", - WS: "WST", - SM: "EUR", - ST: "STN", - SA: "SAR", - SN: "XOF", - RS: "RSD", - SC: "SCR", - SL: "SLL", - SG: "SGD", - SX: "ANG", - SK: "EUR", - SI: "EUR", - SB: "SBD", - SO: "SOS", - ZA: "ZAR", - GS: "GBP", - KR: "KRW", - SS: "SSP", - ES: "EUR", - LK: "LKR", - SD: "SDG", - SR: "SRD", - SJ: "NOK", - SZ: "SZL", - SE: "SEK", - CH: "CHF", - SY: "SYP", - TW: "TWD", - TJ: "TJS", - TZ: "TZS", - TH: "THB", - TG: "XOF", - TK: "NZD", - TO: "TOP", - TT: "TTD", - TA: "SHP", - TN: "TND", - TR: "TRY", - TM: "TMT", - TC: "USD", - TV: "AUD", - UM: "USD", - VI: "USD", - UG: "UGX", - UA: "UAH", - AE: "AED", - GB: "GBP", - US: "USD", - UY: "UYU", - UZ: "UZS", - VU: "VUV", - VA: "EUR", - VE: "VES", - VN: "VND", - YE: "YER", - ZM: "ZMW", - ZW: "ZWL", -}; diff --git a/lib/countries.js.map b/lib/countries.js.map new file mode 100644 index 00000000..066e1d34 --- /dev/null +++ b/lib/countries.js.map @@ -0,0 +1 @@ +{"version":3,"file":"countries.js","sourceRoot":"","sources":["countries.ts"],"names":[],"mappings":";;AAAA,kBAAe;IACb,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;CACV,CAAC"} \ No newline at end of file diff --git a/lib/countries.ts b/lib/countries.ts new file mode 100644 index 00000000..70f04c84 --- /dev/null +++ b/lib/countries.ts @@ -0,0 +1,248 @@ +export default { + AF: "AFN", + AL: "ALL", + DZ: "DZD", + AD: "EUR", + AO: "AOA", + AI: "XCD", + AQ: "USD", + AR: "ARS", + AM: "AMD", + AW: "AWG", + AU: "AUD", + AT: "EUR", + AZ: "AZN", + BS: "BSD", + BH: "BHD", + BD: "BDT", + BB: "BBD", + BY: "BYN", + BE: "EUR", + BZ: "BZD", + BJ: "XOF", + BM: "BMD", + BT: "BTN", + BO: "BOB", + BW: "BWP", + BV: "NOK", + BR: "BRL", + IO: "USD", + VG: "USD", + BN: "BND", + BG: "BGN", + BF: "XOF", + BI: "BIF", + KH: "KHR", + CM: "XAF", + CA: "CAD", + IC: "EUR", + CV: "CVE", + KY: "KYD", + CF: "XAF", + TD: "XAF", + CL: "CLP", + CN: "CNY", + CX: "AUD", + CC: "AUD", + CO: "COP", + KM: "KMF", + CG: "XAF", + CD: "CDF", + CK: "NZD", + CR: "CRC", + CI: "XOF", + HR: "HRK", + CU: "CUP", + CW: "ANG", + CY: "EUR", + CZ: "CZK", + DK: "DKK", + DJ: "DJF", + DM: "XCD", + DO: "DOP", + TL: "USD", + EC: "USD", + EG: "EGP", + SV: "USD", + GQ: "XAF", + ER: "ERN", + EE: "EUR", + ET: "ETB", + FK: "FKP", + FO: "DKK", + FJ: "FJD", + FI: "EUR", + FR: "EUR", + GF: "EUR", + PF: "XPF", + TF: "EUR", + GA: "XAF", + GM: "GMD", + GE: "GEL", + DE: "EUR", + GH: "GHS", + GI: "GIP", + GR: "EUR", + GL: "DKK", + GD: "XCD", + GP: "EUR", + GU: "USD", + GT: "GTQ", + GG: "GBP", + GN: "GNF", + GW: "XOF", + GY: "GYD", + HT: "HTG", + HM: "AUD", + HN: "HNL", + HK: "HKD", + HU: "HUF", + IS: "ISK", + IN: "INR", + ID: "IDR", + IR: "IRR", + IQ: "IQD", + IE: "EUR", + IM: "GBP", + IL: "ILS", + IT: "EUR", + JM: "JMD", + JP: "JPY", + JE: "GBP", + JO: "JOD", + KZ: "KZT", + KE: "KES", + KI: "AUD", + XK: "EUR", + KW: "KWD", + KG: "KGS", + LA: "LAK", + LV: "EUR", + LB: "LBP", + LS: "LSL", + LR: "LRD", + LY: "LYD", + LI: "CHF", + LT: "EUR", + LU: "EUR", + MO: "MOP", + MK: "MKD", + MG: "MGA", + MW: "MWK", + MY: "MYR", + MV: "MVR", + ML: "XOF", + MT: "EUR", + MH: "USD", + MQ: "EUR", + MR: "MRO", + MU: "MUR", + YT: "EUR", + MX: "MXN", + FM: "USD", + MD: "MDL", + MC: "EUR", + MN: "MNT", + ME: "EUR", + MS: "XCD", + MA: "MAD", + MZ: "MZN", + MM: "MMK", + NA: "NAD", + NR: "AUD", + NP: "NPR", + NL: "EUR", + AN: "ANG", + NC: "XPF", + NZ: "NZD", + NI: "NIO", + NE: "XOF", + NG: "NGN", + NU: "NZD", + NF: "AUD", + KP: "KPW", + MP: "USD", + NO: "NOK", + OM: "OMR", + PK: "PKR", + PW: "USD", + PS: "ILS", + PA: "PAB", + PG: "PGK", + PY: "PYG", + PE: "PEN", + PH: "PHP", + PN: "NZD", + PL: "PLN", + PT: "EUR", + PR: "USD", + QA: "QAR", + RE: "EUR", + RO: "RON", + RU: "RUB", + RW: "RWF", + BL: "EUR", + SH: "SHP", + KN: "XCD", + LC: "XCD", + MF: "EUR", + PM: "EUR", + VC: "XCD", + WS: "WST", + SM: "EUR", + ST: "STN", + SA: "SAR", + SN: "XOF", + RS: "RSD", + SC: "SCR", + SL: "SLL", + SG: "SGD", + SX: "ANG", + SK: "EUR", + SI: "EUR", + SB: "SBD", + SO: "SOS", + ZA: "ZAR", + GS: "GBP", + KR: "KRW", + SS: "SSP", + ES: "EUR", + LK: "LKR", + SD: "SDG", + SR: "SRD", + SJ: "NOK", + SZ: "SZL", + SE: "SEK", + CH: "CHF", + SY: "SYP", + TW: "TWD", + TJ: "TJS", + TZ: "TZS", + TH: "THB", + TG: "XOF", + TK: "NZD", + TO: "TOP", + TT: "TTD", + TA: "SHP", + TN: "TND", + TR: "TRY", + TM: "TMT", + TC: "USD", + TV: "AUD", + UM: "USD", + VI: "USD", + UG: "UGX", + UA: "UAH", + AE: "AED", + GB: "GBP", + US: "USD", + UY: "UYU", + UZ: "UZS", + VU: "VUV", + VA: "EUR", + VE: "VES", + VN: "VND", + YE: "YER", + ZM: "ZMW", + ZW: "ZWL", +}; diff --git a/lib/db.js b/lib/db.js deleted file mode 100644 index 4f113a1d..00000000 --- a/lib/db.js +++ /dev/null @@ -1,142 +0,0 @@ -import config from "$config"; -import { fail, wait, sleep } from "$lib/utils"; -import { createClient, defineScript } from "redis"; -import { warn, l, err } from "$lib/logging"; - -let SCRIPT = ` -local balanceKey = KEYS[1] -local creditKey = KEYS[2] -local amount = tonumber(ARGV[1]) -local tip = tonumber(ARGV[2]) -local fee = tonumber(ARGV[3]) -local ourfee = tonumber(ARGV[4]) - -local balance = tonumber(redis.call('get', balanceKey) or '0') -local credit = tonumber(redis.call('get', creditKey) or '0') - -local covered = math.min(credit, ourfee) -ourfee = ourfee - covered - -if balance < amount + tip + fee + ourfee then - return {err = 'Insufficient funds ⚡️' .. balance .. ' of ⚡️' .. amount + tip + fee + ourfee} -end - -redis.call('decrby', creditKey, tostring(math.floor(covered))) -redis.call('decrby', balanceKey, tostring(math.floor(amount + tip + fee + ourfee))) - -return ourfee -`; - -let debit = defineScript({ - NUMBER_OF_KEYS: 2, - SCRIPT, - transformArguments: (...args) => args.map((a) => a.toString()), -}); - -export let db = createClient({ - url: config.db, - scripts: { debit }, - socket: { - reconnectStrategy: (retries) => Math.min(retries * 50, 5000), - }, -}); - -export let archive = createClient({ - url: config.archive, - socket: { - reconnectStrategy: (retries) => Math.min(retries * 50, 5000), - }, -}); - -export let arc2 = createClient({ - url: config.arc2, - socket: { - reconnectStrategy: (retries) => Math.min(retries * 50, 5000), - }, -}); - -async function dbReconnect() { - try { - await db.connect(); - } catch (err) { - console.error("Failed to connect to Redis, retrying...", err); - setTimeout(dbReconnect, 5000); // Retry after 5 seconds - } -} - -async function archiveReconnect() { - try { - await archive.connect(); - } catch (err) { - console.error("Failed to connect to Redis, retrying...", err); - setTimeout(archiveReconnect, 5000); // Retry after 5 seconds - } -} - -dbReconnect(); -archiveReconnect(); - -db.on("error", (e) => { - if (e.message.startsWith("getaddr")) return; - err("Redis error", e.message); -}); - -db.on("end", () => { - warn("Redis connection ended"); -}); - -export default db; - -export let g = async (k) => { - let v = await db.get(k); - try { - return JSON.parse(v); - } catch (e) { - return v; - } -}; - -export let s = (k, v) => { - if (k === "user:null" || k === "user:undefined") fail("null user"); - db.set(k, JSON.stringify(v)); -}; - -export let ga = async (k) => { - let v = await archive.get(k); - try { - return JSON.parse(v); - } catch (e) { - return v; - } -}; - -export let sa = (k, v) => { - if (k === "user:null" || k === "user:undefined") { - warn("###### NULL USER #######"); - console.trace(); - } - archive.set(k, JSON.stringify(v)); -}; - -let retries = {}; -export let t = async (k, f) => { - try { - await db.watch(k); - await f(await db.get(k), db); - } catch (err) { - if (!err.message.includes("watch")) throw err; - - let r = retries[k] || 0; - retries[k] = r + 1; - - if (r < 10) { - await sleep(100); - await t(k, f); - } else { - delete retries[k]; - fail("unable to obtain lock"); - } - } - - delete retries[k]; -}; diff --git a/lib/db.js.map b/lib/db.js.map new file mode 100644 index 00000000..349459a4 --- /dev/null +++ b/lib/db.js.map @@ -0,0 +1 @@ +{"version":3,"file":"db.js","sourceRoot":"","sources":["db.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/db.ts b/lib/db.ts new file mode 100644 index 00000000..4f113a1d --- /dev/null +++ b/lib/db.ts @@ -0,0 +1,142 @@ +import config from "$config"; +import { fail, wait, sleep } from "$lib/utils"; +import { createClient, defineScript } from "redis"; +import { warn, l, err } from "$lib/logging"; + +let SCRIPT = ` +local balanceKey = KEYS[1] +local creditKey = KEYS[2] +local amount = tonumber(ARGV[1]) +local tip = tonumber(ARGV[2]) +local fee = tonumber(ARGV[3]) +local ourfee = tonumber(ARGV[4]) + +local balance = tonumber(redis.call('get', balanceKey) or '0') +local credit = tonumber(redis.call('get', creditKey) or '0') + +local covered = math.min(credit, ourfee) +ourfee = ourfee - covered + +if balance < amount + tip + fee + ourfee then + return {err = 'Insufficient funds ⚡️' .. balance .. ' of ⚡️' .. amount + tip + fee + ourfee} +end + +redis.call('decrby', creditKey, tostring(math.floor(covered))) +redis.call('decrby', balanceKey, tostring(math.floor(amount + tip + fee + ourfee))) + +return ourfee +`; + +let debit = defineScript({ + NUMBER_OF_KEYS: 2, + SCRIPT, + transformArguments: (...args) => args.map((a) => a.toString()), +}); + +export let db = createClient({ + url: config.db, + scripts: { debit }, + socket: { + reconnectStrategy: (retries) => Math.min(retries * 50, 5000), + }, +}); + +export let archive = createClient({ + url: config.archive, + socket: { + reconnectStrategy: (retries) => Math.min(retries * 50, 5000), + }, +}); + +export let arc2 = createClient({ + url: config.arc2, + socket: { + reconnectStrategy: (retries) => Math.min(retries * 50, 5000), + }, +}); + +async function dbReconnect() { + try { + await db.connect(); + } catch (err) { + console.error("Failed to connect to Redis, retrying...", err); + setTimeout(dbReconnect, 5000); // Retry after 5 seconds + } +} + +async function archiveReconnect() { + try { + await archive.connect(); + } catch (err) { + console.error("Failed to connect to Redis, retrying...", err); + setTimeout(archiveReconnect, 5000); // Retry after 5 seconds + } +} + +dbReconnect(); +archiveReconnect(); + +db.on("error", (e) => { + if (e.message.startsWith("getaddr")) return; + err("Redis error", e.message); +}); + +db.on("end", () => { + warn("Redis connection ended"); +}); + +export default db; + +export let g = async (k) => { + let v = await db.get(k); + try { + return JSON.parse(v); + } catch (e) { + return v; + } +}; + +export let s = (k, v) => { + if (k === "user:null" || k === "user:undefined") fail("null user"); + db.set(k, JSON.stringify(v)); +}; + +export let ga = async (k) => { + let v = await archive.get(k); + try { + return JSON.parse(v); + } catch (e) { + return v; + } +}; + +export let sa = (k, v) => { + if (k === "user:null" || k === "user:undefined") { + warn("###### NULL USER #######"); + console.trace(); + } + archive.set(k, JSON.stringify(v)); +}; + +let retries = {}; +export let t = async (k, f) => { + try { + await db.watch(k); + await f(await db.get(k), db); + } catch (err) { + if (!err.message.includes("watch")) throw err; + + let r = retries[k] || 0; + retries[k] = r + 1; + + if (r < 10) { + await sleep(100); + await t(k, f); + } else { + delete retries[k]; + fail("unable to obtain lock"); + } + } + + delete retries[k]; +}; diff --git a/lib/ecash.js b/lib/ecash.js deleted file mode 100644 index 4ce8912a..00000000 --- a/lib/ecash.js +++ /dev/null @@ -1,60 +0,0 @@ -import config from "$config"; -import { - CashuMint, - CashuWallet, - getDecodedToken, - getEncodedToken, - MintQuoteState, -} from "@cashu/cashu-ts"; -import { g, s } from "$lib/db"; -import ln from "$lib/ln"; -import { fail } from "$lib/utils"; - -let m = new CashuMint(config.mintUrl); -let w = new CashuWallet(m); - -let enc = (proofs) => - getEncodedToken({ - token: [{ mint: config.mintUrl, proofs }], - }); - -let dec = (token) => getDecodedToken(token).token[0]; - -let ext = async (mint) => { - let issuer = new CashuMint(mint); - let { pubkey: issuerPk } = await issuer.getInfo(); - let { pubkey: ourPk } = await m.getInfo(); - return issuerPk !== ourPk; -}; - -export async function claim(token) { - let { proofs: current } = dec(await g(`cash`)); - let { mint } = dec(token); - - if (await ext(mint)) fail("Unable to receive from other mints"); - - let rcvd = await w.receive(token); - - await s(`cash`, enc([...current, ...rcvd])); - return rcvd.reduce((a, b) => a + b.amount, 0); -} - -export async function mint(amount) { - let { proofs } = dec(await g(`cash`)); - let { send, returnChange } = await w.send(amount, proofs); - let rcvd = await w.receive(enc(send)); - await s(`cash`, enc(returnChange)); - return enc(rcvd); -} - -export async function check(token) { - let { mint, proofs } = dec(token); - let total = proofs.reduce((a, b) => a + b.amount, 0); - - let external = await ext(mint); - - let r = await w.checkProofsSpent(proofs); - let spent = r.reduce((a, b) => a + b.amount, 0); - - return { total, spent, mint, external }; -} diff --git a/lib/ecash.js.map b/lib/ecash.js.map new file mode 100644 index 00000000..d6cc9117 --- /dev/null +++ b/lib/ecash.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ecash.js","sourceRoot":"","sources":["ecash.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/ecash.ts b/lib/ecash.ts new file mode 100644 index 00000000..4ce8912a --- /dev/null +++ b/lib/ecash.ts @@ -0,0 +1,60 @@ +import config from "$config"; +import { + CashuMint, + CashuWallet, + getDecodedToken, + getEncodedToken, + MintQuoteState, +} from "@cashu/cashu-ts"; +import { g, s } from "$lib/db"; +import ln from "$lib/ln"; +import { fail } from "$lib/utils"; + +let m = new CashuMint(config.mintUrl); +let w = new CashuWallet(m); + +let enc = (proofs) => + getEncodedToken({ + token: [{ mint: config.mintUrl, proofs }], + }); + +let dec = (token) => getDecodedToken(token).token[0]; + +let ext = async (mint) => { + let issuer = new CashuMint(mint); + let { pubkey: issuerPk } = await issuer.getInfo(); + let { pubkey: ourPk } = await m.getInfo(); + return issuerPk !== ourPk; +}; + +export async function claim(token) { + let { proofs: current } = dec(await g(`cash`)); + let { mint } = dec(token); + + if (await ext(mint)) fail("Unable to receive from other mints"); + + let rcvd = await w.receive(token); + + await s(`cash`, enc([...current, ...rcvd])); + return rcvd.reduce((a, b) => a + b.amount, 0); +} + +export async function mint(amount) { + let { proofs } = dec(await g(`cash`)); + let { send, returnChange } = await w.send(amount, proofs); + let rcvd = await w.receive(enc(send)); + await s(`cash`, enc(returnChange)); + return enc(rcvd); +} + +export async function check(token) { + let { mint, proofs } = dec(token); + let total = proofs.reduce((a, b) => a + b.amount, 0); + + let external = await ext(mint); + + let r = await w.checkProofsSpent(proofs); + let spent = r.reduce((a, b) => a + b.amount, 0); + + return { total, spent, mint, external }; +} diff --git a/lib/invoices.js b/lib/invoices.js deleted file mode 100644 index 2f1225e7..00000000 --- a/lib/invoices.js +++ /dev/null @@ -1,128 +0,0 @@ -import crypto from "crypto"; -import store from "$lib/store"; -import { l } from "$lib/logging"; -import { emit } from "$lib/sockets"; -import { db, g, s } from "$lib/db"; -import { getUser, bip21, fail, SATS } from "$lib/utils"; -import { types } from "$lib/payments"; -import { v4 } from "uuid"; - -import lq from "$lib/liquid"; -import bc from "$lib/bitcoin"; -import ln from "$lib/ln"; - -export let generate = async ({ invoice, user, sender }) => { - let { - bolt11, - currency, - expiry, - fiat, - tip, - amount, - items = [], - rate, - request_id, - memo, - memoPrompt, - prompt, - type, - webhook, - secret, - } = invoice; - - amount = parseInt(amount || 0); - tip = parseInt(tip) || null; - - if (user) user = await getUser(user.username); - else if (sender) user = await getUser(sender.username); - if (!user) fail("user not provided"); - - let rates = await g('rates'); - if (!currency) currency = user.currency; - if (!rate) rate = rates[currency]; - if (fiat) amount = Math.round((SATS * fiat) / rate); - if (amount < 0) fail("invalid amount"); - if (tip < 0) fail("invalid tip"); - if (rate < 0) fail("invalid rate"); - if (memo && memo.length > 5000) fail("memo too long"); - - let id = v4(); - - let hash, text; - if (type === types.lightning) { - let r; - if (bolt11) { - let { id: nodeid } = await ln.getinfo(); - r = await ln.decode(bolt11); - if (r.payee !== nodeid) fail("invalid invoice"); - amount = Math.round(r.amount_msat / 1000); - r.bolt11 = bolt11; - } else { - r = await ln.invoice({ - amount_msat: amount ? `${amount + tip}sat` : "any", - label: id, - description: memo || "", - expiry: expiry || 60 * 60 * 24 * 30, - deschashonly: true, - cltv: 19, - }); - } - - hash = r.bolt11; - text = r.bolt11; - } else if (type === types.bitcoin) { - hash = await bc.getNewAddress(); - text = bip21(hash, invoice); - } else if (type === types.liquid) { - hash = await lq.getNewAddress(); - text = bip21(hash, invoice); - } else if (type === types.internal) { - hash = id; - } else { - fail("unrecognized type"); - } - - invoice = { - amount, - created: Date.now(), - currency, - hash, - id, - items, - memo, - rate, - pending: 0, - received: 0, - request_id, - memoPrompt, - prompt, - secret, - text, - tip, - type, - uid: user.id, - webhook, - }; - - if (type === "liquid") { - let { unconfidential } = await lq.getAddressInfo(hash); - await s(`invoice:${unconfidential}`, id); - } - - await s(`invoice:${hash}`, id); - await s(`invoice:${id}`, invoice); - await db.lPush(`${user.id}:invoices`, id); - - if (request_id) { - let request = await g(`request:${request_id}`); - if (request) { - let { invoice_id: prev } = request; - request.invoice_id = id; - await s(`request:${request_id}`, request); - - if (!prev) emit(request.requester_id, "invoice", invoice); - } - } - - return invoice; -}; diff --git a/lib/invoices.js.map b/lib/invoices.js.map new file mode 100644 index 00000000..e48d08fc --- /dev/null +++ b/lib/invoices.js.map @@ -0,0 +1 @@ +{"version":3,"file":"invoices.js","sourceRoot":"","sources":["invoices.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/invoices.ts b/lib/invoices.ts new file mode 100644 index 00000000..2f1225e7 --- /dev/null +++ b/lib/invoices.ts @@ -0,0 +1,128 @@ +import crypto from "crypto"; +import store from "$lib/store"; +import { l } from "$lib/logging"; +import { emit } from "$lib/sockets"; +import { db, g, s } from "$lib/db"; +import { getUser, bip21, fail, SATS } from "$lib/utils"; +import { types } from "$lib/payments"; +import { v4 } from "uuid"; + +import lq from "$lib/liquid"; +import bc from "$lib/bitcoin"; +import ln from "$lib/ln"; + +export let generate = async ({ invoice, user, sender }) => { + let { + bolt11, + currency, + expiry, + fiat, + tip, + amount, + items = [], + rate, + request_id, + memo, + memoPrompt, + prompt, + type, + webhook, + secret, + } = invoice; + + amount = parseInt(amount || 0); + tip = parseInt(tip) || null; + + if (user) user = await getUser(user.username); + else if (sender) user = await getUser(sender.username); + if (!user) fail("user not provided"); + + let rates = await g('rates'); + if (!currency) currency = user.currency; + if (!rate) rate = rates[currency]; + if (fiat) amount = Math.round((SATS * fiat) / rate); + if (amount < 0) fail("invalid amount"); + if (tip < 0) fail("invalid tip"); + if (rate < 0) fail("invalid rate"); + if (memo && memo.length > 5000) fail("memo too long"); + + let id = v4(); + + let hash, text; + if (type === types.lightning) { + let r; + if (bolt11) { + let { id: nodeid } = await ln.getinfo(); + r = await ln.decode(bolt11); + if (r.payee !== nodeid) fail("invalid invoice"); + amount = Math.round(r.amount_msat / 1000); + r.bolt11 = bolt11; + } else { + r = await ln.invoice({ + amount_msat: amount ? `${amount + tip}sat` : "any", + label: id, + description: memo || "", + expiry: expiry || 60 * 60 * 24 * 30, + deschashonly: true, + cltv: 19, + }); + } + + hash = r.bolt11; + text = r.bolt11; + } else if (type === types.bitcoin) { + hash = await bc.getNewAddress(); + text = bip21(hash, invoice); + } else if (type === types.liquid) { + hash = await lq.getNewAddress(); + text = bip21(hash, invoice); + } else if (type === types.internal) { + hash = id; + } else { + fail("unrecognized type"); + } + + invoice = { + amount, + created: Date.now(), + currency, + hash, + id, + items, + memo, + rate, + pending: 0, + received: 0, + request_id, + memoPrompt, + prompt, + secret, + text, + tip, + type, + uid: user.id, + webhook, + }; + + if (type === "liquid") { + let { unconfidential } = await lq.getAddressInfo(hash); + await s(`invoice:${unconfidential}`, id); + } + + await s(`invoice:${hash}`, id); + await s(`invoice:${id}`, invoice); + await db.lPush(`${user.id}:invoices`, id); + + if (request_id) { + let request = await g(`request:${request_id}`); + if (request) { + let { invoice_id: prev } = request; + request.invoice_id = id; + await s(`request:${request_id}`, request); + + if (!prev) emit(request.requester_id, "invoice", invoice); + } + } + + return invoice; +}; diff --git a/lib/lightning.js b/lib/lightning.js deleted file mode 100644 index 2d44581d..00000000 --- a/lib/lightning.js +++ /dev/null @@ -1,52 +0,0 @@ -import { handleZap } from "$lib/nostr"; -import config from "$config"; -import { notify } from "$lib/notifications"; -import { callWebhook } from "$lib/webhooks"; -import ln from "$lib/ln"; -import { l, err, warn } from "$lib/logging"; -import { g, s, t, db } from "$lib/db"; -import { credit, types } from "$lib/payments"; - -export async function listenForLightning() { - let inv = await ln.waitanyinvoice((await g("pay_index")) || 0); - let { - bolt11, - description, - pay_index, - status, - amount_received_msat, - payment_preimage: preimage, - } = inv; - - await s("pay_index", pay_index); - setTimeout(listenForLightning); - - let received = Math.round(amount_received_msat / 1000); - - try { - if (!preimage) return; - - let id = await g(`invoice:${bolt11}`); - let invoice = await g(`invoice:${id}`); - if (!invoice) return warn("received lightning with no invoice", bolt11); - - if (invoice && invoice.memo) { - try { - if (JSON.parse(description).kind === 9734) await handleZap(inv); - } catch (e) { - if (!e.message.startsWith("Unexpected")) - warn("failed to handle zap", e.message); - } - } - - let p = await g(`payment:${bolt11}`); - if (typeof p === "string") p = await g(`payment:${p}`); - - if (p) return warn("already processed", bolt11); - - await credit(bolt11, received, invoice.memo, preimage, types.lightning); - } catch (e) { - console.log(e); - err("problem receiving lightning payment", e.message); - } -} diff --git a/lib/lightning.js.map b/lib/lightning.js.map new file mode 100644 index 00000000..b98e19d7 --- /dev/null +++ b/lib/lightning.js.map @@ -0,0 +1 @@ +{"version":3,"file":"lightning.js","sourceRoot":"","sources":["lightning.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,gDA0CC;AAnDD,oCAAuC;AAIvC,+CAAyB;AACzB,wCAA4C;AAC5C,8BAAsC;AACtC,0CAA8C;AAE9C,SAAsB,kBAAkB;;;;;;oBACtB,KAAA,CAAA,KAAA,YAAE,CAAA,CAAC,cAAc,CAAA;oBAAE,qBAAM,IAAA,MAAC,EAAC,WAAW,CAAC,EAAA;wBAA7C,qBAAM,cAAkB,CAAC,SAAoB,CAAC,IAAI,CAAC,EAAC,EAAA;;oBAA1D,GAAG,GAAG,SAAoD;oBAE5D,MAAM,GAMJ,GAAG,OANC,EACN,WAAW,GAKT,GAAG,YALM,EACX,SAAS,GAIP,GAAG,UAJI,EACT,MAAM,GAGJ,GAAG,OAHC,EACN,oBAAoB,GAElB,GAAG,qBAFe,EACF,QAAQ,GACxB,GAAG,iBADqB,CACpB;oBAER,qBAAM,IAAA,MAAC,EAAC,WAAW,EAAE,SAAS,CAAC,EAAA;;oBAA/B,SAA+B,CAAC;oBAChC,UAAU,CAAC,kBAAkB,CAAC,CAAC;oBAE3B,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;;;;oBAGrD,IAAI,CAAC,QAAQ;wBAAE,sBAAO;oBAEb,qBAAM,IAAA,MAAC,EAAC,kBAAW,MAAM,CAAE,CAAC,EAAA;;oBAAjC,EAAE,GAAG,SAA4B;oBACvB,qBAAM,IAAA,MAAC,EAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;;oBAAlC,OAAO,GAAG,SAAwB;oBACtC,IAAI,CAAC,OAAO;wBAAE,sBAAO,IAAA,cAAI,EAAC,oCAAoC,EAAE,MAAM,CAAC,EAAC;yBAEpE,CAAA,OAAO,IAAI,OAAO,CAAC,IAAI,CAAA,EAAvB,yBAAuB;;;;yBAEnB,CAAA,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,KAAK,IAAI,CAAA,EAArC,wBAAqC;oBAAE,qBAAM,IAAA,iBAAS,EAAC,GAAG,CAAC,EAAA;;oBAApB,SAAoB,CAAC;;;;;oBAEhE,IAAI,CAAC,GAAC,CAAC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC;wBACrC,IAAA,cAAI,EAAC,sBAAsB,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;yBAItC,qBAAM,IAAA,MAAC,EAAC,kBAAW,MAAM,CAAE,CAAC,EAAA;;oBAAhC,CAAC,GAAG,SAA4B;yBAChC,CAAA,OAAO,CAAC,KAAK,QAAQ,CAAA,EAArB,yBAAqB;oBAAM,qBAAM,IAAA,MAAC,EAAC,kBAAW,CAAC,CAAE,CAAC,EAAA;;oBAA3B,CAAC,GAAG,SAAuB,CAAC;;;oBAEvD,IAAI,CAAC;wBAAE,sBAAO,IAAA,cAAI,EAAC,mBAAmB,EAAE,MAAM,CAAC,EAAC;oBAEhD,qBAAM,IAAA,iBAAM,EAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,gBAAK,CAAC,SAAS,CAAC,EAAA;;oBAAvE,SAAuE,CAAC;;;;oBAExE,OAAO,CAAC,GAAG,CAAC,GAAC,CAAC,CAAC;oBACf,IAAA,aAAG,EAAC,qCAAqC,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;CAEzD"} \ No newline at end of file diff --git a/lib/lightning.ts b/lib/lightning.ts new file mode 100644 index 00000000..2d44581d --- /dev/null +++ b/lib/lightning.ts @@ -0,0 +1,52 @@ +import { handleZap } from "$lib/nostr"; +import config from "$config"; +import { notify } from "$lib/notifications"; +import { callWebhook } from "$lib/webhooks"; +import ln from "$lib/ln"; +import { l, err, warn } from "$lib/logging"; +import { g, s, t, db } from "$lib/db"; +import { credit, types } from "$lib/payments"; + +export async function listenForLightning() { + let inv = await ln.waitanyinvoice((await g("pay_index")) || 0); + let { + bolt11, + description, + pay_index, + status, + amount_received_msat, + payment_preimage: preimage, + } = inv; + + await s("pay_index", pay_index); + setTimeout(listenForLightning); + + let received = Math.round(amount_received_msat / 1000); + + try { + if (!preimage) return; + + let id = await g(`invoice:${bolt11}`); + let invoice = await g(`invoice:${id}`); + if (!invoice) return warn("received lightning with no invoice", bolt11); + + if (invoice && invoice.memo) { + try { + if (JSON.parse(description).kind === 9734) await handleZap(inv); + } catch (e) { + if (!e.message.startsWith("Unexpected")) + warn("failed to handle zap", e.message); + } + } + + let p = await g(`payment:${bolt11}`); + if (typeof p === "string") p = await g(`payment:${p}`); + + if (p) return warn("already processed", bolt11); + + await credit(bolt11, received, invoice.memo, preimage, types.lightning); + } catch (e) { + console.log(e); + err("problem receiving lightning payment", e.message); + } +} diff --git a/lib/liquid.js b/lib/liquid.js deleted file mode 100644 index ec8ec7cb..00000000 --- a/lib/liquid.js +++ /dev/null @@ -1,3 +0,0 @@ -import config from "$config"; -import rpc from "$lib/rpc"; -export default rpc(config.liquid); diff --git a/lib/liquid.js.map b/lib/liquid.js.map new file mode 100644 index 00000000..2c399781 --- /dev/null +++ b/lib/liquid.js.map @@ -0,0 +1 @@ +{"version":3,"file":"liquid.js","sourceRoot":"","sources":["liquid.ts"],"names":[],"mappings":";;;;;AAAA,oDAA6B;AAC7B,iDAA2B;AAC3B,kBAAe,IAAA,aAAG,EAAC,iBAAM,CAAC,MAAM,CAAC,CAAC"} \ No newline at end of file diff --git a/lib/liquid.ts b/lib/liquid.ts new file mode 100644 index 00000000..49bd838d --- /dev/null +++ b/lib/liquid.ts @@ -0,0 +1,4 @@ +import config from "$config"; +import rpc from "$lib/rpc"; +console.log("LIQ", config.liquid); +export default rpc(config.liquid); diff --git a/lib/ln.js b/lib/ln.js deleted file mode 100644 index c4445b26..00000000 --- a/lib/ln.js +++ /dev/null @@ -1,7 +0,0 @@ -import config from "$config"; -let ln; - -let { LightningClient } = (await import("clightning-client")).default; -ln = new LightningClient(config.lightning); - -export default ln; diff --git a/lib/ln.js.map b/lib/ln.js.map new file mode 100644 index 00000000..418dc9e2 --- /dev/null +++ b/lib/ln.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ln.js","sourceRoot":"","sources":["ln.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAA6B;AAC7B,IAAI,EAAE,CAAC;AAED,IAAA,eAAe,GAAK,CAAC,uEAAa,mBAAmB,MAAC,CAAC,CAAC,OAAO,gBAAhD,CAAiD;AACtE,EAAE,GAAG,IAAI,eAAe,CAAC,iBAAM,CAAC,SAAS,CAAC,CAAC;AAE3C,kBAAe,EAAE,CAAC"} \ No newline at end of file diff --git a/lib/ln.ts b/lib/ln.ts new file mode 100644 index 00000000..c4445b26 --- /dev/null +++ b/lib/ln.ts @@ -0,0 +1,7 @@ +import config from "$config"; +let ln; + +let { LightningClient } = (await import("clightning-client")).default; +ln = new LightningClient(config.lightning); + +export default ln; diff --git a/lib/locales/index.js b/lib/locales/index.js deleted file mode 100644 index 3416a31b..00000000 --- a/lib/locales/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import fs from "fs"; -export default "de,en,fa,ja,ru,el,es,fr,pt,zh".split(",").reduce((a, l) => { - a[l] = JSON.parse(fs.readFileSync(`lib/locales/${l}.json`)); - return a; -}, {}); diff --git a/lib/locales/index.js.map b/lib/locales/index.js.map new file mode 100644 index 00000000..3fe687da --- /dev/null +++ b/lib/locales/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;;;AAAA,0CAAoB;AACpB,kBAAe,+BAA+B,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,UAAC,CAAC,EAAE,CAAC;IAChE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,YAAE,CAAC,YAAY,CAAC,sBAAe,CAAC,UAAO,CAAC,CAAC,CAAC;IAC9D,OAAO,CAAC,CAAC;AACb,CAAC,EAAE,EAAE,CAAC,CAAC"} \ No newline at end of file diff --git a/lib/locales/index.ts b/lib/locales/index.ts new file mode 100644 index 00000000..3416a31b --- /dev/null +++ b/lib/locales/index.ts @@ -0,0 +1,5 @@ +import fs from "fs"; +export default "de,en,fa,ja,ru,el,es,fr,pt,zh".split(",").reduce((a, l) => { + a[l] = JSON.parse(fs.readFileSync(`lib/locales/${l}.json`)); + return a; +}, {}); diff --git a/lib/locations.js b/lib/locations.js deleted file mode 100644 index 39212d05..00000000 --- a/lib/locations.js +++ /dev/null @@ -1,40 +0,0 @@ -import fs from "fs"; -import { err } from "$lib/logging"; -import got from "got"; -import { g, s } from "$lib/db"; -import { fields, pick } from "$lib/utils"; - -export let getLocations = async () => { - try { - let locations = await got( - "https://api.btcmap.org/v2/elements?updated_since=2022-09-19T00:00:00Z", - ).json(); - - locations = locations.filter( - (l) => - l["osm_json"].tags && l["osm_json"].tags["payment:coinos"] === "yes", - ); - - locations.map((l) => { - let { bounds, lat, lon } = l["osm_json"]; - - l["osm_json"].lat = lat || (bounds.minlat + bounds.maxlat) / 2; - l["osm_json"].lon = lon || (bounds.minlon + bounds.maxlon) / 2; - }); - - for await (let l of locations) { - let username = l.tags["payment:coinos"]; - if (username) { - let uid = await g(`user:${username}`); - let user = await g(`user:${uid}`); - if (user) l["osm_json"].tags.user = pick(user, fields); - } - } - - await s("locations", locations); - } catch (e) { - err("problem fetching locations", e); - } - - setTimeout(getLocations, 60000); -}; diff --git a/lib/locations.js.map b/lib/locations.js.map new file mode 100644 index 00000000..c41e5a3c --- /dev/null +++ b/lib/locations.js.map @@ -0,0 +1 @@ +{"version":3,"file":"locations.js","sourceRoot":"","sources":["locations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,wCAAmC;AACnC,4CAAsB;AACtB,8BAA+B;AAC/B,oCAA0C;AAEnC,IAAI,YAAY,GAAG;;;;;;;gBAEN,qBAAM,IAAA,aAAG,EACvB,uEAAuE,CACxE,CAAC,IAAI,EAAE,EAAA;;gBAFJ,SAAS,GAAG,SAER;gBAER,SAAS,GAAG,SAAS,CAAC,MAAM,CAC1B,UAAC,CAAC;oBACA,OAAA,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,KAAK;gBAApE,CAAoE,CACvE,CAAC;gBAEF,SAAS,CAAC,GAAG,CAAC,UAAC,CAAC;;oBACR,IAAA,MAAM,IAAR,KAAuB,CAAC,CAAC,UAAU,CAAC,YAA5B,EAAE,GAAG,SAAA,EAAE,GAAG,SAAA,CAAmB;oBAEzC,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBAC/D,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjE,CAAC,CAAC,CAAC;;;;2BAEiB,cAAA,cAAA,SAAS,CAAA;;;;;gBAAT,yBAAS;gBAAT,WAAS;gBAAd,CAAC,KAAA,CAAA;gBACV,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;qBACpC,QAAQ,EAAR,wBAAQ;gBACA,qBAAM,IAAA,MAAC,EAAC,eAAQ,QAAQ,CAAE,CAAC,EAAA;;gBAAjC,GAAG,GAAG,SAA2B;gBAC1B,qBAAM,IAAA,MAAC,EAAC,eAAQ,GAAG,CAAE,CAAC,EAAA;;gBAA7B,IAAI,GAAG,SAAsB;gBACjC,IAAI,IAAI;oBAAE,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,IAAA,YAAI,EAAC,IAAI,EAAE,cAAM,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;qBAI3D,qBAAM,IAAA,MAAC,EAAC,WAAW,EAAE,SAAS,CAAC,EAAA;;gBAA/B,SAA+B,CAAC;;;;gBAEhC,IAAA,aAAG,EAAC,4BAA4B,EAAE,GAAC,CAAC,CAAC;;;gBAGvC,UAAU,CAAC,oBAAY,EAAE,KAAK,CAAC,CAAC;;;;KACjC,CAAC;AAjCS,QAAA,YAAY,gBAiCrB"} \ No newline at end of file diff --git a/lib/locations.ts b/lib/locations.ts new file mode 100644 index 00000000..39212d05 --- /dev/null +++ b/lib/locations.ts @@ -0,0 +1,40 @@ +import fs from "fs"; +import { err } from "$lib/logging"; +import got from "got"; +import { g, s } from "$lib/db"; +import { fields, pick } from "$lib/utils"; + +export let getLocations = async () => { + try { + let locations = await got( + "https://api.btcmap.org/v2/elements?updated_since=2022-09-19T00:00:00Z", + ).json(); + + locations = locations.filter( + (l) => + l["osm_json"].tags && l["osm_json"].tags["payment:coinos"] === "yes", + ); + + locations.map((l) => { + let { bounds, lat, lon } = l["osm_json"]; + + l["osm_json"].lat = lat || (bounds.minlat + bounds.maxlat) / 2; + l["osm_json"].lon = lon || (bounds.minlon + bounds.maxlon) / 2; + }); + + for await (let l of locations) { + let username = l.tags["payment:coinos"]; + if (username) { + let uid = await g(`user:${username}`); + let user = await g(`user:${uid}`); + if (user) l["osm_json"].tags.user = pick(user, fields); + } + } + + await s("locations", locations); + } catch (e) { + err("problem fetching locations", e); + } + + setTimeout(getLocations, 60000); +}; diff --git a/lib/logging.js b/lib/logging.js deleted file mode 100644 index 4913839a..00000000 --- a/lib/logging.js +++ /dev/null @@ -1,12 +0,0 @@ -import pino from "pino"; - -export let l = (...msgs) => pino().info(msgs.join(" ")); -export let warn = (...msgs) => pino().warn(msgs.join(" ")); -export let err = (...msgs) => pino().error(msgs.join(" ")); - -export let line = () => { - let stack = new Error().stack; - let stackLine = stack.split("\n")[1]; - let match = stackLine.match(/at\s+(.*):(\d+):(\d+)/); - return `${match[1]}:${match[2]}`; -}; diff --git a/lib/logging.js.map b/lib/logging.js.map new file mode 100644 index 00000000..d3ddf096 --- /dev/null +++ b/lib/logging.js.map @@ -0,0 +1 @@ +{"version":3,"file":"logging.js","sourceRoot":"","sources":["logging.ts"],"names":[],"mappings":";;;;;;AAAA,8CAAwB;AAEjB,IAAI,CAAC,GAAG;IAAC,cAAO;SAAP,UAAO,EAAP,qBAAO,EAAP,IAAO;QAAP,yBAAO;;IAAK,OAAA,IAAA,cAAI,GAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAA3B,CAA2B,CAAC;AAA7C,QAAA,CAAC,KAA4C;AACjD,IAAI,IAAI,GAAG;IAAC,cAAO;SAAP,UAAO,EAAP,qBAAO,EAAP,IAAO;QAAP,yBAAO;;IAAK,OAAA,IAAA,cAAI,GAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAA3B,CAA2B,CAAC;AAAhD,QAAA,IAAI,QAA4C;AACpD,IAAI,GAAG,GAAG;IAAC,cAAO;SAAP,UAAO,EAAP,qBAAO,EAAP,IAAO;QAAP,yBAAO;;IAAK,OAAA,IAAA,cAAI,GAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAA5B,CAA4B,CAAC;AAAhD,QAAA,GAAG,OAA6C;AAEpD,IAAI,IAAI,GAAG;IAChB,IAAI,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC,KAAK,CAAC;IAC9B,IAAI,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,IAAI,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IACrD,OAAO,UAAG,KAAK,CAAC,CAAC,CAAC,cAAI,KAAK,CAAC,CAAC,CAAC,CAAE,CAAC;AACnC,CAAC,CAAC;AALS,QAAA,IAAI,QAKb"} \ No newline at end of file diff --git a/lib/logging.ts b/lib/logging.ts new file mode 100644 index 00000000..4913839a --- /dev/null +++ b/lib/logging.ts @@ -0,0 +1,12 @@ +import pino from "pino"; + +export let l = (...msgs) => pino().info(msgs.join(" ")); +export let warn = (...msgs) => pino().warn(msgs.join(" ")); +export let err = (...msgs) => pino().error(msgs.join(" ")); + +export let line = () => { + let stack = new Error().stack; + let stackLine = stack.split("\n")[1]; + let match = stackLine.match(/at\s+(.*):(\d+):(\d+)/); + return `${match[1]}:${match[2]}`; +}; diff --git a/lib/mail.js b/lib/mail.js deleted file mode 100644 index 16b3dbbf..00000000 --- a/lib/mail.js +++ /dev/null @@ -1,44 +0,0 @@ -import { l, err, warn } from "$lib/logging"; -import config from "$config"; -import { SESClient } from "@aws-sdk/client-ses"; -import { SendEmailCommand } from "@aws-sdk/client-ses"; -import handlebars from "handlebars"; -import fs from "fs"; -import path from "path"; - -let Charset = "UTF-8"; - -export let templates = { - verifyEmail: "templates/payments/verify.html", - paymentReceived: "templates/payments/received.html", - passwordReset: "templates/payments/reset.html", -}; - -export let mail = async (user, subject, template, params) => { - try { - l("sending mail", user.username, subject); - if (!user.email) return; - - let source = fs.readFileSync(template, "utf8"); - let html = handlebars.compile(source)(params); - - let client = new SESClient({ region: "us-east-2" }); - - await client.send( - new SendEmailCommand({ - Destination: { - ToAddresses: [user.email], - }, - Message: { - Body: { - Html: { Charset, Data: html }, - }, - Subject: { Charset, Data: subject }, - }, - Source: `"Coinos " <${config.support}>`, - }), - ); - } catch (e) { - err("failed to send email", e.message); - } -}; diff --git a/lib/mail.js.map b/lib/mail.js.map new file mode 100644 index 00000000..afc37431 --- /dev/null +++ b/lib/mail.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mail.js","sourceRoot":"","sources":["mail.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wCAA4C;AAC5C,oDAA6B;AAC7B,kDAAgD;AAChD,kDAAuD;AACvD,0DAAoC;AACpC,0CAAoB;AAGpB,IAAI,OAAO,GAAG,OAAO,CAAC;AAEX,QAAA,SAAS,GAAG;IACrB,WAAW,EAAE,gCAAgC;IAC7C,eAAe,EAAE,kCAAkC;IACnD,aAAa,EAAE,+BAA+B;CAC/C,CAAC;AAEK,IAAI,IAAI,GAAG,UAAO,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM;;;;;;gBAEpD,IAAA,WAAC,EAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAC1C,IAAI,CAAC,IAAI,CAAC,KAAK;oBAAE,sBAAO;gBAEpB,MAAM,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBAC3C,IAAI,GAAG,oBAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;gBAE1C,MAAM,GAAG,IAAI,sBAAS,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;gBAEpD,qBAAM,MAAM,CAAC,IAAI,CACf,IAAI,6BAAgB,CAAC;wBACnB,WAAW,EAAE;4BACX,WAAW,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;yBAC1B;wBACD,OAAO,EAAE;4BACP,IAAI,EAAE;gCACJ,IAAI,EAAE,EAAE,OAAO,SAAA,EAAE,IAAI,EAAE,IAAI,EAAE;6BAC9B;4BACD,OAAO,EAAE,EAAE,OAAO,SAAA,EAAE,IAAI,EAAE,OAAO,EAAE;yBACpC;wBACD,MAAM,EAAE,uBAAc,iBAAM,CAAC,OAAO,MAAG;qBACxC,CAAC,CACH,EAAA;;gBAbD,SAaC,CAAC;;;;gBAEF,IAAA,aAAG,EAAC,sBAAsB,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;KAE1C,CAAC;AA3BS,QAAA,IAAI,QA2Bb"} \ No newline at end of file diff --git a/lib/mail.ts b/lib/mail.ts new file mode 100644 index 00000000..16b3dbbf --- /dev/null +++ b/lib/mail.ts @@ -0,0 +1,44 @@ +import { l, err, warn } from "$lib/logging"; +import config from "$config"; +import { SESClient } from "@aws-sdk/client-ses"; +import { SendEmailCommand } from "@aws-sdk/client-ses"; +import handlebars from "handlebars"; +import fs from "fs"; +import path from "path"; + +let Charset = "UTF-8"; + +export let templates = { + verifyEmail: "templates/payments/verify.html", + paymentReceived: "templates/payments/received.html", + passwordReset: "templates/payments/reset.html", +}; + +export let mail = async (user, subject, template, params) => { + try { + l("sending mail", user.username, subject); + if (!user.email) return; + + let source = fs.readFileSync(template, "utf8"); + let html = handlebars.compile(source)(params); + + let client = new SESClient({ region: "us-east-2" }); + + await client.send( + new SendEmailCommand({ + Destination: { + ToAddresses: [user.email], + }, + Message: { + Body: { + Html: { Charset, Data: html }, + }, + Subject: { Charset, Data: subject }, + }, + Source: `"Coinos " <${config.support}>`, + }), + ); + } catch (e) { + err("failed to send email", e.message); + } +}; diff --git a/lib/migrate.js b/lib/migrate.js deleted file mode 100644 index cee5e946..00000000 --- a/lib/migrate.js +++ /dev/null @@ -1,134 +0,0 @@ -import { err, warn, l, line } from "$lib/logging"; -import { types } from "$lib/payments"; -import { archive, arc2, db, g, ga, s } from "$lib/db"; -import { wait } from "$lib/utils"; - -let queued = {}; -let migrating = {}; - -async function migrate(id) { - try { - delete queued[id]; - if (id) id = id.replace(/\s/g, "").toLowerCase(); - - let user = await g(`user:${id}`); - if (typeof user === "string") user = await g(`user:${user}`); - - if (user) return user; - - user = await ga(`user:${id}`); - - if (typeof user === "string") { - if (await g(`user:${user}`)) { - warn("user already migrated", id); - s(`user:${id}`, user); - } - - id = user; - user = await ga(`user:${id}`); - } - - if (user) { - if (migrating[id]) return; - migrating[id] = true; - - let { username } = user; - username = username.replace(/\s/g, "").toLowerCase(); - l("migrating", username); - - try { - let multi = await db - .multi() - .set(`user:${username}`, id) - .set(`user:${user.pubkey}`, id) - .set(`user:${id}`, JSON.stringify(user)); - - await multi.exec(); - - await archive.del(`user:${username}`); - await archive.del(`user:${user.pubkey}`); - await archive.del(`user:${id}`); - - await s(`${id}:contacts`, (await ga(`${id}:contacts`)) || []); - await archive.del(`${id}:contacts`); - - await s( - `credit:bitcoin:${id}`, - (await ga(`credit:bitcoin:${id}`)) || 0, - ); - await archive.del(`credit:bitcoin:${id}`); - - await s( - `credit:lightning:${id}`, - (await ga(`credit:lightning:${id}`)) || 0, - ); - await archive.del(`credit:lightning:${id}`); - - await s(`${id}:lastlen`, (await ga(`${id}:lastlen`)) || 0); - await archive.del(`${id}:lastlen`); - await s(`${id}:cindex`, (await ga(`${id}:cindex`)) || 0); - await archive.del(`${id}:cindex`); - await s(`balance:${id}`, (await ga(`balance:${id}`)) || 0); - await archive.del(`balance:${id}`); - await s(`pending:${id}`, (await ga(`pending:${id}`)) || 0); - await archive.del(`pending:${id}`); - - let pid; - while ((pid = await archive.rPop(`${id}:payments`))) { - let p = await ga(`payment:${pid}`); - let pos = await db.lPos(`${id}:payments`, pid); - if (!p || pos) continue; - if (p.type === types.internal && p.ref) { - let recipient = await g(`user:${p.ref}`); - if (!recipient) queued[p.ref] = true; - } - - await db.lPush(`${id}:payments`, pid); - await s(`payment:${pid}`, p); - - await archive.del(`payment:${pid}`); - } - await archive.del(`${id}:payments`); - - let iid; - while ((iid = await archive.rPop(`${id}:invoices`))) { - let inv = await ga(`invoice:${iid}`); - let pos = await db.lPos(`${id}:invoices`, iid); - if (!inv || pos) continue; - await db.lPush(`${id}:invoices`, iid); - await s(`invoice:${iid}`, inv); - - await archive.del(`invoice:${iid}`); - } - await archive.del(`${id}:invoices`); - - l("done migrating", username); - } catch (e) { - warn("failed to migrate", username, e.message, line()); - } - - delete migrating[id]; - return user; - } - } catch (e) { - delete migrating[id]; - throw e; - } -} - -let keepMigrating = async () => { - try { - if (Object.keys(queued).length) { - let next = Object.keys(queued)[0]; - await migrate(next); - } - } catch (e) { - err("problem migrating", e.message); - } - - setTimeout(keepMigrating, 1000); -}; - -keepMigrating(); - -export default migrate; diff --git a/lib/migrate.js.map b/lib/migrate.js.map new file mode 100644 index 00000000..7e905ae3 --- /dev/null +++ b/lib/migrate.js.map @@ -0,0 +1 @@ +{"version":3,"file":"migrate.js","sourceRoot":"","sources":["migrate.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wCAAkD;AAClD,0CAAsC;AACtC,8BAAsD;AAGtD,IAAI,MAAM,GAAG,EAAE,CAAC;AAChB,IAAI,SAAS,GAAG,EAAE,CAAC;AAEnB,SAAe,OAAO,CAAC,EAAE;;;;;;;oBAErB,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC;oBAClB,IAAI,EAAE;wBAAE,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;oBAEtC,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;oBAA5B,IAAI,GAAG,SAAqB;yBAC5B,CAAA,OAAO,IAAI,KAAK,QAAQ,CAAA,EAAxB,wBAAwB;oBAAS,qBAAM,IAAA,MAAC,EAAC,eAAQ,IAAI,CAAE,CAAC,EAAA;;oBAA9B,IAAI,GAAG,SAAuB,CAAC;;;oBAE7D,IAAI,IAAI;wBAAE,sBAAO,IAAI,EAAC;oBAEf,qBAAM,IAAA,OAAE,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;oBAA7B,IAAI,GAAG,SAAsB,CAAC;yBAE1B,CAAA,OAAO,IAAI,KAAK,QAAQ,CAAA,EAAxB,wBAAwB;oBACtB,qBAAM,IAAA,MAAC,EAAC,eAAQ,IAAI,CAAE,CAAC,EAAA;;oBAA3B,IAAI,SAAuB,EAAE,CAAC;wBAC5B,IAAA,cAAI,EAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;wBAClC,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,CAAC;oBACxB,CAAC;oBAED,EAAE,GAAG,IAAI,CAAC;oBACH,qBAAM,IAAA,OAAE,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;oBAA7B,IAAI,GAAG,SAAsB,CAAC;;;yBAG5B,IAAI,EAAJ,yBAAI;oBACN,IAAI,SAAS,CAAC,EAAE,CAAC;wBAAE,sBAAO;oBAC1B,SAAS,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;oBAEf,QAAQ,GAAK,IAAI,SAAT,CAAU;oBACxB,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;oBACrD,IAAA,WAAC,EAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;;;;oBAGX,qBAAM,OAAE;6BACjB,KAAK,EAAE;6BACP,GAAG,CAAC,eAAQ,QAAQ,CAAE,EAAE,EAAE,CAAC;6BAC3B,GAAG,CAAC,eAAQ,IAAI,CAAC,MAAM,CAAE,EAAE,EAAE,CAAC;6BAC9B,GAAG,CAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAA;;oBAJtC,KAAK,GAAG,SAI8B;oBAE1C,qBAAM,KAAK,CAAC,IAAI,EAAE,EAAA;;oBAAlB,SAAkB,CAAC;oBAEnB,qBAAM,YAAO,CAAC,GAAG,CAAC,eAAQ,QAAQ,CAAE,CAAC,EAAA;;oBAArC,SAAqC,CAAC;oBACtC,qBAAM,YAAO,CAAC,GAAG,CAAC,eAAQ,IAAI,CAAC,MAAM,CAAE,CAAC,EAAA;;oBAAxC,SAAwC,CAAC;oBACzC,qBAAM,YAAO,CAAC,GAAG,CAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;oBAA/B,SAA+B,CAAC;oBAE1B,KAAA,MAAC,CAAA;0BAAC,UAAG,EAAE,cAAW;oBAAG,qBAAM,IAAA,OAAE,EAAC,UAAG,EAAE,cAAW,CAAC,EAAA;yBAArD,qBAAM,4BAAoB,CAAC,SAA0B,CAAC,IAAI,EAAE,GAAC,EAAA;;oBAA7D,SAA6D,CAAC;oBAC9D,qBAAM,YAAO,CAAC,GAAG,CAAC,UAAG,EAAE,cAAW,CAAC,EAAA;;oBAAnC,SAAmC,CAAC;oBAE9B,KAAA,MAAC,CAAA;0BACL,yBAAkB,EAAE,CAAE;oBACrB,qBAAM,IAAA,OAAE,EAAC,yBAAkB,EAAE,CAAE,CAAC,EAAA;yBAFnC,qBAAM,4BAEJ,CAAC,SAAgC,CAAC,IAAI,CAAC,GACxC,EAAA;;oBAHD,SAGC,CAAC;oBACF,qBAAM,YAAO,CAAC,GAAG,CAAC,yBAAkB,EAAE,CAAE,CAAC,EAAA;;oBAAzC,SAAyC,CAAC;oBAEpC,KAAA,MAAC,CAAA;0BACL,2BAAoB,EAAE,CAAE;oBACvB,qBAAM,IAAA,OAAE,EAAC,2BAAoB,EAAE,CAAE,CAAC,EAAA;yBAFrC,qBAAM,4BAEJ,CAAC,SAAkC,CAAC,IAAI,CAAC,GAC1C,EAAA;;oBAHD,SAGC,CAAC;oBACF,qBAAM,YAAO,CAAC,GAAG,CAAC,2BAAoB,EAAE,CAAE,CAAC,EAAA;;oBAA3C,SAA2C,CAAC;oBAEtC,KAAA,MAAC,CAAA;0BAAC,UAAG,EAAE,aAAU;oBAAG,qBAAM,IAAA,OAAE,EAAC,UAAG,EAAE,aAAU,CAAC,EAAA;yBAAnD,qBAAM,4BAAmB,CAAC,SAAyB,CAAC,IAAI,CAAC,GAAC,EAAA;;oBAA1D,SAA0D,CAAC;oBAC3D,qBAAM,YAAO,CAAC,GAAG,CAAC,UAAG,EAAE,aAAU,CAAC,EAAA;;oBAAlC,SAAkC,CAAC;oBAC7B,KAAA,MAAC,CAAA;0BAAC,UAAG,EAAE,YAAS;oBAAG,qBAAM,IAAA,OAAE,EAAC,UAAG,EAAE,YAAS,CAAC,EAAA;yBAAjD,qBAAM,4BAAkB,CAAC,SAAwB,CAAC,IAAI,CAAC,GAAC,EAAA;;oBAAxD,SAAwD,CAAC;oBACzD,qBAAM,YAAO,CAAC,GAAG,CAAC,UAAG,EAAE,YAAS,CAAC,EAAA;;oBAAjC,SAAiC,CAAC;oBAC5B,KAAA,MAAC,CAAA;0BAAC,kBAAW,EAAE,CAAE;oBAAG,qBAAM,IAAA,OAAE,EAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;yBAAnD,qBAAM,4BAAmB,CAAC,SAAyB,CAAC,IAAI,CAAC,GAAC,EAAA;;oBAA1D,SAA0D,CAAC;oBAC3D,qBAAM,YAAO,CAAC,GAAG,CAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;;oBAAlC,SAAkC,CAAC;oBAC7B,KAAA,MAAC,CAAA;0BAAC,kBAAW,EAAE,CAAE;oBAAG,qBAAM,IAAA,OAAE,EAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;yBAAnD,qBAAM,4BAAmB,CAAC,SAAyB,CAAC,IAAI,CAAC,GAAC,EAAA;;oBAA1D,SAA0D,CAAC;oBAC3D,qBAAM,YAAO,CAAC,GAAG,CAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;;oBAAlC,SAAkC,CAAC;oBAE/B,GAAG,SAAA,CAAC;;yBACM,qBAAM,YAAO,CAAC,IAAI,CAAC,UAAG,EAAE,cAAW,CAAC,EAAA;;yBAA3C,CAAC,GAAG,GAAG,SAAoC,CAAC;oBACzC,qBAAM,IAAA,OAAE,EAAC,kBAAW,GAAG,CAAE,CAAC,EAAA;;oBAA9B,CAAC,GAAG,SAA0B;oBACxB,qBAAM,OAAE,CAAC,IAAI,CAAC,UAAG,EAAE,cAAW,EAAE,GAAG,CAAC,EAAA;;oBAA1C,GAAG,GAAG,SAAoC;oBAC9C,IAAI,CAAC,CAAC,IAAI,GAAG;wBAAE,yBAAS;yBACpB,CAAA,CAAC,CAAC,IAAI,KAAK,gBAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAA,EAAlC,yBAAkC;oBACpB,qBAAM,IAAA,MAAC,EAAC,eAAQ,CAAC,CAAC,GAAG,CAAE,CAAC,EAAA;;oBAApC,SAAS,GAAG,SAAwB;oBACxC,IAAI,CAAC,SAAS;wBAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;;yBAGvC,qBAAM,OAAE,CAAC,KAAK,CAAC,UAAG,EAAE,cAAW,EAAE,GAAG,CAAC,EAAA;;oBAArC,SAAqC,CAAC;oBACtC,qBAAM,IAAA,MAAC,EAAC,kBAAW,GAAG,CAAE,EAAE,CAAC,CAAC,EAAA;;oBAA5B,SAA4B,CAAC;oBAE7B,qBAAM,YAAO,CAAC,GAAG,CAAC,kBAAW,GAAG,CAAE,CAAC,EAAA;;oBAAnC,SAAmC,CAAC;;yBAEtC,qBAAM,YAAO,CAAC,GAAG,CAAC,UAAG,EAAE,cAAW,CAAC,EAAA;;oBAAnC,SAAmC,CAAC;oBAEhC,GAAG,SAAA,CAAC;;yBACM,qBAAM,YAAO,CAAC,IAAI,CAAC,UAAG,EAAE,cAAW,CAAC,EAAA;;yBAA3C,CAAC,GAAG,GAAG,SAAoC,CAAC;oBACvC,qBAAM,IAAA,OAAE,EAAC,kBAAW,GAAG,CAAE,CAAC,EAAA;;oBAAhC,GAAG,GAAG,SAA0B;oBAC1B,qBAAM,OAAE,CAAC,IAAI,CAAC,UAAG,EAAE,cAAW,EAAE,GAAG,CAAC,EAAA;;oBAA1C,GAAG,GAAG,SAAoC;oBAC9C,IAAI,CAAC,GAAG,IAAI,GAAG;wBAAE,yBAAS;oBAC1B,qBAAM,OAAE,CAAC,KAAK,CAAC,UAAG,EAAE,cAAW,EAAE,GAAG,CAAC,EAAA;;oBAArC,SAAqC,CAAC;oBACtC,qBAAM,IAAA,MAAC,EAAC,kBAAW,GAAG,CAAE,EAAE,GAAG,CAAC,EAAA;;oBAA9B,SAA8B,CAAC;oBAE/B,qBAAM,YAAO,CAAC,GAAG,CAAC,kBAAW,GAAG,CAAE,CAAC,EAAA;;oBAAnC,SAAmC,CAAC;;yBAEtC,qBAAM,YAAO,CAAC,GAAG,CAAC,UAAG,EAAE,cAAW,CAAC,EAAA;;oBAAnC,SAAmC,CAAC;oBAEpC,IAAA,WAAC,EAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;;;;oBAE9B,IAAA,cAAI,EAAC,mBAAmB,EAAE,QAAQ,EAAE,GAAC,CAAC,OAAO,EAAE,IAAA,cAAI,GAAE,CAAC,CAAC;;;oBAGzD,OAAO,SAAS,CAAC,EAAE,CAAC,CAAC;oBACrB,sBAAO,IAAI,EAAC;;;;oBAGd,OAAO,SAAS,CAAC,EAAE,CAAC,CAAC;oBACrB,MAAM,GAAC,CAAC;;;;;CAEX;AAED,IAAI,aAAa,GAAG;;;;;;qBAEZ,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,EAA1B,wBAA0B;gBACxB,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAClC,qBAAM,OAAO,CAAC,IAAI,CAAC,EAAA;;gBAAnB,SAAmB,CAAC;;;;;gBAGtB,IAAA,aAAG,EAAC,mBAAmB,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;gBAGtC,UAAU,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;;;;KACjC,CAAC;AAEF,aAAa,EAAE,CAAC;AAEhB,kBAAe,OAAO,CAAC"} \ No newline at end of file diff --git a/lib/migrate.ts b/lib/migrate.ts new file mode 100644 index 00000000..cee5e946 --- /dev/null +++ b/lib/migrate.ts @@ -0,0 +1,134 @@ +import { err, warn, l, line } from "$lib/logging"; +import { types } from "$lib/payments"; +import { archive, arc2, db, g, ga, s } from "$lib/db"; +import { wait } from "$lib/utils"; + +let queued = {}; +let migrating = {}; + +async function migrate(id) { + try { + delete queued[id]; + if (id) id = id.replace(/\s/g, "").toLowerCase(); + + let user = await g(`user:${id}`); + if (typeof user === "string") user = await g(`user:${user}`); + + if (user) return user; + + user = await ga(`user:${id}`); + + if (typeof user === "string") { + if (await g(`user:${user}`)) { + warn("user already migrated", id); + s(`user:${id}`, user); + } + + id = user; + user = await ga(`user:${id}`); + } + + if (user) { + if (migrating[id]) return; + migrating[id] = true; + + let { username } = user; + username = username.replace(/\s/g, "").toLowerCase(); + l("migrating", username); + + try { + let multi = await db + .multi() + .set(`user:${username}`, id) + .set(`user:${user.pubkey}`, id) + .set(`user:${id}`, JSON.stringify(user)); + + await multi.exec(); + + await archive.del(`user:${username}`); + await archive.del(`user:${user.pubkey}`); + await archive.del(`user:${id}`); + + await s(`${id}:contacts`, (await ga(`${id}:contacts`)) || []); + await archive.del(`${id}:contacts`); + + await s( + `credit:bitcoin:${id}`, + (await ga(`credit:bitcoin:${id}`)) || 0, + ); + await archive.del(`credit:bitcoin:${id}`); + + await s( + `credit:lightning:${id}`, + (await ga(`credit:lightning:${id}`)) || 0, + ); + await archive.del(`credit:lightning:${id}`); + + await s(`${id}:lastlen`, (await ga(`${id}:lastlen`)) || 0); + await archive.del(`${id}:lastlen`); + await s(`${id}:cindex`, (await ga(`${id}:cindex`)) || 0); + await archive.del(`${id}:cindex`); + await s(`balance:${id}`, (await ga(`balance:${id}`)) || 0); + await archive.del(`balance:${id}`); + await s(`pending:${id}`, (await ga(`pending:${id}`)) || 0); + await archive.del(`pending:${id}`); + + let pid; + while ((pid = await archive.rPop(`${id}:payments`))) { + let p = await ga(`payment:${pid}`); + let pos = await db.lPos(`${id}:payments`, pid); + if (!p || pos) continue; + if (p.type === types.internal && p.ref) { + let recipient = await g(`user:${p.ref}`); + if (!recipient) queued[p.ref] = true; + } + + await db.lPush(`${id}:payments`, pid); + await s(`payment:${pid}`, p); + + await archive.del(`payment:${pid}`); + } + await archive.del(`${id}:payments`); + + let iid; + while ((iid = await archive.rPop(`${id}:invoices`))) { + let inv = await ga(`invoice:${iid}`); + let pos = await db.lPos(`${id}:invoices`, iid); + if (!inv || pos) continue; + await db.lPush(`${id}:invoices`, iid); + await s(`invoice:${iid}`, inv); + + await archive.del(`invoice:${iid}`); + } + await archive.del(`${id}:invoices`); + + l("done migrating", username); + } catch (e) { + warn("failed to migrate", username, e.message, line()); + } + + delete migrating[id]; + return user; + } + } catch (e) { + delete migrating[id]; + throw e; + } +} + +let keepMigrating = async () => { + try { + if (Object.keys(queued).length) { + let next = Object.keys(queued)[0]; + await migrate(next); + } + } catch (e) { + err("problem migrating", e.message); + } + + setTimeout(keepMigrating, 1000); +}; + +keepMigrating(); + +export default migrate; diff --git a/lib/mqtt.js b/lib/mqtt.js deleted file mode 100644 index 636dc3e1..00000000 --- a/lib/mqtt.js +++ /dev/null @@ -1,5 +0,0 @@ -import mqtt from "mqtt"; -import config from "$config"; - -export let mqtt1 = mqtt.connect("mqtt://ln.coinos.io", config.mqtt1); -export let mqtt2 = mqtt.connect("mqtt://mqtt.coinos.io", config.mqtt2); diff --git a/lib/mqtt.js.map b/lib/mqtt.js.map new file mode 100644 index 00000000..d4a56ea2 --- /dev/null +++ b/lib/mqtt.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mqtt.js","sourceRoot":"","sources":["mqtt.ts"],"names":[],"mappings":";;;;;;AAAA,8CAAwB;AACxB,oDAA6B;AAElB,QAAA,KAAK,GAAG,cAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,iBAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,QAAA,KAAK,GAAG,cAAI,CAAC,OAAO,CAAC,uBAAuB,EAAE,iBAAM,CAAC,KAAK,CAAC,CAAC"} \ No newline at end of file diff --git a/lib/mqtt.ts b/lib/mqtt.ts new file mode 100644 index 00000000..636dc3e1 --- /dev/null +++ b/lib/mqtt.ts @@ -0,0 +1,5 @@ +import mqtt from "mqtt"; +import config from "$config"; + +export let mqtt1 = mqtt.connect("mqtt://ln.coinos.io", config.mqtt1); +export let mqtt2 = mqtt.connect("mqtt://mqtt.coinos.io", config.mqtt2); diff --git a/lib/nostr.js b/lib/nostr.js deleted file mode 100644 index ccfb4e54..00000000 --- a/lib/nostr.js +++ /dev/null @@ -1,280 +0,0 @@ -import config from "$config"; -import { l } from "$lib/logging"; -import { Relay, RelayPool, calculateId, signId, getPublicKey } from "nostr"; -import { broadcast, emit } from "$lib/sockets"; -import store from "$lib/store"; -import { g, s, db } from "$lib/db"; -import { fail, nada, wait } from "$lib/utils"; -import { nip19 } from "nostr-tools"; - -export const COINOS_PUBKEY = getPublicKey(nip19.decode(config.nostrKey).data); -export let coinos; -export let pool; - -export let fillPool = () => { - try { - pool = RelayPool(config.relays); - pool.on("open", (relay) => { - if (relay.url.includes(config.nostr)) coinos = relay; - relay.subscribe("live", { limit: 1 }); - }); - - pool.on("event", async (relay, sub, ev) => { - if (!coinos) return; - if (timeouts[sub]) timeouts[sub].extend(ev); - try { - let content; - try { - content = JSON.parse(ev.content); - } catch (e) { - content = ev.content; - } - - if (sub === "live") { - let { pubkey } = ev; - - if (Math.abs(Math.floor(Date.now() / 1000) - ev.created_at) > 7200) - return; - - if (seen.includes(ev.id)) return; - seen.push(ev.id); - seen.length > 1000 && seen.shift(); - - if (coinos && ev.kind < 5) { - // coinos.send(["EVENT", ev]); - ev.user = await getUser(pubkey); - // broadcast("event", ev); - - if (ev.kind === 4) { - let uid = await g(`user:${ev.tags[0][1]}`); - if (uid) ev.recipient = await g(`user:${uid}`); - ev.author = ev.user; - emit(uid, "event", ev); - } - } - } else if (sub.includes("profile")) { - let { pubkey } = ev; - let user = await getUser(pubkey); - - if (user.updated > ev.created_at) return; - - if (content.name && user.username === pubkey.substr(0, 6)) - user.username = content.name; - - delete content.name; - - user = { - ...user, - ...content, - updated: ev.created_at, - }; - - await s(`user:${pubkey}`, JSON.stringify(user)); - } else if (sub.includes("messages")) { - let pubkey = sub.split(":")[0]; - await db.sAdd(`${pubkey}:messages`, ev.id); - await s(`ev:${ev.id}`, ev); - } else if (sub.includes("follows")) { - let pubkey = sub.split(":")[0]; - await s(`${pubkey}:follows`, ev.tags); - for (let f of ev.tags) { - let [_, followPubkey] = f; - await db.sAdd(`${followPubkey}:followers`, pubkey); - } - coinos.send(ev); - } else if (sub.includes("followers")) { - let followed = sub.split(":")[0]; - let { pubkey } = ev; - await db.sAdd(`${followed}:followers`, pubkey); - coinos.send(ev); - } else if (sub.includes("notes")) { - let pubkey = sub.split(":")[0]; - await db.sAdd(pubkey, ev.id); - await s(`ev:${ev.id}`, ev); - } - } catch (e) { - console.log(e); - } - }); - } catch(e) { - console.log("POOL", e) - } -}; - -let now = () => Math.round(Date.now() / 1000); - -let timeouts = {}; -export let q = async ( - sub, - query, - { timeout = 20000, since = 3600, eager = 200 }, -) => - new Promise(async (r, j) => { - let start = Date.now(); - let seen = []; - let rejected; - // query.since = await g(`since:${sub}`); - // if (now() - query.since < since) return r(); - - let done = { [sub]: [] }; - - let check = (s) => { - let elapsed = Date.now() - start; - if (rejected) return true; - if ( - done[s] && - (done[s].length === pool.relays.length || - (done[s].length && elapsed > eager)) - ) { - r(); - if (timeouts[s]) timeouts[s].clear(); - return true; - } - }; - - timeouts[sub] = { - async clear() { - clearTimeout(this.timer); - delete timeouts[sub]; - pool.unsubscribe(sub); - await s(`since:${sub}`, now()); - }, - extend(ev) { - if (ev) { - if (seen.includes(ev.id)) return; - seen.push(ev.id); - seen.length > 1000 && seen.shift(); - } - - clearTimeout(this.timer); - this.timer = setTimeout(() => { - j(new Error(`query timed out: ${sub} ${Date.now()}`)); - pool.unsubscribe(sub); - rejected = true; - }, timeout); - }, - timer: undefined, - }; - - timeouts[sub].extend(); - pool.subscribe(sub, query); - - pool.on("eose", (relay, s) => { - if (done[s]) done[s].push(relay.url); - check(s); - }); - - try { - await wait(() => check(sub), 100, 200); - } catch (e) { - console.log(e); - } - }); - -let seen = []; - -let getUser = async (pubkey) => { - let id = await g(`user:${pubkey}`); - let user = await g(`user:${id}`); - return ( - user || { - username: pubkey.substr(0, 6), - pubkey, - anon: true, - } - ); -}; - -function send(ev, url, opts) { - let timeout = (opts && opts.timeout != null && opts.timeout) || 1000; - - return new Promise((resolve, reject) => { - let relay = Relay(url); - - function timeout_reached() { - relay.close(); - reject(new Error("Request timeout")); - } - - let timer = setTimeout(timeout_reached, timeout); - - relay.on("open", () => { - clearTimeout(timer); - timer = setTimeout(timeout_reached, timeout); - relay.send(["EVENT", ev]); - }); - - relay.on("ok", (evid, ok, msg) => { - clearTimeout(timer); - relay.close(); - resolve({ evid, ok, msg }); - }); - }); -} - -export async function handleZap(invoice) { - let { data: privkey } = nip19.decode(config.nostrKey); - let pubkey = getPublicKey(privkey); - let keypair = { privkey, pubkey }; - let zapreq = JSON.parse(invoice.description); - - if (!zapreq.tags || zapreq.tags.length === 0) { - fail(`No tags found`); - } - - let ptags = zapreq.tags.filter( - (t) => t && t.length && t.length >= 2 && t[0] === "p", - ); - - if (ptags.length !== 1) { - fail(`None or multiple p tags found`); - } - - let etags = zapreq.tags.filter( - (t) => t && t.length && t.length >= 2 && t[0] === "e", - ); - - if (!(etags.length === 0 || etags.length === 1)) { - fail(`Expected none or 1 e tags`); - } - - let relays_tag = zapreq.tags.find( - (t) => t && t.length && t.length >= 2 && t[0] === "relays", - ); - - if (!relays_tag) { - fail(`No relays tag found`); - } - - let relays = relays_tag.slice(1).filter((r) => r && r.startsWith("ws")); - let etag = etags.length > 0 && etags[0]; - let ptag = ptags[0]; - - let kind = 9735; - let created_at = invoice.paid_at; - let content = zapreq.content; - - let tags = [ptag]; - if (etag) tags.push(etag); - - tags.push(["bolt11", invoice.bolt11]); - tags.push(["description", invoice.description]); - tags.push(["preimage", invoice.payment_preimage]); - - let ev = { pubkey, kind, created_at, content, tags }; - ev.id = await calculateId(ev); - ev.sig = await signId(privkey, ev.id); - - l("sending receipt"); - - await Promise.allSettled(relays.map((r) => send(ev, r))); -} - -// let damus = new Relay("ws://strfry:7777"); -// damus.on("open", (relay) => { -// damus.subscribe("nwc", { limit: 1, kinds: [23194] }); -// }); -// -// damus.on("event", async (sub, ev) => { -// console.log("EV", ev); -// }); diff --git a/lib/nostr.js.map b/lib/nostr.js.map new file mode 100644 index 00000000..46c97623 --- /dev/null +++ b/lib/nostr.js.map @@ -0,0 +1 @@ +{"version":3,"file":"nostr.js","sourceRoot":"","sources":["nostr.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/nostr.ts b/lib/nostr.ts new file mode 100644 index 00000000..ccfb4e54 --- /dev/null +++ b/lib/nostr.ts @@ -0,0 +1,280 @@ +import config from "$config"; +import { l } from "$lib/logging"; +import { Relay, RelayPool, calculateId, signId, getPublicKey } from "nostr"; +import { broadcast, emit } from "$lib/sockets"; +import store from "$lib/store"; +import { g, s, db } from "$lib/db"; +import { fail, nada, wait } from "$lib/utils"; +import { nip19 } from "nostr-tools"; + +export const COINOS_PUBKEY = getPublicKey(nip19.decode(config.nostrKey).data); +export let coinos; +export let pool; + +export let fillPool = () => { + try { + pool = RelayPool(config.relays); + pool.on("open", (relay) => { + if (relay.url.includes(config.nostr)) coinos = relay; + relay.subscribe("live", { limit: 1 }); + }); + + pool.on("event", async (relay, sub, ev) => { + if (!coinos) return; + if (timeouts[sub]) timeouts[sub].extend(ev); + try { + let content; + try { + content = JSON.parse(ev.content); + } catch (e) { + content = ev.content; + } + + if (sub === "live") { + let { pubkey } = ev; + + if (Math.abs(Math.floor(Date.now() / 1000) - ev.created_at) > 7200) + return; + + if (seen.includes(ev.id)) return; + seen.push(ev.id); + seen.length > 1000 && seen.shift(); + + if (coinos && ev.kind < 5) { + // coinos.send(["EVENT", ev]); + ev.user = await getUser(pubkey); + // broadcast("event", ev); + + if (ev.kind === 4) { + let uid = await g(`user:${ev.tags[0][1]}`); + if (uid) ev.recipient = await g(`user:${uid}`); + ev.author = ev.user; + emit(uid, "event", ev); + } + } + } else if (sub.includes("profile")) { + let { pubkey } = ev; + let user = await getUser(pubkey); + + if (user.updated > ev.created_at) return; + + if (content.name && user.username === pubkey.substr(0, 6)) + user.username = content.name; + + delete content.name; + + user = { + ...user, + ...content, + updated: ev.created_at, + }; + + await s(`user:${pubkey}`, JSON.stringify(user)); + } else if (sub.includes("messages")) { + let pubkey = sub.split(":")[0]; + await db.sAdd(`${pubkey}:messages`, ev.id); + await s(`ev:${ev.id}`, ev); + } else if (sub.includes("follows")) { + let pubkey = sub.split(":")[0]; + await s(`${pubkey}:follows`, ev.tags); + for (let f of ev.tags) { + let [_, followPubkey] = f; + await db.sAdd(`${followPubkey}:followers`, pubkey); + } + coinos.send(ev); + } else if (sub.includes("followers")) { + let followed = sub.split(":")[0]; + let { pubkey } = ev; + await db.sAdd(`${followed}:followers`, pubkey); + coinos.send(ev); + } else if (sub.includes("notes")) { + let pubkey = sub.split(":")[0]; + await db.sAdd(pubkey, ev.id); + await s(`ev:${ev.id}`, ev); + } + } catch (e) { + console.log(e); + } + }); + } catch(e) { + console.log("POOL", e) + } +}; + +let now = () => Math.round(Date.now() / 1000); + +let timeouts = {}; +export let q = async ( + sub, + query, + { timeout = 20000, since = 3600, eager = 200 }, +) => + new Promise(async (r, j) => { + let start = Date.now(); + let seen = []; + let rejected; + // query.since = await g(`since:${sub}`); + // if (now() - query.since < since) return r(); + + let done = { [sub]: [] }; + + let check = (s) => { + let elapsed = Date.now() - start; + if (rejected) return true; + if ( + done[s] && + (done[s].length === pool.relays.length || + (done[s].length && elapsed > eager)) + ) { + r(); + if (timeouts[s]) timeouts[s].clear(); + return true; + } + }; + + timeouts[sub] = { + async clear() { + clearTimeout(this.timer); + delete timeouts[sub]; + pool.unsubscribe(sub); + await s(`since:${sub}`, now()); + }, + extend(ev) { + if (ev) { + if (seen.includes(ev.id)) return; + seen.push(ev.id); + seen.length > 1000 && seen.shift(); + } + + clearTimeout(this.timer); + this.timer = setTimeout(() => { + j(new Error(`query timed out: ${sub} ${Date.now()}`)); + pool.unsubscribe(sub); + rejected = true; + }, timeout); + }, + timer: undefined, + }; + + timeouts[sub].extend(); + pool.subscribe(sub, query); + + pool.on("eose", (relay, s) => { + if (done[s]) done[s].push(relay.url); + check(s); + }); + + try { + await wait(() => check(sub), 100, 200); + } catch (e) { + console.log(e); + } + }); + +let seen = []; + +let getUser = async (pubkey) => { + let id = await g(`user:${pubkey}`); + let user = await g(`user:${id}`); + return ( + user || { + username: pubkey.substr(0, 6), + pubkey, + anon: true, + } + ); +}; + +function send(ev, url, opts) { + let timeout = (opts && opts.timeout != null && opts.timeout) || 1000; + + return new Promise((resolve, reject) => { + let relay = Relay(url); + + function timeout_reached() { + relay.close(); + reject(new Error("Request timeout")); + } + + let timer = setTimeout(timeout_reached, timeout); + + relay.on("open", () => { + clearTimeout(timer); + timer = setTimeout(timeout_reached, timeout); + relay.send(["EVENT", ev]); + }); + + relay.on("ok", (evid, ok, msg) => { + clearTimeout(timer); + relay.close(); + resolve({ evid, ok, msg }); + }); + }); +} + +export async function handleZap(invoice) { + let { data: privkey } = nip19.decode(config.nostrKey); + let pubkey = getPublicKey(privkey); + let keypair = { privkey, pubkey }; + let zapreq = JSON.parse(invoice.description); + + if (!zapreq.tags || zapreq.tags.length === 0) { + fail(`No tags found`); + } + + let ptags = zapreq.tags.filter( + (t) => t && t.length && t.length >= 2 && t[0] === "p", + ); + + if (ptags.length !== 1) { + fail(`None or multiple p tags found`); + } + + let etags = zapreq.tags.filter( + (t) => t && t.length && t.length >= 2 && t[0] === "e", + ); + + if (!(etags.length === 0 || etags.length === 1)) { + fail(`Expected none or 1 e tags`); + } + + let relays_tag = zapreq.tags.find( + (t) => t && t.length && t.length >= 2 && t[0] === "relays", + ); + + if (!relays_tag) { + fail(`No relays tag found`); + } + + let relays = relays_tag.slice(1).filter((r) => r && r.startsWith("ws")); + let etag = etags.length > 0 && etags[0]; + let ptag = ptags[0]; + + let kind = 9735; + let created_at = invoice.paid_at; + let content = zapreq.content; + + let tags = [ptag]; + if (etag) tags.push(etag); + + tags.push(["bolt11", invoice.bolt11]); + tags.push(["description", invoice.description]); + tags.push(["preimage", invoice.payment_preimage]); + + let ev = { pubkey, kind, created_at, content, tags }; + ev.id = await calculateId(ev); + ev.sig = await signId(privkey, ev.id); + + l("sending receipt"); + + await Promise.allSettled(relays.map((r) => send(ev, r))); +} + +// let damus = new Relay("ws://strfry:7777"); +// damus.on("open", (relay) => { +// damus.subscribe("nwc", { limit: 1, kinds: [23194] }); +// }); +// +// damus.on("event", async (sub, ev) => { +// console.log("EV", ev); +// }); diff --git a/lib/notifications.js b/lib/notifications.js deleted file mode 100644 index af47a6d7..00000000 --- a/lib/notifications.js +++ /dev/null @@ -1,38 +0,0 @@ -import config from "$config"; -import webpush from "web-push"; - -if (config.vapid) { - webpush.setVapidDetails( - config.vapid.url, - config.vapid.publicKey, - config.vapid.privateKey, - ); -} - -export const notify = async (user, payload) => { - try { - let { subscriptions } = user; - - if (subscriptions) { - let i = 0; - for (let subscription of subscriptions) { - i++; - try { - await webpush.sendNotification(subscription, payload); - } catch (e) { - if ( - e.body && - (e.body.includes("expired") || e.body.includes("No such")) - ) { - user.subscriptions.splice(i, 1); - await user.save(); - } else { - err("problem sending notification", e); - } - } - } - } - } catch (e) { - console.log("problem notifying", e); - } -}; diff --git a/lib/notifications.js.map b/lib/notifications.js.map new file mode 100644 index 00000000..0052e1f8 --- /dev/null +++ b/lib/notifications.js.map @@ -0,0 +1 @@ +{"version":3,"file":"notifications.js","sourceRoot":"","sources":["notifications.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAA6B;AAC7B,sDAA+B;AAE/B,IAAI,iBAAM,CAAC,KAAK,EAAE,CAAC;IACjB,kBAAO,CAAC,eAAe,CACrB,iBAAM,CAAC,KAAK,CAAC,GAAG,EAChB,iBAAM,CAAC,KAAK,CAAC,SAAS,EACtB,iBAAM,CAAC,KAAK,CAAC,UAAU,CACxB,CAAC;AACJ,CAAC;AAEM,IAAM,MAAM,GAAG,UAAO,IAAI,EAAE,OAAO;;;;;;gBAEhC,aAAa,GAAK,IAAI,cAAT,CAAU;qBAEzB,aAAa,EAAb,wBAAa;gBACX,CAAC,GAAG,CAAC,CAAC;sBAC4B,EAAb,+BAAa;;;qBAAb,CAAA,2BAAa,CAAA;gBAA7B,YAAY;gBACnB,CAAC,EAAE,CAAC;;;;gBAEF,qBAAM,kBAAO,CAAC,gBAAgB,CAAC,YAAY,EAAE,OAAO,CAAC,EAAA;;gBAArD,SAAqD,CAAC;;;;qBAGpD,CAAA,GAAC,CAAC,IAAI;oBACN,CAAC,GAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,GAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA,EAD1D,wBAC0D;gBAE1D,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAChC,qBAAM,IAAI,CAAC,IAAI,EAAE,EAAA;;gBAAjB,SAAiB,CAAC;;;gBAElB,GAAG,CAAC,8BAA8B,EAAE,GAAC,CAAC,CAAC;;;;gBAZpB,IAAa,CAAA;;;;;gBAkBxC,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,GAAC,CAAC,CAAC;;;;;KAEvC,CAAC;AA1BW,QAAA,MAAM,UA0BjB"} \ No newline at end of file diff --git a/lib/notifications.ts b/lib/notifications.ts new file mode 100644 index 00000000..af47a6d7 --- /dev/null +++ b/lib/notifications.ts @@ -0,0 +1,38 @@ +import config from "$config"; +import webpush from "web-push"; + +if (config.vapid) { + webpush.setVapidDetails( + config.vapid.url, + config.vapid.publicKey, + config.vapid.privateKey, + ); +} + +export const notify = async (user, payload) => { + try { + let { subscriptions } = user; + + if (subscriptions) { + let i = 0; + for (let subscription of subscriptions) { + i++; + try { + await webpush.sendNotification(subscription, payload); + } catch (e) { + if ( + e.body && + (e.body.includes("expired") || e.body.includes("No such")) + ) { + user.subscriptions.splice(i, 1); + await user.save(); + } else { + err("problem sending notification", e); + } + } + } + } + } catch (e) { + console.log("problem notifying", e); + } +}; diff --git a/lib/payments.js b/lib/payments.js deleted file mode 100644 index 92e17839..00000000 --- a/lib/payments.js +++ /dev/null @@ -1,682 +0,0 @@ -import locales from "$lib/locales/index"; -import config from "$config"; -import { emit } from "$lib/sockets"; -import { v4 } from "uuid"; -import { notify } from "$lib/notifications"; -import { db, g, s, t } from "$lib/db"; -import { l, err, warn } from "$lib/logging"; -import { - f, - btc, - fail, - fiat, - getInvoice, - getUser, - sleep, - wait, - SATS, - sats, - formatReceipt, -} from "$lib/utils"; -import { callWebhook } from "$lib/webhooks"; -import got from "got"; -import { mqtt1, mqtt2 } from "$lib/mqtt"; -import { mail, templates } from "$lib/mail"; -import api from "$lib/api"; - -import lq from "$lib/liquid"; -import bc from "$lib/bitcoin"; -import ln from "$lib/ln"; - -let { URL } = process.env; - -export let types = { - internal: "internal", - bitcoin: "bitcoin", - lightning: "lightning", - fund: "fund", - liquid: "liquid", - ecash: "ecash", -}; - -export let debit = async ({ - hash, - amount, - fee = 0, - memo, - user, - type = types.internal, - rate, -}) => { - amount = parseInt(amount); - if (type !== types.internal && (await g("freeze"))) - fail("Problem sending payment"); - // if (!user.unlimited && amount > 1000000) - // fail(`⚡️${amount} exceeds max withdrawal of ⚡️1,000,000`); - let ref; - let { id: uid, currency, username } = user; - - let rates = await g('rates'); - if (!rate) rate = rates[currency]; - - let invoice = await getInvoice(hash); - let iid; - - if (invoice) { - if (invoice.received >= amount) fail("Invoice already paid"); - ({ id: iid } = invoice); - - ref = invoice.uid; - - let equivalentRate = - invoice.rate * (rates[currency] / rates[invoice.currency]); - - if (Math.abs(invoice.rate / rates[invoice.currency] - 1) < 0.01) { - rate = equivalentRate; - } else { - warn("rate slipped", hash, invoice.rate, equivalentRate); - } - } - - let tip = parseInt(invoice?.tip) || null; - if (tip < 0) fail("Invalid tip"); - - if (!amount || amount < 0) fail("Amount must be greater than zero"); - - let ourfee = [types.bitcoin, types.liquid, types.lightning].includes(type) - ? Math.round((amount + fee + tip) * config.fee) - : 0; - - ourfee = await db.debit( - `balance:${uid}`, - `credit:${type}:${uid}`, - amount || 0, - tip || 0, - fee || 0, - ourfee || 0, - ); - - if (ourfee.err) fail(ourfee.err); - - let id = v4(); - let p = { - id, - amount: -amount, - fee, - hash, - ourfee, - memo, - iid, - uid, - confirmed: ![types.bitcoin, types.liquid].includes(type), - rate, - currency, - type, - ref, - tip, - created: Date.now(), - }; - - await s(`payment:${hash}`, id); - await s(`payment:${id}`, p); - await db.lPush(`${uid}:payments`, id); - - l(user.username, "sent", type, amount); - //emit(user.id, "payment", p); - - return p; -}; - -export let credit = async (hash, amount, memo, ref, type = types.internal) => { - amount = parseInt(amount) || 0; - - let inv = await getInvoice(hash); - if (!inv) { - await db.sAdd("missing", ref.split(":")[0]); - return; - } - - let { tip } = inv; - tip = parseInt(tip) || 0; - - if (!memo) ({ memo } = inv); - if (memo && memo.length > 5000) fail("memo too long"); - if (amount < 0 || tip < 0) fail("Invalid amount"); - if (type === types.internal) amount += tip; - - let user = await g(`user:${inv.uid}`); - let { id: uid, currency, username } = user; - - let rates = await g('rates'); - let rate = rates[currency]; - - if (!rate) await sleep(1000); - rate = rates[currency]; - - let equivalentRate = - inv.rate * (rates[currency] / rates[inv.currency]); - - if (Math.abs(inv.rate / rates[inv.currency] - 1) < 0.01) { - rate = equivalentRate; - } else { - // warn("rate slipped", hash, invoice.rate, equivalentRate); - } - - let id = v4(); - let p = { - id, - iid: inv.id, - hash, - amount: parseInt(amount - tip), - uid, - rate, - currency, - memo, - ref, - tip, - type, - confirmed: true, - created: Date.now(), - }; - - if ([types.bitcoin, types.liquid].includes(type)) inv.pending += amount; - else { - inv.received += amount; - inv.preimage = ref; - } - - let balanceKey = "balance"; - if ([types.bitcoin, types.liquid].includes(type)) { - let [txid, vout] = ref.split(":").slice(-2); - p.confirmed = false; - balanceKey = "pending"; - await s(`payment:${txid}:${vout}`, id); - } else { - await s(`payment:${hash}`, id); - } - - let m = await db.multi(); - - if ([types.bitcoin, types.liquid, types.lightning].includes(type)) - m.incrBy(`credit:${type}:${uid}`, Math.round(amount * config.fee)); - - m.set(`invoice:${inv.id}`, JSON.stringify(inv)) - .set(`payment:${p.id}`, JSON.stringify(p)) - .lPush(`${uid}:payments`, p.id) - .incrBy(`${balanceKey}:${uid}`, amount) - .exec(); - - emit(uid, "payment", p); - notify(user, `Received ${amount} sats`); - callWebhook(inv, p); - l(username, "received", type, amount); - - let items; - if (inv.items && inv.items.length) { - formatReceipt(inv.items, inv.currency); - p.items = inv.items; - } - - if (config.mqtt1) { - mqtt1.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${items}`, - ); - } - - if (config.mqtt2) { - mqtt2.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${items}`, - ); - } - - try { - await completePayment(p, user); - } catch (e) { - console.log(e); - } - - return p; -}; - -let fmt = (sats) => - "⚡️" + - new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(sats); - -export let completePayment = async (p, user) => { - let { id, autowithdraw, threshold, reserve, destination, username } = user; - let link = (id) => `${URL}/payment/${id}`; - if (p.confirmed) { - let withdrawal; - if (autowithdraw) { - try { - let to = destination.trim(); - let balance = await g(`balance:${id}`); - let amount = balance - reserve; - if (balance > threshold) { - l("initiating autowithdrawal", amount, to, balance, threshold); - let w = await pay({ amount, to, user }); - withdrawal = { - amount: fmt(-w.amount), - link: link(w.id), - }; - } - } catch (e) { - withdrawal = { failed: true }; - warn(username, "autowithdraw failed", e.message); - } - } - - let t = locales[user.language || "en"]; - if (user.verified && user.notify) { - mail(user, t.paymentReceived, templates.paymentReceived, { - ...t, - username, - payment: { - amount: fmt(p.amount), - link: link(p.id), - tip: p.tip ? fmt(p.tip) : undefined, - fiat: f(fiat(p.amount, p.rate), p.currency), - fiatTip: p.tip ? f(fiat(p.tip, p.rate), p.currency) : undefined, - memo: p.memo, - items: - p.items && - p.items.map((i) => { - return { - quantity: i.quantity, - name: i.name, - total: i.quantity * i.price, - totalFiat: f(i.quantity * i.price, p.currency), - }; - }), - }, - withdrawal, - }); - } - } -}; - -let pay = async ({ amount, to, user }) => { - amount = parseInt(amount) || 0; - let lnurl, pr; - if (to.includes("@") && to.includes(".")) { - let [name, domain] = to.split("@"); - lnurl = `https://${domain}/.well-known/lnurlp/${name}`; - } else if (to.startsWith("lnurl")) { - lnurl = Buffer.from( - bech32.fromWords(bech32.decode(text, 20000).words), - ).toString(); - } - - let maxfee = getMaxFee(amount); - if (lnurl) { - amount -= maxfee; - let { callback } = await got(lnurl).json(); - ({ pr } = await got(`${callback}?amount=${amount * 1000}`).json()); - } else if (to.startsWith("ln")) { - amount -= maxfee; - pr = to; - } - - return pr - ? await sendLightning({ user, pr, amount, maxfee }) - : await sendOnchain({ amount, address: to, user, subtract: true }); -}; - -export let decode = async (hex) => { - let type, tx; - try { - tx = await bc.decodeRawTransaction(hex); - type = types.bitcoin; - } catch (e) { - try { - tx = await lq.decodeRawTransaction(hex); - type = types.liquid; - } catch (e) { - err("invalid hex", hex); - fail("unrecognized tx"); - } - } - - return { tx, type }; -}; - -export let sendOnchain = async (params) => { - let { hex, rate, user } = params; - if (!hex) ({ hex } = await build(params)); - - let { tx, type } = await decode(hex); - let node = getNode(type); - let { txid } = tx; - - try { - if (inflight[txid]) fail("payment in flight"); - inflight[txid] = true; - - if (config[type].walletpass) - await node.walletPassphrase(config[type].walletpass, 300); - - let { hex: signed } = await node.signRawTransactionWithWallet( - type === types.liquid ? await node.blindRawTransaction(hex) : hex, - ); - - ({ txid } = await node.decodeRawTransaction(signed)); - - let r = await node.testMempoolAccept([signed]); - if (!r[0].allowed) fail("transaction rejected"); - - let total = 0; - let fee = 0; - let change = 0; - - if (type === types.liquid) { - for (let { - asset, - scriptPubKey: { address, type }, - value, - } of tx.vout) { - if (asset !== config.liquid.btc) fail("only L-BTC supported"); - if (type === "fee") fee = sats(value); - else { - total += sats(value); - - if (address) { - if ((await node.getAddressInfo(address)).ismine) { - change += sats(value); - } - } - } - } - } else { - let totalIn = 0; - for await (let { txid, vout } of tx.vin) { - let hex = await node.getRawTransaction(txid); - let tx = await node.decodeRawTransaction(hex); - totalIn += sats(tx.vout[vout].value); - } - - for (let { - scriptPubKey: { address }, - value, - } of tx.vout) { - total += sats(value); - if (await g(`invoice:${address}`)) - fail("Cannot send to internal address"); - - if ((await node.getAddressInfo(address)).ismine) change += sats(value); - } - - fee = totalIn - total; - } - - let amount = total - change; - - let p = await debit({ - hash: txid, - amount, - fee, - rate, - user, - type, - }); - - p.hex = signed; - await s(`payment:${p.id}`, p); - - await node.sendRawTransaction(signed); - - delete inflight[txid]; - return p; - } catch (e) { - delete inflight[txid]; - throw e; - } -}; - -let getMaxFee = (n) => - Math.round( - n < 100 - ? n * 5 - : n < 1000 - ? n - : n < 10000 - ? n * 0.5 - : n < 100000 - ? n * 0.1 - : n < 1000000 - ? n * 0.05 - : n * 0.01, - ); - -let checking = {}; -let inflight = {}; -export let sendLightning = async ({ user, pr, amount, maxfee, memo }) => { - try { - if (inflight[user.id]) fail("payment in flight"); - inflight[user.id] = true; - - let p; - let hash = pr; - - if (typeof amount !== "undefined") { - amount = parseInt(amount); - if (amount < 0 || amount > SATS || isNaN(amount)) fail("Invalid amount"); - } - - let total = amount; - let { amount_msat, payment_hash } = await ln.decode(pr); - if (amount_msat) total = Math.round(amount_msat / 1000); - - maxfee = parseInt(maxfee) || getMaxFee(total); - - if (maxfee < 0) fail("Max fee cannot be negative"); - - let invoice = await getInvoice(hash); - - if (invoice) { - if (invoice.uid === user.id) fail("Cannot send to self"); - hash = pr; - } else { - let r; - - let { pays } = await ln.listpays(pr); - if (pays.find((p) => p.status === "complete")) - fail("Invoice has already been paid"); - - if (pays.find((p) => p.status === "pending")) - fail("Payment is already underway"); - - p = await debit({ - hash: pr, - amount: total, - fee: maxfee, - memo, - user, - type: types.lightning, - }); - - let check = async () => { - if (checking[pr]) return; - checking[pr] = true; - - try { - let { pays } = await ln.listpays(pr); - - let recordExists = !!(await g(`payment:${pr}`)); - let paymentFailed = - !pays.length || pays.every((p) => p.status === "failed"); - - if (recordExists && paymentFailed) { - await db.del(`payment:${pr}`); - await db.del(`payment:${p.id}`); - - let ourfee = p.ourfee || 0; - let credit = Math.round(total * config.fee) - ourfee; - await db.incrBy(`balance:${p.uid}`, total + maxfee + ourfee); - - await db.incrBy(`credit:${types.lightning}:${p.uid}`, credit); - await db.lRem(`${p.uid}:payments`, 1, p.id); - - clearInterval(interval); - } - } catch (e) { - err("Failed to reverse payment", r); - } - - checking[pr] = false; - }; - - let interval = setInterval(check, 5000); - - try { - l("paying lightning invoice", pr.substr(-8), total, amount, maxfee); - - r = await ln.pay({ - bolt11: pr.replace(/\s/g, "").toLowerCase(), - amount_msat: amount_msat ? undefined : amount * 1000, - maxfee: maxfee * 1000, - retry_for: 5, - }); - - if (!(r.status === "complete" && r.payment_preimage)) - fail("Payment did not complete"); - - l("payment completed", p.id, r.payment_preimage); - - p.amount = -total; - p.fee = Math.round((r.amount_sent_msat - r.amount_msat) / 1000); - p.ref = r.payment_preimage; - - await s(`payment:${p.id}`, p); - - l("refunding fee", maxfee, p.fee, maxfee - p.fee, p.ref); - await db.incrBy(`balance:${p.uid}`, maxfee - p.fee); - } catch (e) { - await check(); - throw e; - } - - clearInterval(interval); - } - - delete inflight[user.id]; - return p; - } catch (e) { - delete inflight[user.id]; - throw e; - } -}; - -export let getNode = (type) => { - if (type === types.bitcoin) return bc; - else if (type === types.liquid) return lq; - else fail("unrecognized transaction type"); -}; - -let getAddressType = async (a) => { - try { - await bc.getAddressInfo(a); - return types.bitcoin; - } catch (e) { - try { - await lq.getAddressInfo(a); - return types.liquid; - } catch (e) { - fail("unrecognized address"); - } - } -}; - -export let build = async ({ amount, address, feeRate, user, subtract }) => { - let type = await getAddressType(address); - - let node = getNode(type); - amount = parseInt(amount); - if (amount < 0) fail("invalid amount"); - - let fees = await fetch(`${api[type]}/fees/recommended`).then((r) => r.json()); - - if (!feeRate) { - feeRate = fees.halfHourFee; - } - - if (feeRate < fees.economyFee) fail("fee rate too low"); - - let replaceable = false; - - let outs = [{ [address]: btc(amount) }]; - - if (type === types.liquid) - outs = outs.map((o) => ({ ...o, asset: config.liquid.btc })); - - let raw = await node.createRawTransaction([], outs, 0, replaceable); - - let tx = await node.fundRawTransaction(raw, { - fee_rate: feeRate, - replaceable, - subtractFeeFromOutputs: [], - }); - - let fee = sats(tx.fee); - let dust = 547; - - let balance = await g(`balance:${user.id}`); - let ourfee = Math.round(amount * config.fee); - let credit = await g(`credit:${type}:${user.id}`); - let covered = Math.min(credit, ourfee); - ourfee -= covered; - - if (subtract || amount + fee + ourfee > balance) { - if (amount <= fee + ourfee + dust) - fail( - `insufficient funds ⚡️${balance} of ⚡️${amount + fee + ourfee + dust}`, - ); - - outs = [{ [address]: btc(amount - ourfee) }]; - raw = await node.createRawTransaction([], outs, 0, replaceable); - - tx = await node.fundRawTransaction(raw, { - fee_rate: feeRate, - replaceable, - subtractFeeFromOutputs: [0], - }); - - fee = sats(tx.fee); - } - - return { feeRate, ourfee, fee, fees, hex: tx.hex }; -}; - -export let catchUp = async () => { - try { - let txns = []; - for (let [type, n] of Object.entries({ bitcoin: bc, liquid: lq })) { - txns.push( - ...(await n.listTransactions("*", 200)).filter((tx) => { - tx.type = type; - return tx.category === "receive" && tx.confirmations > 0; - }), - ); - } - - for (let { txid, type } of txns) { - try { - if (await db.zScore("seen", txid)) continue; - await got.post(`http://localhost:${process.env.PORT || 3119}/confirm`, { - json: { txid, wallet: config.bitcoin.wallet, type }, - }); - - await db.zAdd("seen", { score: Date.now(), value: txid }); - if ((await db.zCard("seen")) > 10000) - await db.zRemRangeByRank("seen", 0, 0); - } catch (e) { - err("problem confirming", e.message); - } - } - } catch (e) { - err("problem syncing", e.message); - } - - setTimeout(catchUp, 2000); -}; diff --git a/lib/payments.js.map b/lib/payments.js.map new file mode 100644 index 00000000..c4cf897b --- /dev/null +++ b/lib/payments.js.map @@ -0,0 +1 @@ +{"version":3,"file":"payments.js","sourceRoot":"","sources":["payments.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6DAAyC;AACzC,oDAA6B;AAC7B,wCAAoC;AACpC,6BAA0B;AAC1B,oDAA4C;AAC5C,8BAAsC;AACtC,wCAA4C;AAC5C,oCAYoB;AACpB,0CAA4C;AAC5C,4CAAsB;AACtB,kCAAyC;AACzC,kCAA4C;AAC5C,iDAA2B;AAE3B,uDAA6B;AAC7B,yDAA8B;AAC9B,+CAAyB;AAEnB,IAAA,GAAG,GAAK,OAAO,CAAC,GAAG,IAAhB,CAAiB;AAEf,QAAA,KAAK,GAAG;IACjB,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,WAAW;IACtB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;CACf,CAAC;AAEK,IAAI,KAAK,GAAG,iEAAO,EAQzB;;QAPC,IAAI,UAAA,EACJ,MAAM,YAAA,EACN,WAAO,EAAP,GAAG,mBAAG,CAAC,KAAA,EACP,IAAI,UAAA,EACJ,IAAI,UAAA,EACJ,YAAqB,EAArB,IAAI,mBAAG,aAAK,CAAC,QAAQ,KAAA,EACrB,IAAI,UAAA;;;;gBAEJ,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;gBACtB,KAAA,IAAI,KAAK,aAAK,CAAC,QAAQ,CAAA;yBAAvB,wBAAuB;gBAAK,qBAAM,IAAA,MAAC,EAAC,QAAQ,CAAC,EAAA;;gBAAlB,KAAA,CAAC,SAAiB,CAAC,CAAA;;;gBAAlD;oBACE,IAAA,YAAI,EAAC,yBAAyB,CAAC,CAAC;gBAIxB,GAAG,GAAyB,IAAI,GAA7B,EAAE,QAAQ,GAAe,IAAI,SAAnB,EAAE,QAAQ,GAAK,IAAI,SAAT,CAAU;gBAE/B,qBAAM,IAAA,MAAC,EAAC,OAAO,CAAC,EAAA;;gBAAxB,KAAK,GAAG,SAAgB;gBAC5B,IAAI,CAAC,IAAI;oBAAE,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAEpB,qBAAM,IAAA,kBAAU,EAAC,IAAI,CAAC,EAAA;;gBAAhC,OAAO,GAAG,SAAsB;gBAGpC,IAAI,OAAO,EAAE,CAAC;oBACZ,IAAI,OAAO,CAAC,QAAQ,IAAI,MAAM;wBAAE,IAAA,YAAI,EAAC,sBAAsB,CAAC,CAAC;oBAC7D,CAAO,GAAG,GAAK,OAAO,GAAZ,CAAa,CAAC;oBAExB,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;oBAEd,cAAc,GAChB,OAAO,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;oBAE7D,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC;wBAChE,IAAI,GAAG,cAAc,CAAC;oBACxB,CAAC;yBAAM,CAAC;wBACN,IAAA,cAAI,EAAC,cAAc,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;oBAC3D,CAAC;gBACH,CAAC;gBAEG,GAAG,GAAG,QAAQ,CAAC,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,GAAG,CAAC,IAAI,IAAI,CAAC;gBACzC,IAAI,GAAG,GAAG,CAAC;oBAAE,IAAA,YAAI,EAAC,aAAa,CAAC,CAAC;gBAEjC,IAAI,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC;oBAAE,IAAA,YAAI,EAAC,kCAAkC,CAAC,CAAC;gBAEhE,MAAM,GAAG,CAAC,aAAK,CAAC,OAAO,EAAE,aAAK,CAAC,MAAM,EAAE,aAAK,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACxE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG,iBAAM,CAAC,GAAG,CAAC;oBAC/C,CAAC,CAAC,CAAC,CAAC;gBAEG,qBAAM,OAAE,CAAC,KAAK,CACrB,kBAAW,GAAG,CAAE,EAChB,iBAAU,IAAI,cAAI,GAAG,CAAE,EACvB,MAAM,IAAI,CAAC,EACX,GAAG,IAAI,CAAC,EACR,GAAG,IAAI,CAAC,EACR,MAAM,IAAI,CAAC,CACZ,EAAA;;gBAPD,MAAM,GAAG,SAOR,CAAC;gBAEF,IAAI,MAAM,CAAC,GAAG;oBAAE,IAAA,YAAI,EAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAE7B,EAAE,GAAG,IAAA,SAAE,GAAE,CAAC;gBACV,CAAC,GAAG;oBACN,EAAE,IAAA;oBACF,MAAM,EAAE,CAAC,MAAM;oBACf,GAAG,KAAA;oBACH,IAAI,MAAA;oBACJ,MAAM,QAAA;oBACN,IAAI,MAAA;oBACJ,GAAG,KAAA;oBACH,GAAG,KAAA;oBACH,SAAS,EAAE,CAAC,CAAC,aAAK,CAAC,OAAO,EAAE,aAAK,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACxD,IAAI,MAAA;oBACJ,QAAQ,UAAA;oBACR,IAAI,MAAA;oBACJ,GAAG,KAAA;oBACH,GAAG,KAAA;oBACH,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE;iBACpB,CAAC;gBAEF,qBAAM,IAAA,MAAC,EAAC,kBAAW,IAAI,CAAE,EAAE,EAAE,CAAC,EAAA;;gBAA9B,SAA8B,CAAC;gBAC/B,qBAAM,IAAA,MAAC,EAAC,kBAAW,EAAE,CAAE,EAAE,CAAC,CAAC,EAAA;;gBAA3B,SAA2B,CAAC;gBAC5B,qBAAM,OAAE,CAAC,KAAK,CAAC,UAAG,GAAG,cAAW,EAAE,EAAE,CAAC,EAAA;;gBAArC,SAAqC,CAAC;gBAEtC,IAAA,WAAC,EAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;gBACvC,8BAA8B;gBAE9B,sBAAO,CAAC,EAAC;;;KACV,CAAC;AAtFS,QAAA,KAAK,SAsFd;AAEK,IAAI,MAAM,GAAG;;;;;+GAAO,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAqB;;;QAArB,qBAAA,EAAA,OAAO,aAAK,CAAC,QAAQ;;;;oBACvE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;oBAErB,qBAAM,IAAA,kBAAU,EAAC,IAAI,CAAC,EAAA;;oBAA5B,GAAG,GAAG,SAAsB;yBAC5B,CAAC,GAAG,EAAJ,wBAAI;oBACN,qBAAM,OAAE,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAA;;oBAA3C,SAA2C,CAAC;oBAC5C,sBAAO;;oBAGH,GAAG,GAAK,GAAG,IAAR,CAAS;oBAClB,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBAEzB,IAAI,CAAC,IAAI;wBAAE,CAAG,IAAI,GAAK,GAAG,KAAR,CAAS,CAAC;oBAC5B,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI;wBAAE,IAAA,YAAI,EAAC,eAAe,CAAC,CAAC;oBACtD,IAAI,MAAM,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC;wBAAE,IAAA,YAAI,EAAC,gBAAgB,CAAC,CAAC;oBAClD,IAAI,IAAI,KAAK,aAAK,CAAC,QAAQ;wBAAE,MAAM,IAAI,GAAG,CAAC;oBAEhC,qBAAM,IAAA,MAAC,EAAC,eAAQ,GAAG,CAAC,GAAG,CAAE,CAAC,EAAA;;oBAAjC,IAAI,GAAG,SAA0B;oBAC3B,GAAG,GAAyB,IAAI,GAA7B,EAAE,QAAQ,GAAe,IAAI,SAAnB,EAAE,QAAQ,GAAK,IAAI,SAAT,CAAU;oBAE/B,qBAAM,IAAA,MAAC,EAAC,OAAO,CAAC,EAAA;;oBAAxB,KAAK,GAAG,SAAgB;oBACxB,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;yBAEvB,CAAC,IAAI,EAAL,wBAAK;oBAAE,qBAAM,IAAA,aAAK,EAAC,IAAI,CAAC,EAAA;;oBAAjB,SAAiB,CAAC;;;oBAC7B,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;oBAEnB,cAAc,GAChB,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;oBAErD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC;wBACxD,IAAI,GAAG,cAAc,CAAC;oBACxB,CAAC;yBAAM,CAAC;wBACN,4DAA4D;oBAC9D,CAAC;oBAEG,EAAE,GAAG,IAAA,SAAE,GAAE,CAAC;oBACV,CAAC,GAAG;wBACN,EAAE,IAAA;wBACF,GAAG,EAAE,GAAG,CAAC,EAAE;wBACX,IAAI,MAAA;wBACJ,MAAM,EAAE,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;wBAC9B,GAAG,KAAA;wBACH,IAAI,MAAA;wBACJ,QAAQ,UAAA;wBACR,IAAI,MAAA;wBACJ,GAAG,KAAA;wBACH,GAAG,KAAA;wBACH,IAAI,MAAA;wBACJ,SAAS,EAAE,IAAI;wBACf,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE;qBACpB,CAAC;oBAEF,IAAI,CAAC,aAAK,CAAC,OAAO,EAAE,aAAK,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;wBAAE,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC;yBACnE,CAAC;wBACJ,GAAG,CAAC,QAAQ,IAAI,MAAM,CAAC;wBACvB,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC;oBACrB,CAAC;oBAEG,UAAU,GAAG,SAAS,CAAC;yBACvB,CAAC,aAAK,CAAC,OAAO,EAAE,aAAK,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAA5C,wBAA4C;oBACzC,IAAI,IAAL,KAAe,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAlC,EAAE,IAAI,QAAA,CAA6B;oBAC5C,CAAC,CAAC,SAAS,GAAG,KAAK,CAAC;oBACpB,UAAU,GAAG,SAAS,CAAC;oBACvB,qBAAM,IAAA,MAAC,EAAC,kBAAW,IAAI,cAAI,IAAI,CAAE,EAAE,EAAE,CAAC,EAAA;;oBAAtC,SAAsC,CAAC;;wBAEvC,qBAAM,IAAA,MAAC,EAAC,kBAAW,IAAI,CAAE,EAAE,EAAE,CAAC,EAAA;;oBAA9B,SAA8B,CAAC;;yBAGzB,qBAAM,OAAE,CAAC,KAAK,EAAE,EAAA;;oBAApB,CAAC,GAAG,SAAgB;oBAExB,IAAI,CAAC,aAAK,CAAC,OAAO,EAAE,aAAK,CAAC,MAAM,EAAE,aAAK,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;wBAC/D,CAAC,CAAC,MAAM,CAAC,iBAAU,IAAI,cAAI,GAAG,CAAE,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,iBAAM,CAAC,GAAG,CAAC,CAAC,CAAC;oBAErE,CAAC,CAAC,GAAG,CAAC,kBAAW,GAAG,CAAC,EAAE,CAAE,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;yBAC5C,GAAG,CAAC,kBAAW,CAAC,CAAC,EAAE,CAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;yBACzC,KAAK,CAAC,UAAG,GAAG,cAAW,EAAE,CAAC,CAAC,EAAE,CAAC;yBAC9B,MAAM,CAAC,UAAG,UAAU,cAAI,GAAG,CAAE,EAAE,MAAM,CAAC;yBACtC,IAAI,EAAE,CAAC;oBAEV,IAAA,cAAI,EAAC,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;oBACxB,IAAA,sBAAM,EAAC,IAAI,EAAE,mBAAY,MAAM,UAAO,CAAC,CAAC;oBACxC,IAAA,sBAAW,EAAC,GAAG,EAAE,CAAC,CAAC,CAAC;oBACpB,IAAA,WAAC,EAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;oBAGtC,IAAI,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;wBAClC,IAAA,qBAAa,EAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;wBACvC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;oBACtB,CAAC;oBAED,IAAI,iBAAM,CAAC,KAAK,EAAE,CAAC;wBACjB,YAAK,CAAC,OAAO,CACX,QAAQ,EACR,cAAO,CAAC,CAAC,MAAM,cAAI,CAAC,CAAC,GAAG,cAAI,CAAC,CAAC,IAAI,cAAI,CAAC,CAAC,OAAO,cAAI,CAAC,CAAC,EAAE,cAAI,CAAC,CAAC,IAAI,cAAI,KAAK,CAAE,CAC7E,CAAC;oBACJ,CAAC;oBAED,IAAI,iBAAM,CAAC,KAAK,EAAE,CAAC;wBACjB,YAAK,CAAC,OAAO,CACX,QAAQ,EACR,cAAO,CAAC,CAAC,MAAM,cAAI,CAAC,CAAC,GAAG,cAAI,CAAC,CAAC,IAAI,cAAI,CAAC,CAAC,OAAO,cAAI,CAAC,CAAC,EAAE,cAAI,CAAC,CAAC,IAAI,cAAI,KAAK,CAAE,CAC7E,CAAC;oBACJ,CAAC;;;;oBAGC,qBAAM,IAAA,uBAAe,EAAC,CAAC,EAAE,IAAI,CAAC,EAAA;;oBAA9B,SAA8B,CAAC;;;;oBAE/B,OAAO,CAAC,GAAG,CAAC,GAAC,CAAC,CAAC;;yBAGjB,sBAAO,CAAC,EAAC;;;;CACV,CAAC;AA/GS,QAAA,MAAM,UA+Gf;AAEF,IAAI,GAAG,GAAG,UAAC,IAAI;IACb,OAAA,IAAI;QACJ,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,qBAAqB,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;AADzE,CACyE,CAAC;AAErE,IAAI,eAAe,GAAG,UAAO,CAAC,EAAE,IAAI;;;;;gBACnC,EAAE,GAA8D,IAAI,GAAlE,EAAE,YAAY,GAAgD,IAAI,aAApD,EAAE,SAAS,GAAqC,IAAI,UAAzC,EAAE,OAAO,GAA4B,IAAI,QAAhC,EAAE,WAAW,GAAe,IAAI,YAAnB,EAAE,QAAQ,GAAK,IAAI,SAAT,CAAU;gBACvE,IAAI,GAAG,UAAC,EAAE,IAAK,OAAA,UAAG,GAAG,sBAAY,EAAE,CAAE,EAAtB,CAAsB,CAAC;qBACtC,CAAC,CAAC,SAAS,EAAX,wBAAW;gBACT,UAAU,SAAA,CAAC;qBACX,YAAY,EAAZ,wBAAY;;;;gBAER,EAAE,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC;gBACd,qBAAM,IAAA,MAAC,EAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;;gBAAlC,OAAO,GAAG,SAAwB;gBAClC,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;qBAC3B,CAAA,OAAO,GAAG,SAAS,CAAA,EAAnB,wBAAmB;gBACrB,IAAA,WAAC,EAAC,2BAA2B,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;gBACvD,qBAAM,GAAG,CAAC,EAAE,MAAM,QAAA,EAAE,EAAE,IAAA,EAAE,IAAI,MAAA,EAAE,CAAC,EAAA;;gBAAnC,CAAC,GAAG,SAA+B;gBACvC,UAAU,GAAG;oBACX,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;oBACtB,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;iBACjB,CAAC;;;;;gBAGJ,UAAU,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;gBAC9B,IAAA,cAAI,EAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;gBAIjD,MAAI,eAAO,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC;gBACvC,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;oBACjC,IAAA,WAAI,EAAC,IAAI,EAAE,GAAC,CAAC,eAAe,EAAE,gBAAS,CAAC,eAAe,wBAClD,GAAC,KACJ,QAAQ,UAAA,EACR,OAAO,EAAE;4BACP,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;4BACrB,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;4BAChB,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;4BACnC,IAAI,EAAE,IAAA,SAAC,EAAC,IAAA,YAAI,EAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC;4BAC3C,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAA,SAAC,EAAC,IAAA,YAAI,EAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;4BAC/D,IAAI,EAAE,CAAC,CAAC,IAAI;4BACZ,KAAK,EACH,CAAC,CAAC,KAAK;gCACP,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,UAAC,CAAC;oCACZ,OAAO;wCACL,QAAQ,EAAE,CAAC,CAAC,QAAQ;wCACpB,IAAI,EAAE,CAAC,CAAC,IAAI;wCACZ,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK;wCAC3B,SAAS,EAAE,IAAA,SAAC,EAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC;qCAC/C,CAAC;gCACJ,CAAC,CAAC;yBACL,EACD,UAAU,YAAA,IACV,CAAC;gBACL,CAAC;;;;;KAEJ,CAAC;AAnDS,QAAA,eAAe,mBAmDxB;AAEF,IAAI,GAAG,GAAG,iEAAO,EAAoB;;QAAlB,MAAM,YAAA,EAAE,EAAE,QAAA,EAAE,IAAI,UAAA;;;;gBACjC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAE/B,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;oBACrC,KAAiB,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAA7B,cAAI,EAAE,MAAM,QAAA,CAAkB;oBACnC,KAAK,GAAG,kBAAW,MAAM,iCAAuB,MAAI,CAAE,CAAC;gBACzD,CAAC;qBAAM,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;oBAClC,KAAK,GAAG,MAAM,CAAC,IAAI,CACjB,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,CACnD,CAAC,QAAQ,EAAE,CAAC;gBACf,CAAC;gBAEG,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;qBAC3B,KAAK,EAAL,wBAAK;gBACP,MAAM,IAAI,MAAM,CAAC;gBACE,qBAAM,IAAA,aAAG,EAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAA;;gBAApC,QAAQ,GAAK,CAAA,SAAuB,CAAA,SAA5B;gBACJ,qBAAM,IAAA,aAAG,EAAC,UAAG,QAAQ,qBAAW,MAAM,GAAG,IAAI,CAAE,CAAC,CAAC,IAAI,EAAE,EAAA;;gBAAjE,CAAG,EAAE,GAAK,CAAA,SAAuD,CAAA,GAA5D,CAA6D,CAAC;;;gBAC9D,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC/B,MAAM,IAAI,MAAM,CAAC;oBACjB,EAAE,GAAG,EAAE,CAAC;gBACV,CAAC;;;qBAEM,EAAE,EAAF,wBAAE;gBACL,qBAAM,IAAA,qBAAa,EAAC,EAAE,IAAI,MAAA,EAAE,EAAE,IAAA,EAAE,MAAM,QAAA,EAAE,MAAM,QAAA,EAAE,CAAC,EAAA;;gBAAjD,KAAA,SAAiD,CAAA;;oBACjD,qBAAM,IAAA,mBAAW,EAAC,EAAE,MAAM,QAAA,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,MAAA,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAA;;gBAAhE,KAAA,SAAgE,CAAA;;oBAFpE,0BAEqE;;;KACtE,CAAC;AAEK,IAAI,MAAM,GAAG,UAAO,GAAG;;;;;;gBAGrB,qBAAM,iBAAE,CAAC,oBAAoB,CAAC,GAAG,CAAC,EAAA;;gBAAvC,EAAE,GAAG,SAAkC,CAAC;gBACxC,IAAI,GAAG,aAAK,CAAC,OAAO,CAAC;;;;;;;gBAGd,qBAAM,gBAAE,CAAC,oBAAoB,CAAC,GAAG,CAAC,EAAA;;gBAAvC,EAAE,GAAG,SAAkC,CAAC;gBACxC,IAAI,GAAG,aAAK,CAAC,MAAM,CAAC;;;;gBAEpB,IAAA,aAAG,EAAC,aAAa,EAAE,GAAG,CAAC,CAAC;gBACxB,IAAA,YAAI,EAAC,iBAAiB,CAAC,CAAC;;;oBAI5B,sBAAO,EAAE,EAAE,IAAA,EAAE,IAAI,MAAA,EAAE,EAAC;;;KACrB,CAAC;AAhBS,QAAA,MAAM,UAgBf;AAEK,IAAI,WAAW,GAAG,UAAO,MAAM;;;;;;;gBAC9B,GAAG,GAAiB,MAAM,IAAvB,EAAE,IAAI,GAAW,MAAM,KAAjB,EAAE,IAAI,GAAK,MAAM,KAAX,CAAY;qBAC7B,CAAC,GAAG,EAAJ,wBAAI;gBAAa,qBAAM,IAAA,aAAK,EAAC,MAAM,CAAC,EAAA;;gBAA9B,CAAG,GAAG,GAAK,CAAA,SAAmB,CAAA,IAAxB,CAAyB,CAAC;;oBAEvB,qBAAM,IAAA,cAAM,EAAC,GAAG,CAAC,EAAA;;gBAA9B,EAAE,IAAJ,KAAe,SAAiB,QAA5B,EAAE,IAAI,UAAA;gBACV,IAAI,GAAG,IAAA,eAAO,EAAC,IAAI,CAAC,CAAC;gBACnB,IAAI,GAAK,EAAE,KAAP,CAAQ;;;;gBAGhB,IAAI,QAAQ,CAAC,IAAI,CAAC;oBAAE,IAAA,YAAI,EAAC,mBAAmB,CAAC,CAAC;gBAC9C,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;qBAElB,iBAAM,CAAC,IAAI,CAAC,CAAC,UAAU,EAAvB,wBAAuB;gBACzB,qBAAM,IAAI,CAAC,gBAAgB,CAAC,iBAAM,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,GAAG,CAAC,EAAA;;gBAAzD,SAAyD,CAAC;;;gBAEhC,KAAA,CAAA,KAAA,IAAI,CAAA,CAAC,4BAA4B,CAAA;qBAC3D,CAAA,IAAI,KAAK,aAAK,CAAC,MAAM,CAAA,EAArB,wBAAqB;gBAAG,qBAAM,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAA;;gBAAnC,KAAA,SAAmC,CAAA;;;gBAAG,KAAA,GAAG,CAAA;;oBAD7C,qBAAM,kBAE3B,EAAA;;gBAFU,MAAM,GAAK,CAAA,SAErB,CAAA,IAFgB;gBAIL,qBAAM,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAA;;gBAAnD,CAAG,IAAI,GAAK,CAAA,SAAuC,CAAA,KAA5C,CAA6C,CAAC;gBAE7C,qBAAM,IAAI,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,EAAA;;gBAA1C,CAAC,GAAG,SAAsC;gBAC9C,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO;oBAAE,IAAA,YAAI,EAAC,sBAAsB,CAAC,CAAC;gBAE5C,KAAK,GAAG,CAAC,CAAC;gBACV,GAAG,GAAG,CAAC,CAAC;gBACR,MAAM,GAAG,CAAC,CAAC;qBAEX,CAAA,IAAI,KAAK,aAAK,CAAC,MAAM,CAAA,EAArB,yBAAqB;sBAKX,EAAP,KAAA,EAAE,CAAC,IAAI;;;qBAAP,CAAA,cAAO,CAAA;gBAJH,WAIR,EAHC,KAAK,WAAA,EACL,oBAA+B,EAAf,OAAO,aAAA,EAAE,gBAAI,EAC7B,KAAK,WAAA;gBAEL,IAAI,KAAK,KAAK,iBAAM,CAAC,MAAM,CAAC,GAAG;oBAAE,IAAA,YAAI,EAAC,sBAAsB,CAAC,CAAC;qBAC1D,CAAA,MAAI,KAAK,KAAK,CAAA,EAAd,yBAAc;gBAAE,GAAG,GAAG,IAAA,YAAI,EAAC,KAAK,CAAC,CAAC;;;gBAEpC,KAAK,IAAI,IAAA,YAAI,EAAC,KAAK,CAAC,CAAC;qBAEjB,OAAO,EAAP,yBAAO;gBACJ,qBAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAA;;gBAAvC,IAAI,CAAC,SAAkC,CAAC,CAAC,MAAM,EAAE,CAAC;oBAChD,MAAM,IAAI,IAAA,YAAI,EAAC,KAAK,CAAC,CAAC;gBACxB,CAAC;;;gBATF,IAAO,CAAA;;;;gBAcR,OAAO,GAAG,CAAC,CAAC;;;;2BACiB,KAAA,cAAA,EAAE,CAAC,GAAG,CAAA;;;;;gBAAN,cAAM;gBAAN,WAAM;gBAAtB,gBAAI,EAAE,IAAI,UAAA,CAAE;gBACjB,qBAAM,IAAI,CAAC,iBAAiB,CAAC,MAAI,CAAC,EAAA;;gBAAxC,QAAM,SAAkC;gBACnC,qBAAM,IAAI,CAAC,oBAAoB,CAAC,KAAG,CAAC,EAAA;;gBAAzC,OAAK,SAAoC;gBAC7C,OAAO,IAAI,IAAA,YAAI,EAAC,IAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;sBAM3B,EAAP,KAAA,EAAE,CAAC,IAAI;;;qBAAP,CAAA,cAAO,CAAA;gBAHH,WAGR,EAFiB,OAAO,0BAAA,EACvB,KAAK,WAAA;gBAEL,KAAK,IAAI,IAAA,YAAI,EAAC,KAAK,CAAC,CAAC;gBACjB,qBAAM,IAAA,MAAC,EAAC,kBAAW,OAAO,CAAE,CAAC,EAAA;;gBAAjC,IAAI,SAA6B;oBAC/B,IAAA,YAAI,EAAC,iCAAiC,CAAC,CAAC;gBAErC,qBAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAA;;gBAAvC,IAAI,CAAC,SAAkC,CAAC,CAAC,MAAM;oBAAE,MAAM,IAAI,IAAA,YAAI,EAAC,KAAK,CAAC,CAAC;;;gBALpE,IAAO,CAAA;;;gBAQZ,GAAG,GAAG,OAAO,GAAG,KAAK,CAAC;;;gBAGpB,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;gBAEpB,qBAAM,IAAA,aAAK,EAAC;wBAClB,IAAI,EAAE,IAAI;wBACV,MAAM,QAAA;wBACN,GAAG,KAAA;wBACH,IAAI,MAAA;wBACJ,IAAI,MAAA;wBACJ,IAAI,MAAA;qBACL,CAAC,EAAA;;gBAPE,CAAC,GAAG,SAON;gBAEF,CAAC,CAAC,GAAG,GAAG,MAAM,CAAC;gBACf,qBAAM,IAAA,MAAC,EAAC,kBAAW,CAAC,CAAC,EAAE,CAAE,EAAE,CAAC,CAAC,EAAA;;gBAA7B,SAA6B,CAAC;gBAE9B,qBAAM,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAA;;gBAArC,SAAqC,CAAC;gBAEtC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACtB,sBAAO,CAAC,EAAC;;;gBAET,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACtB,MAAM,GAAC,CAAC;;;;KAEX,CAAC;AA1FS,QAAA,WAAW,eA0FpB;AAEF,IAAI,SAAS,GAAG,UAAC,CAAC;IAChB,OAAA,IAAI,CAAC,KAAK,CACR,CAAC,GAAG,GAAG;QACL,CAAC,CAAC,CAAC,GAAG,CAAC;QACP,CAAC,CAAC,CAAC,GAAG,IAAI;YACR,CAAC,CAAC,CAAC;YACH,CAAC,CAAC,CAAC,GAAG,KAAK;gBACT,CAAC,CAAC,CAAC,GAAG,GAAG;gBACT,CAAC,CAAC,CAAC,GAAG,MAAM;oBACV,CAAC,CAAC,CAAC,GAAG,GAAG;oBACT,CAAC,CAAC,CAAC,GAAG,OAAO;wBACX,CAAC,CAAC,CAAC,GAAG,IAAI;wBACV,CAAC,CAAC,CAAC,GAAG,IAAI,CACrB;AAZD,CAYC,CAAC;AAEJ,IAAI,QAAQ,GAAG,EAAE,CAAC;AAClB,IAAI,QAAQ,GAAG,EAAE,CAAC;AACX,IAAI,aAAa,GAAG,iEAAO,EAAkC;;;QAAhC,IAAI,UAAA,EAAE,EAAE,QAAA,EAAE,MAAM,YAAA,EAAE,MAAM,YAAA,EAAE,IAAI,UAAA;;;;;gBAE9D,IAAI,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBAAE,IAAA,YAAI,EAAC,mBAAmB,CAAC,CAAC;gBACjD,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;gBAGrB,IAAI,GAAG,EAAE,CAAC;gBAEd,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;oBAClC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;oBAC1B,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,YAAI,IAAI,KAAK,CAAC,MAAM,CAAC;wBAAE,IAAA,YAAI,EAAC,gBAAgB,CAAC,CAAC;gBAC3E,CAAC;gBAEG,UAAQ,MAAM,CAAC;gBACiB,qBAAM,YAAE,CAAC,MAAM,CAAC,EAAE,CAAC,EAAA;;gBAAjD,WAAW,IAAb,KAAgC,SAAmB,iBAAtC,EAAE,YAAY,kBAAA;gBAC/B,IAAI,WAAW;oBAAE,OAAK,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;gBAExD,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,OAAK,CAAC,CAAC;gBAE9C,IAAI,MAAM,GAAG,CAAC;oBAAE,IAAA,YAAI,EAAC,4BAA4B,CAAC,CAAC;gBAErC,qBAAM,IAAA,kBAAU,EAAC,IAAI,CAAC,EAAA;;gBAAhC,OAAO,GAAG,SAAsB;qBAEhC,OAAO,EAAP,wBAAO;gBACT,IAAI,OAAO,CAAC,GAAG,KAAK,IAAI,CAAC,EAAE;oBAAE,IAAA,YAAI,EAAC,qBAAqB,CAAC,CAAC;gBACzD,IAAI,GAAG,EAAE,CAAC;;oBAIK,qBAAM,YAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAA;;gBAA9B,IAAI,GAAK,CAAA,SAAqB,CAAA,KAA1B;gBACV,IAAI,IAAI,CAAC,IAAI,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,MAAM,KAAK,UAAU,EAAvB,CAAuB,CAAC;oBAC3C,IAAA,YAAI,EAAC,+BAA+B,CAAC,CAAC;gBAExC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,MAAM,KAAK,SAAS,EAAtB,CAAsB,CAAC;oBAC1C,IAAA,YAAI,EAAC,6BAA6B,CAAC,CAAC;gBAElC,qBAAM,IAAA,aAAK,EAAC;wBACd,IAAI,EAAE,EAAE;wBACR,MAAM,EAAE,OAAK;wBACb,GAAG,EAAE,MAAM;wBACX,IAAI,MAAA;wBACJ,IAAI,MAAA;wBACJ,IAAI,EAAE,aAAK,CAAC,SAAS;qBACtB,CAAC,EAAA;;gBAPF,GAAC,GAAG,SAOF,CAAC;gBAEC,KAAK,GAAG;;;;;gCACV,IAAI,QAAQ,CAAC,EAAE,CAAC;oCAAE,sBAAO;gCACzB,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;;;;gCAGH,qBAAM,YAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAA;;gCAA9B,SAAS,CAAA,SAAqB,CAAA,KAA1B;gCAEY,qBAAM,IAAA,MAAC,EAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;;gCAA1C,YAAY,GAAG,CAAC,CAAC,CAAC,SAAwB,CAAC;gCAC3C,aAAa,GACf,CAAC,MAAI,CAAC,MAAM,IAAI,MAAI,CAAC,KAAK,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,MAAM,KAAK,QAAQ,EAArB,CAAqB,CAAC,CAAC;qCAEvD,CAAA,YAAY,IAAI,aAAa,CAAA,EAA7B,wBAA6B;gCAC/B,qBAAM,OAAE,CAAC,GAAG,CAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;;gCAA7B,SAA6B,CAAC;gCAC9B,qBAAM,OAAE,CAAC,GAAG,CAAC,kBAAW,GAAC,CAAC,EAAE,CAAE,CAAC,EAAA;;gCAA/B,SAA+B,CAAC;gCAE5B,MAAM,GAAG,GAAC,CAAC,MAAM,IAAI,CAAC,CAAC;gCACvB,WAAS,IAAI,CAAC,KAAK,CAAC,OAAK,GAAG,iBAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;gCACrD,qBAAM,OAAE,CAAC,MAAM,CAAC,kBAAW,GAAC,CAAC,GAAG,CAAE,EAAE,OAAK,GAAG,MAAM,GAAG,MAAM,CAAC,EAAA;;gCAA5D,SAA4D,CAAC;gCAE7D,qBAAM,OAAE,CAAC,MAAM,CAAC,iBAAU,aAAK,CAAC,SAAS,cAAI,GAAC,CAAC,GAAG,CAAE,EAAE,QAAM,CAAC,EAAA;;gCAA7D,SAA6D,CAAC;gCAC9D,qBAAM,OAAE,CAAC,IAAI,CAAC,UAAG,GAAC,CAAC,GAAG,cAAW,EAAE,CAAC,EAAE,GAAC,CAAC,EAAE,CAAC,EAAA;;gCAA3C,SAA2C,CAAC;gCAE5C,aAAa,CAAC,UAAQ,CAAC,CAAC;;;;;gCAG1B,IAAA,aAAG,EAAC,2BAA2B,EAAE,GAAC,CAAC,CAAC;;;gCAGtC,QAAQ,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC;;;;qBACtB,CAAC;gBAEE,aAAW,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;;;;gBAGtC,IAAA,WAAC,EAAC,0BAA0B,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,OAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;gBAEhE,qBAAM,YAAE,CAAC,GAAG,CAAC;wBACf,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE;wBAC3C,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,GAAG,IAAI;wBACpD,MAAM,EAAE,MAAM,GAAG,IAAI;wBACrB,SAAS,EAAE,CAAC;qBACb,CAAC,EAAA;;gBALF,GAAC,GAAG,SAKF,CAAC;gBAEH,IAAI,CAAC,CAAC,GAAC,CAAC,MAAM,KAAK,UAAU,IAAI,GAAC,CAAC,gBAAgB,CAAC;oBAClD,IAAA,YAAI,EAAC,0BAA0B,CAAC,CAAC;gBAEnC,IAAA,WAAC,EAAC,mBAAmB,EAAE,GAAC,CAAC,EAAE,EAAE,GAAC,CAAC,gBAAgB,CAAC,CAAC;gBAEjD,GAAC,CAAC,MAAM,GAAG,CAAC,OAAK,CAAC;gBAClB,GAAC,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAC,CAAC,gBAAgB,GAAG,GAAC,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,CAAC;gBAChE,GAAC,CAAC,GAAG,GAAG,GAAC,CAAC,gBAAgB,CAAC;gBAE3B,qBAAM,IAAA,MAAC,EAAC,kBAAW,GAAC,CAAC,EAAE,CAAE,EAAE,GAAC,CAAC,EAAA;;gBAA7B,SAA6B,CAAC;gBAE9B,IAAA,WAAC,EAAC,eAAe,EAAE,MAAM,EAAE,GAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAC,CAAC,GAAG,EAAE,GAAC,CAAC,GAAG,CAAC,CAAC;gBACzD,qBAAM,OAAE,CAAC,MAAM,CAAC,kBAAW,GAAC,CAAC,GAAG,CAAE,EAAE,MAAM,GAAG,GAAC,CAAC,GAAG,CAAC,EAAA;;gBAAnD,SAAmD,CAAC;;;;gBAEpD,qBAAM,KAAK,EAAE,EAAA;;gBAAb,SAAa,CAAC;gBACd,MAAM,GAAC,CAAC;;gBAGV,aAAa,CAAC,UAAQ,CAAC,CAAC;;;gBAG1B,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACzB,sBAAO,GAAC,EAAC;;;gBAET,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACzB,MAAM,GAAC,CAAC;;;;KAEX,CAAC;AAnHS,QAAA,aAAa,iBAmHtB;AAEK,IAAI,OAAO,GAAG,UAAC,IAAI;IACxB,IAAI,IAAI,KAAK,aAAK,CAAC,OAAO;QAAE,OAAO,iBAAE,CAAC;SACjC,IAAI,IAAI,KAAK,aAAK,CAAC,MAAM;QAAE,OAAO,gBAAE,CAAC;;QACrC,IAAA,YAAI,EAAC,+BAA+B,CAAC,CAAC;AAC7C,CAAC,CAAC;AAJS,QAAA,OAAO,WAIhB;AAEF,IAAI,cAAc,GAAG,UAAO,CAAC;;;;;;gBAEzB,qBAAM,iBAAE,CAAC,cAAc,CAAC,CAAC,CAAC,EAAA;;gBAA1B,SAA0B,CAAC;gBAC3B,sBAAO,aAAK,CAAC,OAAO,EAAC;;;;;;gBAGnB,qBAAM,gBAAE,CAAC,cAAc,CAAC,CAAC,CAAC,EAAA;;gBAA1B,SAA0B,CAAC;gBAC3B,sBAAO,aAAK,CAAC,MAAM,EAAC;;;gBAEpB,IAAA,YAAI,EAAC,sBAAsB,CAAC,CAAC;;;;;;KAGlC,CAAC;AAEK,IAAI,KAAK,GAAG,iEAAO,EAA4C;;;QAA1C,MAAM,YAAA,EAAE,OAAO,aAAA,EAAE,OAAO,aAAA,EAAE,IAAI,UAAA,EAAE,QAAQ,cAAA;;;oBACvD,qBAAM,cAAc,CAAC,OAAO,CAAC,EAAA;;gBAApC,IAAI,GAAG,SAA6B;gBAEpC,IAAI,GAAG,IAAA,eAAO,EAAC,IAAI,CAAC,CAAC;gBACzB,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAC1B,IAAI,MAAM,GAAG,CAAC;oBAAE,IAAA,YAAI,EAAC,gBAAgB,CAAC,CAAC;gBAE5B,qBAAM,KAAK,CAAC,UAAG,aAAG,CAAC,IAAI,CAAC,sBAAmB,CAAC,CAAC,IAAI,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,IAAI,EAAE,EAAR,CAAQ,CAAC,EAAA;;gBAAzE,IAAI,GAAG,SAAkE;gBAE7E,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC;gBAC7B,CAAC;gBAED,IAAI,OAAO,GAAG,IAAI,CAAC,UAAU;oBAAE,IAAA,YAAI,EAAC,kBAAkB,CAAC,CAAC;gBAEpD,WAAW,GAAG,KAAK,CAAC;gBAEpB,IAAI,GAAG,WAAG,GAAC,OAAO,IAAG,IAAA,WAAG,EAAC,MAAM,CAAC,MAAG,CAAC;gBAExC,IAAI,IAAI,KAAK,aAAK,CAAC,MAAM;oBACvB,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,UAAC,CAAC,IAAK,OAAA,uBAAM,CAAC,KAAE,KAAK,EAAE,iBAAM,CAAC,MAAM,CAAC,GAAG,IAAG,EAApC,CAAoC,CAAC,CAAC;gBAErD,qBAAM,IAAI,CAAC,oBAAoB,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,CAAC,EAAA;;gBAA/D,GAAG,GAAG,SAAyD;gBAE1D,qBAAM,IAAI,CAAC,kBAAkB,CAAC,GAAG,EAAE;wBAC1C,QAAQ,EAAE,OAAO;wBACjB,WAAW,aAAA;wBACX,sBAAsB,EAAE,EAAE;qBAC3B,CAAC,EAAA;;gBAJE,EAAE,GAAG,SAIP;gBAEE,GAAG,GAAG,IAAA,YAAI,EAAC,EAAE,CAAC,GAAG,CAAC,CAAC;gBACnB,IAAI,GAAG,GAAG,CAAC;gBAED,qBAAM,IAAA,MAAC,EAAC,kBAAW,IAAI,CAAC,EAAE,CAAE,CAAC,EAAA;;gBAAvC,OAAO,GAAG,SAA6B;gBACvC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,iBAAM,CAAC,GAAG,CAAC,CAAC;gBAChC,qBAAM,IAAA,MAAC,EAAC,iBAAU,IAAI,cAAI,IAAI,CAAC,EAAE,CAAE,CAAC,EAAA;;gBAA7C,MAAM,GAAG,SAAoC;gBAC7C,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBACvC,MAAM,IAAI,OAAO,CAAC;qBAEd,CAAA,QAAQ,IAAI,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,OAAO,CAAA,EAA3C,wBAA2C;gBAC7C,IAAI,MAAM,IAAI,GAAG,GAAG,MAAM,GAAG,IAAI;oBAC/B,IAAA,YAAI,EACF,yCAAwB,OAAO,6BAAS,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,IAAI,CAAE,CACvE,CAAC;gBAEJ,IAAI,GAAG,WAAG,GAAC,OAAO,IAAG,IAAA,WAAG,EAAC,MAAM,GAAG,MAAM,CAAC,MAAG,CAAC;gBACvC,qBAAM,IAAI,CAAC,oBAAoB,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,CAAC,EAAA;;gBAA/D,GAAG,GAAG,SAAyD,CAAC;gBAE3D,qBAAM,IAAI,CAAC,kBAAkB,CAAC,GAAG,EAAE;wBACtC,QAAQ,EAAE,OAAO;wBACjB,WAAW,aAAA;wBACX,sBAAsB,EAAE,CAAC,CAAC,CAAC;qBAC5B,CAAC,EAAA;;gBAJF,EAAE,GAAG,SAIH,CAAC;gBAEH,GAAG,GAAG,IAAA,YAAI,EAAC,EAAE,CAAC,GAAG,CAAC,CAAC;;oBAGrB,sBAAO,EAAE,OAAO,SAAA,EAAE,MAAM,QAAA,EAAE,GAAG,KAAA,EAAE,IAAI,MAAA,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAC;;;KACpD,CAAC;AA1DS,QAAA,KAAK,SA0Dd;AAEK,IAAI,OAAO,GAAG;;;;;;gBAEb,IAAI,GAAG,EAAE,CAAC;oCACJ,IAAI,EAAE,CAAC;;;;;qCACf,CAAA,KAAA,IAAI,CAAC,IAAI,CAAA;sCAAT,IAAI;gCACE,qBAAM,CAAC,CAAC,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,EAAA;;gCADxC,wBACK,CAAC,SAAkC,CAAC,CAAC,MAAM,CAAC,UAAC,EAAE;wCAChD,EAAE,CAAC,IAAI,GAAG,IAAI,CAAC;wCACf,OAAO,EAAE,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,CAAC,aAAa,GAAG,CAAC,CAAC;oCAC3D,CAAC,CAAC,IACF;;;;;sBAN6D,EAA3C,KAAA,MAAM,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,iBAAE,EAAE,MAAM,EAAE,gBAAE,EAAE,CAAC;;;qBAA3C,CAAA,cAA2C,CAAA;gBAAxD,WAAS,EAAR,IAAI,QAAA,EAAE,CAAC,QAAA;8CAAP,IAAI,EAAE,CAAC;;;;;gBAAK,IAA2C,CAAA;;;sBASlC,EAAJ,aAAI;;;qBAAJ,CAAA,kBAAI,CAAA;gBAAtB,eAAc,EAAZ,IAAI,UAAA,EAAE,IAAI,UAAA;;;;gBAEb,qBAAM,OAAE,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,EAAA;;gBAAjC,IAAI,SAA6B;oBAAE,yBAAS;gBAC5C,qBAAM,aAAG,CAAC,IAAI,CAAC,2BAAoB,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,aAAU,EAAE;wBACrE,IAAI,EAAE,EAAE,IAAI,MAAA,EAAE,MAAM,EAAE,iBAAM,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,MAAA,EAAE;qBACpD,CAAC,EAAA;;gBAFF,SAEE,CAAC;gBAEH,qBAAM,OAAE,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAA;;gBAAzD,SAAyD,CAAC;gBACrD,qBAAM,OAAE,CAAC,KAAK,CAAC,MAAM,CAAC,EAAA;;qBAAvB,CAAA,CAAC,SAAsB,CAAC,GAAG,KAAK,CAAA,EAAhC,yBAAgC;gBAClC,qBAAM,OAAE,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,EAAA;;gBAAtC,SAAsC,CAAC;;;;;gBAEzC,IAAA,aAAG,EAAC,oBAAoB,EAAE,IAAC,CAAC,OAAO,CAAC,CAAC;;;gBAXd,IAAI,CAAA;;;;;gBAe/B,IAAA,aAAG,EAAC,iBAAiB,EAAE,IAAC,CAAC,OAAO,CAAC,CAAC;;;gBAGpC,UAAU,CAAC,eAAO,EAAE,IAAI,CAAC,CAAC;;;;KAC3B,CAAC;AA/BS,QAAA,OAAO,WA+BhB"} \ No newline at end of file diff --git a/lib/payments.ts b/lib/payments.ts new file mode 100644 index 00000000..92e17839 --- /dev/null +++ b/lib/payments.ts @@ -0,0 +1,682 @@ +import locales from "$lib/locales/index"; +import config from "$config"; +import { emit } from "$lib/sockets"; +import { v4 } from "uuid"; +import { notify } from "$lib/notifications"; +import { db, g, s, t } from "$lib/db"; +import { l, err, warn } from "$lib/logging"; +import { + f, + btc, + fail, + fiat, + getInvoice, + getUser, + sleep, + wait, + SATS, + sats, + formatReceipt, +} from "$lib/utils"; +import { callWebhook } from "$lib/webhooks"; +import got from "got"; +import { mqtt1, mqtt2 } from "$lib/mqtt"; +import { mail, templates } from "$lib/mail"; +import api from "$lib/api"; + +import lq from "$lib/liquid"; +import bc from "$lib/bitcoin"; +import ln from "$lib/ln"; + +let { URL } = process.env; + +export let types = { + internal: "internal", + bitcoin: "bitcoin", + lightning: "lightning", + fund: "fund", + liquid: "liquid", + ecash: "ecash", +}; + +export let debit = async ({ + hash, + amount, + fee = 0, + memo, + user, + type = types.internal, + rate, +}) => { + amount = parseInt(amount); + if (type !== types.internal && (await g("freeze"))) + fail("Problem sending payment"); + // if (!user.unlimited && amount > 1000000) + // fail(`⚡️${amount} exceeds max withdrawal of ⚡️1,000,000`); + let ref; + let { id: uid, currency, username } = user; + + let rates = await g('rates'); + if (!rate) rate = rates[currency]; + + let invoice = await getInvoice(hash); + let iid; + + if (invoice) { + if (invoice.received >= amount) fail("Invoice already paid"); + ({ id: iid } = invoice); + + ref = invoice.uid; + + let equivalentRate = + invoice.rate * (rates[currency] / rates[invoice.currency]); + + if (Math.abs(invoice.rate / rates[invoice.currency] - 1) < 0.01) { + rate = equivalentRate; + } else { + warn("rate slipped", hash, invoice.rate, equivalentRate); + } + } + + let tip = parseInt(invoice?.tip) || null; + if (tip < 0) fail("Invalid tip"); + + if (!amount || amount < 0) fail("Amount must be greater than zero"); + + let ourfee = [types.bitcoin, types.liquid, types.lightning].includes(type) + ? Math.round((amount + fee + tip) * config.fee) + : 0; + + ourfee = await db.debit( + `balance:${uid}`, + `credit:${type}:${uid}`, + amount || 0, + tip || 0, + fee || 0, + ourfee || 0, + ); + + if (ourfee.err) fail(ourfee.err); + + let id = v4(); + let p = { + id, + amount: -amount, + fee, + hash, + ourfee, + memo, + iid, + uid, + confirmed: ![types.bitcoin, types.liquid].includes(type), + rate, + currency, + type, + ref, + tip, + created: Date.now(), + }; + + await s(`payment:${hash}`, id); + await s(`payment:${id}`, p); + await db.lPush(`${uid}:payments`, id); + + l(user.username, "sent", type, amount); + //emit(user.id, "payment", p); + + return p; +}; + +export let credit = async (hash, amount, memo, ref, type = types.internal) => { + amount = parseInt(amount) || 0; + + let inv = await getInvoice(hash); + if (!inv) { + await db.sAdd("missing", ref.split(":")[0]); + return; + } + + let { tip } = inv; + tip = parseInt(tip) || 0; + + if (!memo) ({ memo } = inv); + if (memo && memo.length > 5000) fail("memo too long"); + if (amount < 0 || tip < 0) fail("Invalid amount"); + if (type === types.internal) amount += tip; + + let user = await g(`user:${inv.uid}`); + let { id: uid, currency, username } = user; + + let rates = await g('rates'); + let rate = rates[currency]; + + if (!rate) await sleep(1000); + rate = rates[currency]; + + let equivalentRate = + inv.rate * (rates[currency] / rates[inv.currency]); + + if (Math.abs(inv.rate / rates[inv.currency] - 1) < 0.01) { + rate = equivalentRate; + } else { + // warn("rate slipped", hash, invoice.rate, equivalentRate); + } + + let id = v4(); + let p = { + id, + iid: inv.id, + hash, + amount: parseInt(amount - tip), + uid, + rate, + currency, + memo, + ref, + tip, + type, + confirmed: true, + created: Date.now(), + }; + + if ([types.bitcoin, types.liquid].includes(type)) inv.pending += amount; + else { + inv.received += amount; + inv.preimage = ref; + } + + let balanceKey = "balance"; + if ([types.bitcoin, types.liquid].includes(type)) { + let [txid, vout] = ref.split(":").slice(-2); + p.confirmed = false; + balanceKey = "pending"; + await s(`payment:${txid}:${vout}`, id); + } else { + await s(`payment:${hash}`, id); + } + + let m = await db.multi(); + + if ([types.bitcoin, types.liquid, types.lightning].includes(type)) + m.incrBy(`credit:${type}:${uid}`, Math.round(amount * config.fee)); + + m.set(`invoice:${inv.id}`, JSON.stringify(inv)) + .set(`payment:${p.id}`, JSON.stringify(p)) + .lPush(`${uid}:payments`, p.id) + .incrBy(`${balanceKey}:${uid}`, amount) + .exec(); + + emit(uid, "payment", p); + notify(user, `Received ${amount} sats`); + callWebhook(inv, p); + l(username, "received", type, amount); + + let items; + if (inv.items && inv.items.length) { + formatReceipt(inv.items, inv.currency); + p.items = inv.items; + } + + if (config.mqtt1) { + mqtt1.publish( + username, + `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${items}`, + ); + } + + if (config.mqtt2) { + mqtt2.publish( + username, + `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${items}`, + ); + } + + try { + await completePayment(p, user); + } catch (e) { + console.log(e); + } + + return p; +}; + +let fmt = (sats) => + "⚡️" + + new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(sats); + +export let completePayment = async (p, user) => { + let { id, autowithdraw, threshold, reserve, destination, username } = user; + let link = (id) => `${URL}/payment/${id}`; + if (p.confirmed) { + let withdrawal; + if (autowithdraw) { + try { + let to = destination.trim(); + let balance = await g(`balance:${id}`); + let amount = balance - reserve; + if (balance > threshold) { + l("initiating autowithdrawal", amount, to, balance, threshold); + let w = await pay({ amount, to, user }); + withdrawal = { + amount: fmt(-w.amount), + link: link(w.id), + }; + } + } catch (e) { + withdrawal = { failed: true }; + warn(username, "autowithdraw failed", e.message); + } + } + + let t = locales[user.language || "en"]; + if (user.verified && user.notify) { + mail(user, t.paymentReceived, templates.paymentReceived, { + ...t, + username, + payment: { + amount: fmt(p.amount), + link: link(p.id), + tip: p.tip ? fmt(p.tip) : undefined, + fiat: f(fiat(p.amount, p.rate), p.currency), + fiatTip: p.tip ? f(fiat(p.tip, p.rate), p.currency) : undefined, + memo: p.memo, + items: + p.items && + p.items.map((i) => { + return { + quantity: i.quantity, + name: i.name, + total: i.quantity * i.price, + totalFiat: f(i.quantity * i.price, p.currency), + }; + }), + }, + withdrawal, + }); + } + } +}; + +let pay = async ({ amount, to, user }) => { + amount = parseInt(amount) || 0; + let lnurl, pr; + if (to.includes("@") && to.includes(".")) { + let [name, domain] = to.split("@"); + lnurl = `https://${domain}/.well-known/lnurlp/${name}`; + } else if (to.startsWith("lnurl")) { + lnurl = Buffer.from( + bech32.fromWords(bech32.decode(text, 20000).words), + ).toString(); + } + + let maxfee = getMaxFee(amount); + if (lnurl) { + amount -= maxfee; + let { callback } = await got(lnurl).json(); + ({ pr } = await got(`${callback}?amount=${amount * 1000}`).json()); + } else if (to.startsWith("ln")) { + amount -= maxfee; + pr = to; + } + + return pr + ? await sendLightning({ user, pr, amount, maxfee }) + : await sendOnchain({ amount, address: to, user, subtract: true }); +}; + +export let decode = async (hex) => { + let type, tx; + try { + tx = await bc.decodeRawTransaction(hex); + type = types.bitcoin; + } catch (e) { + try { + tx = await lq.decodeRawTransaction(hex); + type = types.liquid; + } catch (e) { + err("invalid hex", hex); + fail("unrecognized tx"); + } + } + + return { tx, type }; +}; + +export let sendOnchain = async (params) => { + let { hex, rate, user } = params; + if (!hex) ({ hex } = await build(params)); + + let { tx, type } = await decode(hex); + let node = getNode(type); + let { txid } = tx; + + try { + if (inflight[txid]) fail("payment in flight"); + inflight[txid] = true; + + if (config[type].walletpass) + await node.walletPassphrase(config[type].walletpass, 300); + + let { hex: signed } = await node.signRawTransactionWithWallet( + type === types.liquid ? await node.blindRawTransaction(hex) : hex, + ); + + ({ txid } = await node.decodeRawTransaction(signed)); + + let r = await node.testMempoolAccept([signed]); + if (!r[0].allowed) fail("transaction rejected"); + + let total = 0; + let fee = 0; + let change = 0; + + if (type === types.liquid) { + for (let { + asset, + scriptPubKey: { address, type }, + value, + } of tx.vout) { + if (asset !== config.liquid.btc) fail("only L-BTC supported"); + if (type === "fee") fee = sats(value); + else { + total += sats(value); + + if (address) { + if ((await node.getAddressInfo(address)).ismine) { + change += sats(value); + } + } + } + } + } else { + let totalIn = 0; + for await (let { txid, vout } of tx.vin) { + let hex = await node.getRawTransaction(txid); + let tx = await node.decodeRawTransaction(hex); + totalIn += sats(tx.vout[vout].value); + } + + for (let { + scriptPubKey: { address }, + value, + } of tx.vout) { + total += sats(value); + if (await g(`invoice:${address}`)) + fail("Cannot send to internal address"); + + if ((await node.getAddressInfo(address)).ismine) change += sats(value); + } + + fee = totalIn - total; + } + + let amount = total - change; + + let p = await debit({ + hash: txid, + amount, + fee, + rate, + user, + type, + }); + + p.hex = signed; + await s(`payment:${p.id}`, p); + + await node.sendRawTransaction(signed); + + delete inflight[txid]; + return p; + } catch (e) { + delete inflight[txid]; + throw e; + } +}; + +let getMaxFee = (n) => + Math.round( + n < 100 + ? n * 5 + : n < 1000 + ? n + : n < 10000 + ? n * 0.5 + : n < 100000 + ? n * 0.1 + : n < 1000000 + ? n * 0.05 + : n * 0.01, + ); + +let checking = {}; +let inflight = {}; +export let sendLightning = async ({ user, pr, amount, maxfee, memo }) => { + try { + if (inflight[user.id]) fail("payment in flight"); + inflight[user.id] = true; + + let p; + let hash = pr; + + if (typeof amount !== "undefined") { + amount = parseInt(amount); + if (amount < 0 || amount > SATS || isNaN(amount)) fail("Invalid amount"); + } + + let total = amount; + let { amount_msat, payment_hash } = await ln.decode(pr); + if (amount_msat) total = Math.round(amount_msat / 1000); + + maxfee = parseInt(maxfee) || getMaxFee(total); + + if (maxfee < 0) fail("Max fee cannot be negative"); + + let invoice = await getInvoice(hash); + + if (invoice) { + if (invoice.uid === user.id) fail("Cannot send to self"); + hash = pr; + } else { + let r; + + let { pays } = await ln.listpays(pr); + if (pays.find((p) => p.status === "complete")) + fail("Invoice has already been paid"); + + if (pays.find((p) => p.status === "pending")) + fail("Payment is already underway"); + + p = await debit({ + hash: pr, + amount: total, + fee: maxfee, + memo, + user, + type: types.lightning, + }); + + let check = async () => { + if (checking[pr]) return; + checking[pr] = true; + + try { + let { pays } = await ln.listpays(pr); + + let recordExists = !!(await g(`payment:${pr}`)); + let paymentFailed = + !pays.length || pays.every((p) => p.status === "failed"); + + if (recordExists && paymentFailed) { + await db.del(`payment:${pr}`); + await db.del(`payment:${p.id}`); + + let ourfee = p.ourfee || 0; + let credit = Math.round(total * config.fee) - ourfee; + await db.incrBy(`balance:${p.uid}`, total + maxfee + ourfee); + + await db.incrBy(`credit:${types.lightning}:${p.uid}`, credit); + await db.lRem(`${p.uid}:payments`, 1, p.id); + + clearInterval(interval); + } + } catch (e) { + err("Failed to reverse payment", r); + } + + checking[pr] = false; + }; + + let interval = setInterval(check, 5000); + + try { + l("paying lightning invoice", pr.substr(-8), total, amount, maxfee); + + r = await ln.pay({ + bolt11: pr.replace(/\s/g, "").toLowerCase(), + amount_msat: amount_msat ? undefined : amount * 1000, + maxfee: maxfee * 1000, + retry_for: 5, + }); + + if (!(r.status === "complete" && r.payment_preimage)) + fail("Payment did not complete"); + + l("payment completed", p.id, r.payment_preimage); + + p.amount = -total; + p.fee = Math.round((r.amount_sent_msat - r.amount_msat) / 1000); + p.ref = r.payment_preimage; + + await s(`payment:${p.id}`, p); + + l("refunding fee", maxfee, p.fee, maxfee - p.fee, p.ref); + await db.incrBy(`balance:${p.uid}`, maxfee - p.fee); + } catch (e) { + await check(); + throw e; + } + + clearInterval(interval); + } + + delete inflight[user.id]; + return p; + } catch (e) { + delete inflight[user.id]; + throw e; + } +}; + +export let getNode = (type) => { + if (type === types.bitcoin) return bc; + else if (type === types.liquid) return lq; + else fail("unrecognized transaction type"); +}; + +let getAddressType = async (a) => { + try { + await bc.getAddressInfo(a); + return types.bitcoin; + } catch (e) { + try { + await lq.getAddressInfo(a); + return types.liquid; + } catch (e) { + fail("unrecognized address"); + } + } +}; + +export let build = async ({ amount, address, feeRate, user, subtract }) => { + let type = await getAddressType(address); + + let node = getNode(type); + amount = parseInt(amount); + if (amount < 0) fail("invalid amount"); + + let fees = await fetch(`${api[type]}/fees/recommended`).then((r) => r.json()); + + if (!feeRate) { + feeRate = fees.halfHourFee; + } + + if (feeRate < fees.economyFee) fail("fee rate too low"); + + let replaceable = false; + + let outs = [{ [address]: btc(amount) }]; + + if (type === types.liquid) + outs = outs.map((o) => ({ ...o, asset: config.liquid.btc })); + + let raw = await node.createRawTransaction([], outs, 0, replaceable); + + let tx = await node.fundRawTransaction(raw, { + fee_rate: feeRate, + replaceable, + subtractFeeFromOutputs: [], + }); + + let fee = sats(tx.fee); + let dust = 547; + + let balance = await g(`balance:${user.id}`); + let ourfee = Math.round(amount * config.fee); + let credit = await g(`credit:${type}:${user.id}`); + let covered = Math.min(credit, ourfee); + ourfee -= covered; + + if (subtract || amount + fee + ourfee > balance) { + if (amount <= fee + ourfee + dust) + fail( + `insufficient funds ⚡️${balance} of ⚡️${amount + fee + ourfee + dust}`, + ); + + outs = [{ [address]: btc(amount - ourfee) }]; + raw = await node.createRawTransaction([], outs, 0, replaceable); + + tx = await node.fundRawTransaction(raw, { + fee_rate: feeRate, + replaceable, + subtractFeeFromOutputs: [0], + }); + + fee = sats(tx.fee); + } + + return { feeRate, ourfee, fee, fees, hex: tx.hex }; +}; + +export let catchUp = async () => { + try { + let txns = []; + for (let [type, n] of Object.entries({ bitcoin: bc, liquid: lq })) { + txns.push( + ...(await n.listTransactions("*", 200)).filter((tx) => { + tx.type = type; + return tx.category === "receive" && tx.confirmations > 0; + }), + ); + } + + for (let { txid, type } of txns) { + try { + if (await db.zScore("seen", txid)) continue; + await got.post(`http://localhost:${process.env.PORT || 3119}/confirm`, { + json: { txid, wallet: config.bitcoin.wallet, type }, + }); + + await db.zAdd("seen", { score: Date.now(), value: txid }); + if ((await db.zCard("seen")) > 10000) + await db.zRemRangeByRank("seen", 0, 0); + } catch (e) { + err("problem confirming", e.message); + } + } + } catch (e) { + err("problem syncing", e.message); + } + + setTimeout(catchUp, 2000); +}; diff --git a/lib/rates.js b/lib/rates.js deleted file mode 100644 index fe942453..00000000 --- a/lib/rates.js +++ /dev/null @@ -1,71 +0,0 @@ -import got from "got"; -import config from "$config"; -import store from "$lib/store"; -import { err, warn } from "$lib/logging"; -import { g, s } from "$lib/db"; -import { sleep } from "$lib/utils"; -import WebSocket from "ws"; - -let last; -let ws; -let connect = async () => { - if (ws && ws.readyState === 1 && Date.now() - last < 5000) return; - if (ws) ws.terminate() && (await sleep(Math.round(Math.random() * 1000))); - - ws = new WebSocket("wss://stream.binance.com:9443/ws/btcusdt@ticker"); - - ws.onmessage = async function (event) { - try { - let msg = JSON.parse(event.data); - let rates = await g("rates"); - let fx = await g("fx"); - if (fx) ({ fx } = fx); - - Object.keys(fx).map((symbol) => { - rates[symbol] = msg.c * fx[symbol]; - }); - - s("rate", msg.c); - s("rates", rates); - last = Date.now(); - } catch (e) { - console.log(e); - err("binance message error", e.message); - } - }; - - ws.onerror = async function (error) { - err("binance socket error", error.message); - }; - - return ws; -}; - -export let getFx = async () => { - connect(); - - let date = 0, - fx = await g("fx"); - if (fx) ({ date, fx } = fx); - - if (Date.now() - date > 24 * 60 * 60 * 1000) { - date = Date.now(); - try { - let r = await got( - `http://data.fixer.io/api/latest?access_key=${config.fixer}`, - ).json(); - let { rates: fx } = r; - let USD = fx["USD"]; - - Object.keys(fx).map((k) => { - fx[k] = fx[k] / USD; - }); - - await s("fx", { date, fx }); - } catch (e) { - err("error fetching rates", e.message); - } - } - - setTimeout(getFx, 30000); -}; diff --git a/lib/rates.js.map b/lib/rates.js.map new file mode 100644 index 00000000..11dc6f05 --- /dev/null +++ b/lib/rates.js.map @@ -0,0 +1 @@ +{"version":3,"file":"rates.js","sourceRoot":"","sources":["rates.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/rates.ts b/lib/rates.ts new file mode 100644 index 00000000..fe942453 --- /dev/null +++ b/lib/rates.ts @@ -0,0 +1,71 @@ +import got from "got"; +import config from "$config"; +import store from "$lib/store"; +import { err, warn } from "$lib/logging"; +import { g, s } from "$lib/db"; +import { sleep } from "$lib/utils"; +import WebSocket from "ws"; + +let last; +let ws; +let connect = async () => { + if (ws && ws.readyState === 1 && Date.now() - last < 5000) return; + if (ws) ws.terminate() && (await sleep(Math.round(Math.random() * 1000))); + + ws = new WebSocket("wss://stream.binance.com:9443/ws/btcusdt@ticker"); + + ws.onmessage = async function (event) { + try { + let msg = JSON.parse(event.data); + let rates = await g("rates"); + let fx = await g("fx"); + if (fx) ({ fx } = fx); + + Object.keys(fx).map((symbol) => { + rates[symbol] = msg.c * fx[symbol]; + }); + + s("rate", msg.c); + s("rates", rates); + last = Date.now(); + } catch (e) { + console.log(e); + err("binance message error", e.message); + } + }; + + ws.onerror = async function (error) { + err("binance socket error", error.message); + }; + + return ws; +}; + +export let getFx = async () => { + connect(); + + let date = 0, + fx = await g("fx"); + if (fx) ({ date, fx } = fx); + + if (Date.now() - date > 24 * 60 * 60 * 1000) { + date = Date.now(); + try { + let r = await got( + `http://data.fixer.io/api/latest?access_key=${config.fixer}`, + ).json(); + let { rates: fx } = r; + let USD = fx["USD"]; + + Object.keys(fx).map((k) => { + fx[k] = fx[k] / USD; + }); + + await s("fx", { date, fx }); + } catch (e) { + err("error fetching rates", e.message); + } + } + + setTimeout(getFx, 30000); +}; diff --git a/lib/register.js b/lib/register.js deleted file mode 100644 index 49111d96..00000000 --- a/lib/register.js +++ /dev/null @@ -1,62 +0,0 @@ -import { got } from "got"; -import { authenticator } from "otplib"; -import { v4 } from "uuid"; - -import config from "$config"; -import countries from "$lib/countries"; -import { s, g, archive } from "$lib/db"; -import { l, warn } from "$lib/logging"; -import { fail, getUser } from "$lib/utils"; - -let valid = /^[\p{L}\p{N}]{2,24}$/u; -export default async (user, ip, requireChallenge) => { - let { profile, password, pubkey, username } = user; - l("registering", username); - - if (!username) fail("Username required"); - username = username.toLowerCase(); - if (!valid.test(username)) - fail("Usernames can only have letters and numbers"); - - let id = v4(); - user.id = id; - - let exists = await getUser(username); - if (exists) fail(`Username ${username} taken`); - - if (password) { - user.password = await Bun.password.hash(password, { - algorithm: "bcrypt", - cost: 1, - }); - } - - user.currency = "USD"; - if (config.ipregistry) { - try { - let { - location: { country: { code } }, - } = await got( - `https://api.ipregistry.co/${ip}?key=${config.ipregistry}&fields=location.country.code`, - ).json(); - - user.currency = countries[code]; - } catch (e) { - warn("unable to detect country from IP", username); - } - } - - user.currencies = [...new Set([user.currency, "CAD", "USD"])]; - user.fiat = false; - user.otpsecret = authenticator.generateSecret(); - user.migrated = true; - user.locktime = 300; - - await s(`user:${id}`, user); - await s(`user:${username}`, id); - await s(`user:${pubkey}`, id); - await s(`balance:${id}`, 0); - - l("new user", username); - return user; -}; diff --git a/lib/register.js.map b/lib/register.js.map new file mode 100644 index 00000000..beb6b80a --- /dev/null +++ b/lib/register.js.map @@ -0,0 +1 @@ +{"version":3,"file":"register.js","sourceRoot":"","sources":["register.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2BAA0B;AAC1B,iCAAuC;AACvC,6BAA0B;AAE1B,oDAA6B;AAC7B,6DAAuC;AACvC,8BAAwC;AACxC,wCAAuC;AACvC,oCAA2C;AAE3C,IAAI,KAAK,GAAG,uBAAuB,CAAC;AACpC,mBAAe,UAAO,IAAI,EAAE,EAAE,EAAE,gBAAgB;;;;;gBACxC,OAAO,GAAiC,IAAI,QAArC,EAAE,QAAQ,GAAuB,IAAI,SAA3B,EAAE,MAAM,GAAe,IAAI,OAAnB,EAAE,QAAQ,GAAK,IAAI,SAAT,CAAU;gBACnD,IAAA,WAAC,EAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;gBAE3B,IAAI,CAAC,QAAQ;oBAAE,IAAA,YAAI,EAAC,mBAAmB,CAAC,CAAC;gBACzC,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;gBAClC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;oBACvB,IAAA,YAAI,EAAC,6CAA6C,CAAC,CAAC;gBAElD,EAAE,GAAG,IAAA,SAAE,GAAE,CAAC;gBACd,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;gBAEA,qBAAM,IAAA,eAAO,EAAC,QAAQ,CAAC,EAAA;;gBAAhC,MAAM,GAAG,SAAuB;gBACpC,IAAI,MAAM;oBAAE,IAAA,YAAI,EAAC,mBAAY,QAAQ,WAAQ,CAAC,CAAC;qBAE3C,QAAQ,EAAR,wBAAQ;gBACV,KAAA,IAAI,CAAA;gBAAY,qBAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE;wBAChD,SAAS,EAAE,QAAQ;wBACnB,IAAI,EAAE,CAAC;qBACR,CAAC,EAAA;;gBAHF,GAAK,QAAQ,GAAG,SAGd,CAAC;;;gBAGL,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;qBAClB,iBAAM,CAAC,UAAU,EAAjB,wBAAiB;;;;gBAIb,qBAAM,IAAA,SAAG,EACX,oCAA6B,EAAE,kBAAQ,iBAAM,CAAC,UAAU,kCAA+B,CACxF,CAAC,IAAI,EAAE,EAAA;;gBAHiB,IAAI,GACzB,CAAA,SAEI,CAAA,sBAHqB;gBAK7B,IAAI,CAAC,QAAQ,GAAG,mBAAS,CAAC,IAAI,CAAC,CAAC;;;;gBAEhC,IAAA,cAAI,EAAC,kCAAkC,EAAE,QAAQ,CAAC,CAAC;;;gBAIvD,IAAI,CAAC,UAAU,qBAAO,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,OAAC,CAAC;gBAC9D,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;gBAClB,IAAI,CAAC,SAAS,GAAG,sBAAa,CAAC,cAAc,EAAE,CAAC;gBAChD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;gBAEpB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;gBAA3B,SAA2B,CAAC;gBAC5B,qBAAM,IAAA,MAAC,EAAC,eAAQ,QAAQ,CAAE,EAAE,EAAE,CAAC,EAAA;;gBAA/B,SAA+B,CAAC;gBAChC,qBAAM,IAAA,MAAC,EAAC,eAAQ,MAAM,CAAE,EAAE,EAAE,CAAC,EAAA;;gBAA7B,SAA6B,CAAC;gBAC9B,qBAAM,IAAA,MAAC,EAAC,kBAAW,EAAE,CAAE,EAAE,CAAC,CAAC,EAAA;;gBAA3B,SAA2B,CAAC;gBAE5B,IAAA,WAAC,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;gBACxB,sBAAO,IAAI,EAAC;;;KACb,EAAC"} \ No newline at end of file diff --git a/lib/register.ts b/lib/register.ts new file mode 100644 index 00000000..49111d96 --- /dev/null +++ b/lib/register.ts @@ -0,0 +1,62 @@ +import { got } from "got"; +import { authenticator } from "otplib"; +import { v4 } from "uuid"; + +import config from "$config"; +import countries from "$lib/countries"; +import { s, g, archive } from "$lib/db"; +import { l, warn } from "$lib/logging"; +import { fail, getUser } from "$lib/utils"; + +let valid = /^[\p{L}\p{N}]{2,24}$/u; +export default async (user, ip, requireChallenge) => { + let { profile, password, pubkey, username } = user; + l("registering", username); + + if (!username) fail("Username required"); + username = username.toLowerCase(); + if (!valid.test(username)) + fail("Usernames can only have letters and numbers"); + + let id = v4(); + user.id = id; + + let exists = await getUser(username); + if (exists) fail(`Username ${username} taken`); + + if (password) { + user.password = await Bun.password.hash(password, { + algorithm: "bcrypt", + cost: 1, + }); + } + + user.currency = "USD"; + if (config.ipregistry) { + try { + let { + location: { country: { code } }, + } = await got( + `https://api.ipregistry.co/${ip}?key=${config.ipregistry}&fields=location.country.code`, + ).json(); + + user.currency = countries[code]; + } catch (e) { + warn("unable to detect country from IP", username); + } + } + + user.currencies = [...new Set([user.currency, "CAD", "USD"])]; + user.fiat = false; + user.otpsecret = authenticator.generateSecret(); + user.migrated = true; + user.locktime = 300; + + await s(`user:${id}`, user); + await s(`user:${username}`, id); + await s(`user:${pubkey}`, id); + await s(`balance:${id}`, 0); + + l("new user", username); + return user; +}; diff --git a/lib/rpc.js b/lib/rpc.js deleted file mode 100644 index bb57d075..00000000 --- a/lib/rpc.js +++ /dev/null @@ -1,28 +0,0 @@ -import got from "got"; - -export default ({ host, port, wallet, username, password }) => - new Proxy( - {}, - { - get: - (target, prop) => - (...params) => - ((method, ...params) => { - let url = `http://${host}:${port}/wallet/${wallet}`; - return got - .post(url, { - json: { - method, - params, - }, - headers: { - authorization: `Basic ${Buffer.from( - `${username}:${password}`, - ).toString("base64")}`, - }, - }) - .json() - .then(({ result }) => result); - })(prop.toLowerCase(), ...params), - }, - ); diff --git a/lib/rpc.js.map b/lib/rpc.js.map new file mode 100644 index 00000000..d3e67844 --- /dev/null +++ b/lib/rpc.js.map @@ -0,0 +1 @@ +{"version":3,"file":"rpc.js","sourceRoot":"","sources":["rpc.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,4CAAsB;AAEtB,mBAAe,UAAC,EAA0C;QAAxC,IAAI,UAAA,EAAE,IAAI,UAAA,EAAE,MAAM,YAAA,EAAE,QAAQ,cAAA,EAAE,QAAQ,cAAA;IACtD,OAAA,IAAI,KAAK,CACP,EAAE,EACF;QACE,GAAG,EACD,UAAC,MAAM,EAAE,IAAI;YACb,OAAA;gBAAC,gBAAS;qBAAT,UAAS,EAAT,qBAAS,EAAT,IAAS;oBAAT,2BAAS;;gBACR,OAAA,CAAC,UAAC,MAAM;oBAAE,gBAAS;yBAAT,UAAS,EAAT,qBAAS,EAAT,IAAS;wBAAT,+BAAS;;oBACjB,IAAI,GAAG,GAAG,iBAAU,IAAI,cAAI,IAAI,qBAAW,MAAM,CAAE,CAAC;oBACpD,OAAO,aAAG;yBACP,IAAI,CAAC,GAAG,EAAE;wBACT,IAAI,EAAE;4BACJ,MAAM,QAAA;4BACN,MAAM,QAAA;yBACP;wBACD,OAAO,EAAE;4BACP,aAAa,EAAE,gBAAS,MAAM,CAAC,IAAI,CACjC,UAAG,QAAQ,cAAI,QAAQ,CAAE,CAC1B,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAE;yBACvB;qBACF,CAAC;yBACD,IAAI,EAAE;yBACN,IAAI,CAAC,UAAC,EAAU;4BAAR,MAAM,YAAA;wBAAO,OAAA,MAAM;oBAAN,CAAM,CAAC,CAAC;gBAClC,CAAC,CAAC,8BAAC,IAAI,CAAC,WAAW,EAAE,GAAK,MAAM;YAhBhC,CAgBiC;QAjBnC,CAiBmC;KACtC,CACF;AAxBD,CAwBC,EAAC"} \ No newline at end of file diff --git a/lib/rpc.ts b/lib/rpc.ts new file mode 100644 index 00000000..bb57d075 --- /dev/null +++ b/lib/rpc.ts @@ -0,0 +1,28 @@ +import got from "got"; + +export default ({ host, port, wallet, username, password }) => + new Proxy( + {}, + { + get: + (target, prop) => + (...params) => + ((method, ...params) => { + let url = `http://${host}:${port}/wallet/${wallet}`; + return got + .post(url, { + json: { + method, + params, + }, + headers: { + authorization: `Basic ${Buffer.from( + `${username}:${password}`, + ).toString("base64")}`, + }, + }) + .json() + .then(({ result }) => result); + })(prop.toLowerCase(), ...params), + }, + ); diff --git a/lib/sockets.js b/lib/sockets.js deleted file mode 100644 index b1fc719c..00000000 --- a/lib/sockets.js +++ /dev/null @@ -1,165 +0,0 @@ -import config from "$config"; -import store from "$lib/store"; -import jwt from "jsonwebtoken"; -import { v4 } from "uuid"; -import whitelist from "$lib/whitelist"; -import { l, err, warn } from "$lib/logging"; -import { g } from "$lib/db"; -import { fail, getUser, pick } from "$lib/utils"; - -const code = 1000; -const all = {}; -const subscriptions = []; -const lastSeen = {}; -const users = {}; - -export const emit = (uid, type, data) => { - if (type === "payment" && data.amount > 0) { - for (let i = subscriptions.length - 1; i >= 0; i--) { - let s = subscriptions[i]; - - if (s.invoice && s.invoice.hash === data.hash) { - s.ws.send(JSON.stringify({ type, data })); - } - } - } - - if (!store.sockets[uid]) return; - for (let id in store.sockets[uid]) { - const ws = store.sockets[uid][id]; - ws.send(JSON.stringify({ type, data })); - } -}; - -export const broadcast = (type, data) => { - const sent = []; - for (const uid in store.sockets) { - for (const id in store.sockets[uid]) { - const ws = store.sockets[uid][id]; - ws.send(JSON.stringify({ type, data })); - sent.push(ws.id); - } - } - - Object.values(all).map((ws) => { - if (!sent.includes(ws.id)) ws.send(JSON.stringify({ type, data })); - }); -}; - -let track = async (ws, token) => { - let { id } = ws; - let { id: uid } = jwt.decode(token); - - if (!uid) fail("Invalid JWT token"); - let user = await getUser(uid); - if (!user) fail(`User not found ${uid}`); - - if (!store.sockets[uid]) store.sockets[uid] = {}; - - const existing = Object.keys(store.sockets[uid]); - if (existing.length > 4) { - const p = existing.find((sid) => sid !== uid); - store.sockets[uid][p].close(code, "too many sockets"); - } - - store.sockets[uid][id] = ws; - users[id] = uid; - ws.user = user; -}; - -console.log("starting socket server"); -Bun.serve({ - hostname: "0.0.0.0", - port: 3120, - fetch(req, server) { - console.log("here we go"); - // upgrade the request to a WebSocket - if (server.upgrade(req)) { - console.log("UPGRADING"); - return; // do not return a Response - } - return new Response("Upgrade failed", { status: 500 }); - }, - websocket: { - async message(ws, message) { - let type, data; - - try { - ({ type, data } = JSON.parse(message)); - } catch (e) { - err("coudn't parse socket message"); - } - - switch (type) { - case "heartbeat": - ws.beats = 0; - try { - if (data) await track(ws, data); - ws.send(JSON.stringify({ type: "id", data: ws.id })); - } catch (e) { - // err("Failed to send heartbeat", e.message); - } - break; - - case "login": - try { - if (!data || data === "null" || !jwt.decode(data)) - return ws.close(code, `bad token ${data}`); - - await track(ws, data); - } catch (e) { - if (e.message.includes("not found")) - ws.send(JSON.stringify({ type: "logout" })); - setTimeout( - () => ws && ws.close(code, `closing due to error ${e.message}`), - 1000, - ); - } - break; - case "token": - if (!ws.user) return; - ws.token = data; - break; - case "lnurl": - store.sessions[data] = ws; - break; - case "subscribe": - subscriptions.push({ invoice: data, ws }); - break; - default: - warn("received socket message of unknown type", type, data); - } - }, - open(ws) { - console.log("OPENING"); - const id = v4(); - ws.id = id; - ws.beats = 0; - ws.send(JSON.stringify({ type: "connected", data: id })); - all[id] = ws; - }, - close(ws) { - let { id } = ws; - try { - const uid = users[id]; - if (store.sockets[uid] && store.sockets[uid][id]) { - delete store.sockets[uid][id]; - } - - if (all[id]) delete all[id]; - } catch (e) { - err("problem closing socket", e.message); - } - }, - }, -}); - -export let sendHeartbeat = () => { - for (const uid in store.sockets) { - for (const id in store.sockets[uid]) { - let ws = store.sockets[uid][id]; - ws.beats++; - if (ws.beats > 10) ws.close(code, `${uid} ${id} lost heartbeat`); - } - } -}; diff --git a/lib/sockets.js.map b/lib/sockets.js.map new file mode 100644 index 00000000..8fb722a2 --- /dev/null +++ b/lib/sockets.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sockets.js","sourceRoot":"","sources":["sockets.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/sockets.ts b/lib/sockets.ts new file mode 100644 index 00000000..0391a07a --- /dev/null +++ b/lib/sockets.ts @@ -0,0 +1,161 @@ +import config from "$config"; +import store from "$lib/store"; +import jwt from "jsonwebtoken"; +import { v4 } from "uuid"; +import whitelist from "$lib/whitelist"; +import { l, err, warn } from "$lib/logging"; +import { g } from "$lib/db"; +import { fail, getUser, pick } from "$lib/utils"; + +const code = 1000; +const all = {}; +const subscriptions = []; +const lastSeen = {}; +const users = {}; + +export const emit = (uid, type, data) => { + if (type === "payment" && data.amount > 0) { + for (let i = subscriptions.length - 1; i >= 0; i--) { + let s = subscriptions[i]; + + if (s.invoice && s.invoice.hash === data.hash) { + s.ws.send(JSON.stringify({ type, data })); + } + } + } + + if (!store.sockets[uid]) return; + for (let id in store.sockets[uid]) { + const ws = store.sockets[uid][id]; + ws.send(JSON.stringify({ type, data })); + } +}; + +export const broadcast = (type, data) => { + const sent = []; + for (const uid in store.sockets) { + for (const id in store.sockets[uid]) { + const ws = store.sockets[uid][id]; + ws.send(JSON.stringify({ type, data })); + sent.push(ws.id); + } + } + + Object.values(all).map((ws) => { + if (!sent.includes(ws.id)) ws.send(JSON.stringify({ type, data })); + }); +}; + +let track = async (ws, token) => { + let { id } = ws; + let { id: uid } = jwt.decode(token); + + if (!uid) fail("Invalid JWT token"); + let user = await getUser(uid); + if (!user) fail(`User not found ${uid}`); + + if (!store.sockets[uid]) store.sockets[uid] = {}; + + const existing = Object.keys(store.sockets[uid]); + if (existing.length > 4) { + const p = existing.find((sid) => sid !== uid); + store.sockets[uid][p].close(code, "too many sockets"); + } + + store.sockets[uid][id] = ws; + users[id] = uid; + ws.user = user; +}; + +Bun.serve({ + hostname: "0.0.0.0", + port: 3120, + fetch(req, server) { + // upgrade the request to a WebSocket + if (server.upgrade(req)) { + return; // do not return a Response + } + return new Response("Upgrade failed", { status: 500 }); + }, + websocket: { + async message(ws, message) { + let type, data; + + try { + ({ type, data } = JSON.parse(message)); + } catch (e) { + err("coudn't parse socket message"); + } + + switch (type) { + case "heartbeat": + ws.beats = 0; + try { + if (data) await track(ws, data); + ws.send(JSON.stringify({ type: "id", data: ws.id })); + } catch (e) { + // err("Failed to send heartbeat", e.message); + } + break; + + case "login": + try { + if (!data || data === "null" || !jwt.decode(data)) + return ws.close(code, `bad token ${data}`); + + await track(ws, data); + } catch (e) { + if (e.message.includes("not found")) + ws.send(JSON.stringify({ type: "logout" })); + setTimeout( + () => ws && ws.close(code, `closing due to error ${e.message}`), + 1000, + ); + } + break; + case "token": + if (!ws.user) return; + ws.token = data; + break; + case "lnurl": + store.sessions[data] = ws; + break; + case "subscribe": + subscriptions.push({ invoice: data, ws }); + break; + default: + warn("received socket message of unknown type", type, data); + } + }, + open(ws) { + const id = v4(); + ws.id = id; + ws.beats = 0; + ws.send(JSON.stringify({ type: "connected", data: id })); + all[id] = ws; + }, + close(ws) { + let { id } = ws; + try { + const uid = users[id]; + if (store.sockets[uid] && store.sockets[uid][id]) { + delete store.sockets[uid][id]; + } + + if (all[id]) delete all[id]; + } catch (e) { + err("problem closing socket", e.message); + } + }, + }, +}); + +export let sendHeartbeat = () => { + for (const uid in store.sockets) { + for (const id in store.sockets[uid]) { + let ws = store.sockets[uid][id]; + ws.beats++; + if (ws.beats > 10) ws.close(code, `${uid} ${id} lost heartbeat`); + } + } +}; diff --git a/lib/store.js b/lib/store.js deleted file mode 100644 index 4c86761c..00000000 --- a/lib/store.js +++ /dev/null @@ -1,22 +0,0 @@ -export default { - addresses: {}, - challenge: {}, - change: [], - issuances: {}, - logins: {}, - nodes: [], - seen: [], - sessions: {}, - sockets: {}, - unaccounted: [], - networks: [], - rates: { USD: 20000 }, - fx: {}, - assets: {}, - bcAddressIndex: 0, - lqAddressIndex: 0, - last: {}, - payments: [], - fetching: {}, - timeouts: {}, -}; diff --git a/lib/store.js.map b/lib/store.js.map new file mode 100644 index 00000000..f576e0c0 --- /dev/null +++ b/lib/store.js.map @@ -0,0 +1 @@ +{"version":3,"file":"store.js","sourceRoot":"","sources":["store.ts"],"names":[],"mappings":";;AAAA,kBAAe;IACb,SAAS,EAAE,EAAE;IACb,SAAS,EAAE,EAAE;IACb,MAAM,EAAE,EAAE;IACV,SAAS,EAAE,EAAE;IACb,MAAM,EAAE,EAAE;IACV,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,EAAE;IACZ,OAAO,EAAE,EAAE;IACX,WAAW,EAAE,EAAE;IACf,QAAQ,EAAE,EAAE;IACZ,KAAK,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE;IACrB,EAAE,EAAE,EAAE;IACN,MAAM,EAAE,EAAE;IACV,cAAc,EAAE,CAAC;IACjB,cAAc,EAAE,CAAC;IACjB,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,EAAE;IACZ,QAAQ,EAAE,EAAE;IACZ,QAAQ,EAAE,EAAE;CACb,CAAC"} \ No newline at end of file diff --git a/lib/store.ts b/lib/store.ts new file mode 100644 index 00000000..4c86761c --- /dev/null +++ b/lib/store.ts @@ -0,0 +1,22 @@ +export default { + addresses: {}, + challenge: {}, + change: [], + issuances: {}, + logins: {}, + nodes: [], + seen: [], + sessions: {}, + sockets: {}, + unaccounted: [], + networks: [], + rates: { USD: 20000 }, + fx: {}, + assets: {}, + bcAddressIndex: 0, + lqAddressIndex: 0, + last: {}, + payments: [], + fetching: {}, + timeouts: {}, +}; diff --git a/lib/upload.js b/lib/upload.js deleted file mode 100644 index 0fb31ea5..00000000 --- a/lib/upload.js +++ /dev/null @@ -1,70 +0,0 @@ -import { fileTypeFromStream } from "file-type"; -import pump from "pump"; -import Clone from "readable-stream-clone"; -import fs from "fs"; -import path from "path"; -import { pipeline } from "stream"; -import { g, s, db } from "$lib/db"; -import { bail, fail } from "$lib/utils"; -import crypto from "crypto"; -import { err } from "$lib/logging"; - -async function getSha256Hash(stream) { - return new Promise((resolve, reject) => { - const hash = crypto.createHash("sha256"); - stream.pipe(hash); - - let hashValue; - hash.on("data", (data) => { - hashValue = data.toString("hex"); - }); - - hash.on("end", () => { - resolve(hashValue); - }); - - hash.on("error", reject); - }); -} - -export default async (req, res) => { - try { - let { - params: { type }, - } = req; - - let width = type === "banner" ? 1920 : 240; - - let data = await req.file(); - let s1 = new Clone(data.file); - let s2 = new Clone(data.file); - - let [format, ext] = (await fileTypeFromStream(s1)).mime.split("/"); - - if (format !== "image" && !["jpg", "jpeg", "png"].includes(ext)) - fail("unsupported file type"); - - let processedBuffer = await streamToBuffer(s2.pipe(t)); - let hash = crypto - .createHash("sha256") - .update(processedBuffer) - .digest("hex"); - - let filePath = `/home/bun/app/data/uploads/${hash}.webp`; - fs.writeFileSync(filePath, processedBuffer); - - res.send({ hash }); - } catch (e) { - err("problem uploading", e.message); - bail(res, e.message); - } -}; - -function streamToBuffer(stream) { - return new Promise((resolve, reject) => { - const chunks = []; - stream.on("data", (chunk) => chunks.push(chunk)); - stream.on("end", () => resolve(Buffer.concat(chunks))); - stream.on("error", reject); - }); -} diff --git a/lib/upload.js.map b/lib/upload.js.map new file mode 100644 index 00000000..9f49b99c --- /dev/null +++ b/lib/upload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"upload.js","sourceRoot":"","sources":["upload.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/upload.ts b/lib/upload.ts new file mode 100644 index 00000000..0fb31ea5 --- /dev/null +++ b/lib/upload.ts @@ -0,0 +1,70 @@ +import { fileTypeFromStream } from "file-type"; +import pump from "pump"; +import Clone from "readable-stream-clone"; +import fs from "fs"; +import path from "path"; +import { pipeline } from "stream"; +import { g, s, db } from "$lib/db"; +import { bail, fail } from "$lib/utils"; +import crypto from "crypto"; +import { err } from "$lib/logging"; + +async function getSha256Hash(stream) { + return new Promise((resolve, reject) => { + const hash = crypto.createHash("sha256"); + stream.pipe(hash); + + let hashValue; + hash.on("data", (data) => { + hashValue = data.toString("hex"); + }); + + hash.on("end", () => { + resolve(hashValue); + }); + + hash.on("error", reject); + }); +} + +export default async (req, res) => { + try { + let { + params: { type }, + } = req; + + let width = type === "banner" ? 1920 : 240; + + let data = await req.file(); + let s1 = new Clone(data.file); + let s2 = new Clone(data.file); + + let [format, ext] = (await fileTypeFromStream(s1)).mime.split("/"); + + if (format !== "image" && !["jpg", "jpeg", "png"].includes(ext)) + fail("unsupported file type"); + + let processedBuffer = await streamToBuffer(s2.pipe(t)); + let hash = crypto + .createHash("sha256") + .update(processedBuffer) + .digest("hex"); + + let filePath = `/home/bun/app/data/uploads/${hash}.webp`; + fs.writeFileSync(filePath, processedBuffer); + + res.send({ hash }); + } catch (e) { + err("problem uploading", e.message); + bail(res, e.message); + } +}; + +function streamToBuffer(stream) { + return new Promise((resolve, reject) => { + const chunks = []; + stream.on("data", (chunk) => chunks.push(chunk)); + stream.on("end", () => resolve(Buffer.concat(chunks))); + stream.on("error", reject); + }); +} diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index ada32521..00000000 --- a/lib/utils.js +++ /dev/null @@ -1,160 +0,0 @@ -import { randomBytes, bytesToHex } from "@noble/hashes/utils"; -import migrate from "$lib/migrate"; -import store from "$lib/store"; -import { g, s } from "$lib/db"; -import config from "$config"; - -export let fail = (msg) => { - throw new Error(msg); -}; - -export let nada = () => {}; - -export let sleep = (n) => new Promise((r) => setTimeout(r, n)); -export let wait = async (f, s = 300, n = 50) => { - let i = 0; - while (!(await f()) && i < s) (await sleep(n)) && i++; - if (i >= s) fail("timeout"); - return f(); -}; - -export let prod = process.env.NODE_ENV === "production"; - -export let bail = (res, msg) => res.code(500).send(msg); - -export let SATS = 100000000; -export let sats = (n) => Math.round(n * SATS); -export let btc = (n) => parseFloat((n / SATS).toFixed(8)); -export let fiat = (n, r) => (n * r) / SATS; - -export let uniq = (a, k) => [...new Map(a.map((x) => [k(x), x])).values()]; -export let pick = (O, K) => - K.reduce((o, k) => (typeof O[k] !== "undefined" && (o[k] = O[k]), o), {}); - -export let bip21 = (address, { amount, memo, tip, type }) => { - if (!(amount || memo)) return address; - - let network = { liquid: "liquidnetwork", bitcoin: "bitcoin" }[type]; - let url = new URLSearchParams(); - - if (amount) { - url.append("amount", btc(amount + tip)); - if (type === "liquid") url.append("assetid", config.liquid.btc); - } - - if (memo) url.append("memo", memo); - - return `${network}:${address}?${url.toString()}`; -}; - -export let fields = [ - "cipher", - "pubkey", - "password", - "username", - "salt", - "currency", - "currencies", - "fiat", - "otpsecret", - "balance", - "ip", - "pin", - "display", - "haspin", - "profile", - "prompt", - "banner", -]; - -export let getUser = async (username) => { - if (username === "undefined") fail("invalid user"); - let user = await migrate(username); - if (user && !user.nwc) { - user.nwc = bytesToHex(randomBytes(32)); - s(`user:${user.id}`, user); - } - - return user; -}; - -export let getInvoice = async (hash) => { - let iid = await g(`invoice:${hash}`); - if (iid && iid.id) iid = iid.id; - else if (iid && iid.hash) iid = iid.hash; - return await g(`invoice:${iid}`); -}; - -export let f = (s, currency) => - new Intl.NumberFormat("en-US", { - style: "currency", - currency, - }) - .format(s) - .replace("CA", ""); - -export function formatReceipt(items, currency) { - function wrapText(text, maxWidth) { - const words = text.split(" "); - const lines = []; - let currentLine = ""; - words.forEach((word) => { - if (currentLine.length + word.length + 1 > maxWidth) { - lines.push(currentLine); - currentLine = word; - } else { - currentLine += (currentLine.length > 0 ? " " : "") + word; - } - }); - if (currentLine) lines.push(currentLine); - return lines; - } - - function calculateColumnWidths(items) { - let maxQuantityLength = 0; - let maxPriceLength = 0; - - items.forEach((item) => { - const quantityLength = String(item.quantity).length; - if (quantityLength > maxQuantityLength) { - maxQuantityLength = quantityLength; - } - - const priceLength = f(item.price * item.quantity, currency).length; - if (priceLength > maxPriceLength) { - maxPriceLength = priceLength; - } - }); - - // Add padding to the widths for aesthetic spacing - return { - quantityColumnWidth: maxQuantityLength + 1, // Space after quantity - priceColumnWidth: maxPriceLength + 1, // Space before price - }; - } - const maxLineWidth = 32; - const { quantityColumnWidth, priceColumnWidth } = - calculateColumnWidths(items); - const nameColumnWidth = maxLineWidth - quantityColumnWidth - priceColumnWidth; - - return items - .map((item) => { - const quantityStr = String(item.quantity).padEnd(quantityColumnWidth); - const priceStr = f(item.price * item.quantity, currency).padStart( - priceColumnWidth, - ); - const nameLines = wrapText(item.name, nameColumnWidth); - - // Construct the full line(s) with the first line including the price - let fullLines = [ - `${quantityStr}${nameLines[0].padEnd(nameColumnWidth)}${priceStr}`, - ]; - // Add any additional name lines, properly indented - for (let i = 1; i < nameLines.length; i++) { - fullLines.push(" ".repeat(quantityColumnWidth) + nameLines[i]); - } - - return fullLines.join("\n"); - }) - .join("\n"); -} diff --git a/lib/utils.js.map b/lib/utils.js.map new file mode 100644 index 00000000..1652f149 --- /dev/null +++ b/lib/utils.js.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.js","sourceRoot":"","sources":["utils.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 00000000..ada32521 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,160 @@ +import { randomBytes, bytesToHex } from "@noble/hashes/utils"; +import migrate from "$lib/migrate"; +import store from "$lib/store"; +import { g, s } from "$lib/db"; +import config from "$config"; + +export let fail = (msg) => { + throw new Error(msg); +}; + +export let nada = () => {}; + +export let sleep = (n) => new Promise((r) => setTimeout(r, n)); +export let wait = async (f, s = 300, n = 50) => { + let i = 0; + while (!(await f()) && i < s) (await sleep(n)) && i++; + if (i >= s) fail("timeout"); + return f(); +}; + +export let prod = process.env.NODE_ENV === "production"; + +export let bail = (res, msg) => res.code(500).send(msg); + +export let SATS = 100000000; +export let sats = (n) => Math.round(n * SATS); +export let btc = (n) => parseFloat((n / SATS).toFixed(8)); +export let fiat = (n, r) => (n * r) / SATS; + +export let uniq = (a, k) => [...new Map(a.map((x) => [k(x), x])).values()]; +export let pick = (O, K) => + K.reduce((o, k) => (typeof O[k] !== "undefined" && (o[k] = O[k]), o), {}); + +export let bip21 = (address, { amount, memo, tip, type }) => { + if (!(amount || memo)) return address; + + let network = { liquid: "liquidnetwork", bitcoin: "bitcoin" }[type]; + let url = new URLSearchParams(); + + if (amount) { + url.append("amount", btc(amount + tip)); + if (type === "liquid") url.append("assetid", config.liquid.btc); + } + + if (memo) url.append("memo", memo); + + return `${network}:${address}?${url.toString()}`; +}; + +export let fields = [ + "cipher", + "pubkey", + "password", + "username", + "salt", + "currency", + "currencies", + "fiat", + "otpsecret", + "balance", + "ip", + "pin", + "display", + "haspin", + "profile", + "prompt", + "banner", +]; + +export let getUser = async (username) => { + if (username === "undefined") fail("invalid user"); + let user = await migrate(username); + if (user && !user.nwc) { + user.nwc = bytesToHex(randomBytes(32)); + s(`user:${user.id}`, user); + } + + return user; +}; + +export let getInvoice = async (hash) => { + let iid = await g(`invoice:${hash}`); + if (iid && iid.id) iid = iid.id; + else if (iid && iid.hash) iid = iid.hash; + return await g(`invoice:${iid}`); +}; + +export let f = (s, currency) => + new Intl.NumberFormat("en-US", { + style: "currency", + currency, + }) + .format(s) + .replace("CA", ""); + +export function formatReceipt(items, currency) { + function wrapText(text, maxWidth) { + const words = text.split(" "); + const lines = []; + let currentLine = ""; + words.forEach((word) => { + if (currentLine.length + word.length + 1 > maxWidth) { + lines.push(currentLine); + currentLine = word; + } else { + currentLine += (currentLine.length > 0 ? " " : "") + word; + } + }); + if (currentLine) lines.push(currentLine); + return lines; + } + + function calculateColumnWidths(items) { + let maxQuantityLength = 0; + let maxPriceLength = 0; + + items.forEach((item) => { + const quantityLength = String(item.quantity).length; + if (quantityLength > maxQuantityLength) { + maxQuantityLength = quantityLength; + } + + const priceLength = f(item.price * item.quantity, currency).length; + if (priceLength > maxPriceLength) { + maxPriceLength = priceLength; + } + }); + + // Add padding to the widths for aesthetic spacing + return { + quantityColumnWidth: maxQuantityLength + 1, // Space after quantity + priceColumnWidth: maxPriceLength + 1, // Space before price + }; + } + const maxLineWidth = 32; + const { quantityColumnWidth, priceColumnWidth } = + calculateColumnWidths(items); + const nameColumnWidth = maxLineWidth - quantityColumnWidth - priceColumnWidth; + + return items + .map((item) => { + const quantityStr = String(item.quantity).padEnd(quantityColumnWidth); + const priceStr = f(item.price * item.quantity, currency).padStart( + priceColumnWidth, + ); + const nameLines = wrapText(item.name, nameColumnWidth); + + // Construct the full line(s) with the first line including the price + let fullLines = [ + `${quantityStr}${nameLines[0].padEnd(nameColumnWidth)}${priceStr}`, + ]; + // Add any additional name lines, properly indented + for (let i = 1; i < nameLines.length; i++) { + fullLines.push(" ".repeat(quantityColumnWidth) + nameLines[i]); + } + + return fullLines.join("\n"); + }) + .join("\n"); +} diff --git a/lib/webhooks.js b/lib/webhooks.js deleted file mode 100644 index b1f035a4..00000000 --- a/lib/webhooks.js +++ /dev/null @@ -1,33 +0,0 @@ -import config from "$config"; -import got from "got"; -import { l, err } from "$lib/logging"; - -export const callWebhook = async (invoice, payment) => { - try { - if (!invoice || !payment) return; - - let { address, received, text, webhook, secret } = invoice; - - if (webhook) { - let { amount, confirmed, hash, memo } = payment; - - l("calling webhook", webhook, amount, hash, address, text); - let res = await got.post(webhook, { - json: { - address, - amount, - confirmed, - hash, - memo, - received, - text, - secret, - }, - https: { rejectUnauthorized: false } - }); - return res; - } - } catch (e) { - err("problem calling webhook", e.message); - } -}; diff --git a/lib/webhooks.js.map b/lib/webhooks.js.map new file mode 100644 index 00000000..40906dcd --- /dev/null +++ b/lib/webhooks.js.map @@ -0,0 +1 @@ +{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["webhooks.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,4CAAsB;AACtB,wCAAsC;AAE/B,IAAM,WAAW,GAAG,UAAO,OAAO,EAAE,OAAO;;;;;;gBAE9C,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO;oBAAE,sBAAO;gBAE3B,OAAO,GAAsC,OAAO,QAA7C,EAAE,QAAQ,GAA4B,OAAO,SAAnC,EAAE,IAAI,GAAsB,OAAO,KAA7B,EAAE,OAAO,GAAa,OAAO,QAApB,EAAE,MAAM,GAAK,OAAO,OAAZ,CAAa;qBAEvD,OAAO,EAAP,wBAAO;gBACH,MAAM,GAA4B,OAAO,OAAnC,EAAE,SAAS,GAAiB,OAAO,UAAxB,EAAE,IAAI,GAAW,OAAO,KAAlB,EAAE,IAAI,GAAK,OAAO,KAAZ,CAAa;gBAEhD,IAAA,WAAC,EAAC,iBAAiB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;gBACjD,qBAAM,aAAG,CAAC,IAAI,CAAC,OAAO,EAAE;wBAChC,IAAI,EAAE;4BACJ,OAAO,SAAA;4BACP,MAAM,QAAA;4BACN,SAAS,WAAA;4BACT,IAAI,MAAA;4BACJ,IAAI,MAAA;4BACJ,QAAQ,UAAA;4BACR,IAAI,MAAA;4BACJ,MAAM,QAAA;yBACP;wBACC,KAAK,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE;qBACvC,CAAC,EAAA;;gBAZE,GAAG,GAAG,SAYR;gBACF,sBAAO,GAAG,EAAC;;;;gBAGb,IAAA,aAAG,EAAC,yBAAyB,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;KAE7C,CAAC;AA5BW,QAAA,WAAW,eA4BtB"} \ No newline at end of file diff --git a/lib/webhooks.ts b/lib/webhooks.ts new file mode 100644 index 00000000..b1f035a4 --- /dev/null +++ b/lib/webhooks.ts @@ -0,0 +1,33 @@ +import config from "$config"; +import got from "got"; +import { l, err } from "$lib/logging"; + +export const callWebhook = async (invoice, payment) => { + try { + if (!invoice || !payment) return; + + let { address, received, text, webhook, secret } = invoice; + + if (webhook) { + let { amount, confirmed, hash, memo } = payment; + + l("calling webhook", webhook, amount, hash, address, text); + let res = await got.post(webhook, { + json: { + address, + amount, + confirmed, + hash, + memo, + received, + text, + secret, + }, + https: { rejectUnauthorized: false } + }); + return res; + } + } catch (e) { + err("problem calling webhook", e.message); + } +}; diff --git a/lib/whitelist.js b/lib/whitelist.js deleted file mode 100644 index bf5b4eb9..00000000 --- a/lib/whitelist.js +++ /dev/null @@ -1,45 +0,0 @@ -export default [ - "address", - "admin", - "anon", - "autowithdraw", - "balance", - "banner", - "cipher", - "currencies", - "currency", - "destination", - "display", - "email", - "fiat", - "followers", - "follows", - "haspin", - "hasprinter", - "hidepay", - "id", - "index", - "keys", - "language", - "linked", - "locktime", - "memoPrompt", - "nip5", - "notify", - "nsec", - "nwc", - "payments", - "profile", - "prompt", - "pubkey", - "reserve", - "salt", - "seed", - "shopifyStore", - "shopifyToken", - "theme", - "threshold", - "twofa", - "username", - "verified", -]; diff --git a/lib/whitelist.js.map b/lib/whitelist.js.map new file mode 100644 index 00000000..f1e01c42 --- /dev/null +++ b/lib/whitelist.js.map @@ -0,0 +1 @@ +{"version":3,"file":"whitelist.js","sourceRoot":"","sources":["whitelist.ts"],"names":[],"mappings":";;AAAA,kBAAe;IACb,SAAS;IACT,OAAO;IACP,MAAM;IACN,cAAc;IACd,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,YAAY;IACZ,UAAU;IACV,aAAa;IACb,SAAS;IACT,OAAO;IACP,MAAM;IACN,WAAW;IACX,SAAS;IACT,QAAQ;IACR,YAAY;IACZ,SAAS;IACT,IAAI;IACJ,OAAO;IACP,MAAM;IACN,UAAU;IACV,QAAQ;IACR,UAAU;IACV,YAAY;IACZ,MAAM;IACN,QAAQ;IACR,MAAM;IACN,KAAK;IACL,UAAU;IACV,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,MAAM;IACN,MAAM;IACN,cAAc;IACd,cAAc;IACd,OAAO;IACP,WAAW;IACX,OAAO;IACP,UAAU;IACV,UAAU;CACX,CAAC"} \ No newline at end of file diff --git a/lib/whitelist.ts b/lib/whitelist.ts new file mode 100644 index 00000000..bf5b4eb9 --- /dev/null +++ b/lib/whitelist.ts @@ -0,0 +1,45 @@ +export default [ + "address", + "admin", + "anon", + "autowithdraw", + "balance", + "banner", + "cipher", + "currencies", + "currency", + "destination", + "display", + "email", + "fiat", + "followers", + "follows", + "haspin", + "hasprinter", + "hidepay", + "id", + "index", + "keys", + "language", + "linked", + "locktime", + "memoPrompt", + "nip5", + "notify", + "nsec", + "nwc", + "payments", + "profile", + "prompt", + "pubkey", + "reserve", + "salt", + "seed", + "shopifyStore", + "shopifyToken", + "theme", + "threshold", + "twofa", + "username", + "verified", +]; -- cgit v1.2.3