ready for migration

This commit is contained in:
hatim 2023-05-08 21:31:05 +02:00
parent 46819446d5
commit c050de214a
42 changed files with 3903 additions and 2262 deletions

615
package-lock.json generated
View file

@ -28,7 +28,7 @@
"grpc-tools": "^1.11.2",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"nostr-tools": "^0.24.1",
"nostr-tools": "^1.9.0",
"pg": "^8.4.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
@ -48,6 +48,7 @@
"@types/jsonwebtoken": "^8.5.9",
"@types/lodash": "^4.14.182",
"@types/node": "^16.11.10",
"@types/node-fetch": "^2.6.3",
"@types/uuid": "^8.3.4",
"nodemon": "^2.0.20",
"ts-node": "10.7.0",
@ -130,15 +131,24 @@
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@noble/hashes": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-0.5.9.tgz",
"integrity": "sha512-7lN1Qh6d8DUGmfN36XRsbN/WcGIPNtTGhkw26vWId/DlCIGsYJJootTtPGghTLcn/AaXPx2Q0b3cacrwXa7OVw=="
"node_modules/@noble/curves": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.0.0.tgz",
"integrity": "sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/hashes": "1.3.0"
}
},
"node_modules/@noble/secp256k1": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.0.tgz",
"integrity": "sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw==",
"node_modules/@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==",
"funding": [
{
"type": "individual",
@ -338,6 +348,48 @@
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
},
"node_modules/@scure/base": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/@scure/bip32": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.0.tgz",
"integrity": "sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/curves": "~1.0.0",
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"node_modules/@scure/bip39": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.0.tgz",
"integrity": "sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"node_modules/@sqltools/formatter": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
@ -472,6 +524,30 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.3.tgz",
"integrity": "sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg=="
},
"node_modules/@types/node-fetch": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.3.tgz",
"integrity": "sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==",
"dev": true,
"dependencies": {
"@types/node": "*",
"form-data": "^3.0.0"
}
},
"node_modules/@types/node-fetch/node_modules/form-data": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
"dev": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@types/object-hash": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-1.3.4.tgz",
@ -875,6 +951,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
"optional": true,
"dependencies": {
"buffer-xor": "^1.0.3",
"cipher-base": "^1.0.0",
@ -884,27 +961,6 @@
"safe-buffer": "^5.0.1"
}
},
"node_modules/browserify-cipher": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
"integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
"dependencies": {
"browserify-aes": "^1.0.4",
"browserify-des": "^1.0.0",
"evp_bytestokey": "^1.0.0"
}
},
"node_modules/browserify-des": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
"integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
"dependencies": {
"cipher-base": "^1.0.1",
"des.js": "^1.0.0",
"inherits": "^2.0.1",
"safe-buffer": "^5.1.2"
}
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@ -944,19 +1000,8 @@
"node_modules/buffer-xor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="
},
"node_modules/bufferutil": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
"integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
"hasInstallScript": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==",
"optional": true
},
"node_modules/bytes": {
"version": "3.1.2",
@ -1086,6 +1131,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
"optional": true,
"dependencies": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
@ -1246,6 +1292,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
"optional": true,
"dependencies": {
"cipher-base": "^1.0.1",
"inherits": "^2.0.1",
@ -1274,23 +1321,6 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"devOptional": true
},
"node_modules/cross-fetch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
"dependencies": {
"node-fetch": "2.6.7"
}
},
"node_modules/d": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
"dependencies": {
"es5-ext": "^0.10.50",
"type": "^1.0.1"
}
},
"node_modules/dataloader": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz",
@ -1348,15 +1378,6 @@
"node": ">= 0.8"
}
},
"node_modules/des.js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
"integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
"dependencies": {
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
@ -1546,44 +1567,11 @@
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
"optional": true
},
"node_modules/es5-ext": {
"version": "0.10.62",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
"integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
"hasInstallScript": true,
"dependencies": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"next-tick": "^1.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"dependencies": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"node_modules/es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"node_modules/es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
"dependencies": {
"d": "^1.0.1",
"ext": "^1.1.2"
}
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -1609,6 +1597,7 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
"integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
"optional": true,
"dependencies": {
"md5.js": "^1.3.4",
"safe-buffer": "^5.1.1"
@ -1655,19 +1644,6 @@
"node": ">= 0.10.0"
}
},
"node_modules/ext": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"dependencies": {
"type": "^2.7.2"
}
},
"node_modules/ext/node_modules/type": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
},
"node_modules/fast-glob": {
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
@ -1962,6 +1938,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
"optional": true,
"dependencies": {
"inherits": "^2.0.4",
"readable-stream": "^3.6.0",
@ -1975,6 +1952,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"optional": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@ -1988,6 +1966,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"optional": true,
"dependencies": {
"safe-buffer": "~5.2.0"
}
@ -2268,11 +2247,6 @@
"node": ">=0.12.0"
}
},
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@ -2476,6 +2450,7 @@
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
"optional": true,
"dependencies": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1",
@ -2511,33 +2486,6 @@
"node": ">= 0.6"
}
},
"node_modules/micro-base": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/micro-base/-/micro-base-0.10.2.tgz",
"integrity": "sha512-lqqJrT7lfJtDmmiQ4zRLZuIJBk96t0RAc5pCrrWpL9zDeH5i/SUL85mku9HqzTI/OCZ8EQ3aicbMW+eK5Nyu5w==",
"deprecated": "Switch to @scure/base for audited version of the lib & updates"
},
"node_modules/micro-bip32": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/micro-bip32/-/micro-bip32-0.1.0.tgz",
"integrity": "sha512-HxwYJzokbObqPHUqQuzRpCEqZ3EE4uHKrGlLX5ylt0ktD6m9LeS3RkWuQ1HApXEgrGMs3XgykN5Bic2YHE0f6Q==",
"deprecated": "Switch to @scure/bip32 for audited version of the lib & updates",
"dependencies": {
"@noble/hashes": "^0.5.7",
"@noble/secp256k1": "^1.3.4",
"micro-base": "^0.10.1"
}
},
"node_modules/micro-bip39": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/micro-bip39/-/micro-bip39-0.1.3.tgz",
"integrity": "sha512-lEaRG/MKxFQvG19lfJfPkLIG0rgT28nWud3otN+VgAbrozGqXn2PLaZuYPsy9guQjIZWBTvoLw/HDJQxmMXjMA==",
"deprecated": "Switch to @scure/bip39 for audited version of the lib & updates",
"dependencies": {
"@noble/hashes": "^0.5.5",
"micro-base": "^0.10.1"
}
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
@ -2728,11 +2676,6 @@
"node": ">= 0.6"
}
},
"node_modules/next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"node_modules/node-addon-api": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
@ -2967,19 +2910,15 @@
}
},
"node_modules/nostr-tools": {
"version": "0.24.1",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-0.24.1.tgz",
"integrity": "sha512-+aUWblwNTYra8ZsjmfzxStr4XSKAb0gPsehNP3oBiSouLevqD3FWngc++kh8l+zfMYEPPGS6kS0i9iaq/5ZF6A==",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.9.0.tgz",
"integrity": "sha512-ZvFf1uiBqWLWhLBHD2nY0KsdSdNWKb3PrQUmYMWxSzfT4k48cDrDJu2qgULkOhQbFX7oty8IpaKnLvixhqefqA==",
"dependencies": {
"@noble/hashes": "^0.5.7",
"@noble/secp256k1": "^1.5.2",
"browserify-cipher": ">=1",
"buffer": ">=5",
"create-hash": "^1.2.0",
"cross-fetch": "^3.1.4",
"micro-bip32": "^0.1.0",
"micro-bip39": "^0.1.3",
"websocket-polyfill": "^0.0.3"
"@noble/curves": "1.0.0",
"@noble/hashes": "1.3.0",
"@scure/base": "1.1.1",
"@scure/bip32": "1.3.0",
"@scure/bip39": "1.2.0"
}
},
"node_modules/npmlog": {
@ -3428,6 +3367,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
"optional": true,
"dependencies": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1"
@ -4060,16 +4000,6 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
},
"node_modules/tstl": {
"version": "2.5.13",
"resolved": "https://registry.npmjs.org/tstl/-/tstl-2.5.13.tgz",
"integrity": "sha512-h9wayHHFI5+yqt8iau0vqH96cTNhezhZ/Fk/hrIdpfkiMu3lg9nzyvMfs5bIdX51IVzZO6DudLqhkL/rVXpT6g=="
},
"node_modules/type": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
},
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@ -4090,14 +4020,6 @@
"node": ">= 0.6"
}
},
"node_modules/typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"dependencies": {
"is-typedarray": "^1.0.0"
}
},
"node_modules/typeorm": {
"version": "0.3.10",
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.10.tgz",
@ -4317,18 +4239,6 @@
"node": ">=8"
}
},
"node_modules/utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -4369,31 +4279,6 @@
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/websocket": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
"integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
"dependencies": {
"bufferutil": "^4.0.1",
"debug": "^2.2.0",
"es5-ext": "^0.10.50",
"typedarray-to-buffer": "^3.1.5",
"utf-8-validate": "^5.0.2",
"yaeti": "^0.0.6"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/websocket-polyfill": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz",
"integrity": "sha512-pF3kR8Uaoau78MpUmFfzbIRxXj9PeQrCuPepGE6JIsfsJ/o/iXr07Q2iQNzKSSblQJ0FiGWlS64N4pVSm+O3Dg==",
"dependencies": {
"tstl": "^2.0.7",
"websocket": "^1.0.28"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@ -4483,14 +4368,6 @@
"node": ">=10"
}
},
"node_modules/yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
"integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==",
"engines": {
"node": ">=0.10.32"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@ -4590,15 +4467,18 @@
"tar": "^6.1.11"
}
},
"@noble/hashes": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-0.5.9.tgz",
"integrity": "sha512-7lN1Qh6d8DUGmfN36XRsbN/WcGIPNtTGhkw26vWId/DlCIGsYJJootTtPGghTLcn/AaXPx2Q0b3cacrwXa7OVw=="
"@noble/curves": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.0.0.tgz",
"integrity": "sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==",
"requires": {
"@noble/hashes": "1.3.0"
}
},
"@noble/secp256k1": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.0.tgz",
"integrity": "sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw=="
"@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg=="
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
@ -4759,6 +4639,30 @@
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
},
"@scure/base": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA=="
},
"@scure/bip32": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.0.tgz",
"integrity": "sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==",
"requires": {
"@noble/curves": "~1.0.0",
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"@scure/bip39": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.0.tgz",
"integrity": "sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==",
"requires": {
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"@sqltools/formatter": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
@ -4890,6 +4794,29 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.3.tgz",
"integrity": "sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg=="
},
"@types/node-fetch": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.3.tgz",
"integrity": "sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==",
"dev": true,
"requires": {
"@types/node": "*",
"form-data": "^3.0.0"
},
"dependencies": {
"form-data": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
"dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
}
}
},
"@types/object-hash": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-1.3.4.tgz",
@ -5211,6 +5138,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
"optional": true,
"requires": {
"buffer-xor": "^1.0.3",
"cipher-base": "^1.0.0",
@ -5220,27 +5148,6 @@
"safe-buffer": "^5.0.1"
}
},
"browserify-cipher": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
"integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
"requires": {
"browserify-aes": "^1.0.4",
"browserify-des": "^1.0.0",
"evp_bytestokey": "^1.0.0"
}
},
"browserify-des": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
"integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
"requires": {
"cipher-base": "^1.0.1",
"des.js": "^1.0.0",
"inherits": "^2.0.1",
"safe-buffer": "^5.1.2"
}
},
"buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@ -5263,15 +5170,8 @@
"buffer-xor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="
},
"bufferutil": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
"integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
"requires": {
"node-gyp-build": "^4.3.0"
}
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==",
"optional": true
},
"bytes": {
"version": "3.1.2",
@ -5366,6 +5266,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
"optional": true,
"requires": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
@ -5491,6 +5392,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
"optional": true,
"requires": {
"cipher-base": "^1.0.1",
"inherits": "^2.0.1",
@ -5519,23 +5421,6 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"devOptional": true
},
"cross-fetch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
"requires": {
"node-fetch": "2.6.7"
}
},
"d": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
"requires": {
"es5-ext": "^0.10.50",
"type": "^1.0.1"
}
},
"dataloader": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz",
@ -5577,15 +5462,6 @@
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
},
"des.js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
"integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
"requires": {
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0"
}
},
"destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
@ -5740,40 +5616,11 @@
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
"optional": true
},
"es5-ext": {
"version": "0.10.62",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
"integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
"requires": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"next-tick": "^1.1.0"
}
},
"es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"requires": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
"requires": {
"d": "^1.0.1",
"ext": "^1.1.2"
}
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -5793,6 +5640,7 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
"integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
"optional": true,
"requires": {
"md5.js": "^1.3.4",
"safe-buffer": "^5.1.1"
@ -5836,21 +5684,6 @@
"vary": "~1.1.2"
}
},
"ext": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"requires": {
"type": "^2.7.2"
},
"dependencies": {
"type": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
}
}
},
"fast-glob": {
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
@ -6059,6 +5892,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
"optional": true,
"requires": {
"inherits": "^2.0.4",
"readable-stream": "^3.6.0",
@ -6069,6 +5903,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"optional": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@ -6079,6 +5914,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"optional": true,
"requires": {
"safe-buffer": "~5.2.0"
}
@ -6293,11 +6129,6 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@ -6480,6 +6311,7 @@
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
"optional": true,
"requires": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1",
@ -6506,30 +6338,6 @@
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
},
"micro-base": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/micro-base/-/micro-base-0.10.2.tgz",
"integrity": "sha512-lqqJrT7lfJtDmmiQ4zRLZuIJBk96t0RAc5pCrrWpL9zDeH5i/SUL85mku9HqzTI/OCZ8EQ3aicbMW+eK5Nyu5w=="
},
"micro-bip32": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/micro-bip32/-/micro-bip32-0.1.0.tgz",
"integrity": "sha512-HxwYJzokbObqPHUqQuzRpCEqZ3EE4uHKrGlLX5ylt0ktD6m9LeS3RkWuQ1HApXEgrGMs3XgykN5Bic2YHE0f6Q==",
"requires": {
"@noble/hashes": "^0.5.7",
"@noble/secp256k1": "^1.3.4",
"micro-base": "^0.10.1"
}
},
"micro-bip39": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/micro-bip39/-/micro-bip39-0.1.3.tgz",
"integrity": "sha512-lEaRG/MKxFQvG19lfJfPkLIG0rgT28nWud3otN+VgAbrozGqXn2PLaZuYPsy9guQjIZWBTvoLw/HDJQxmMXjMA==",
"requires": {
"@noble/hashes": "^0.5.5",
"micro-base": "^0.10.1"
}
},
"micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
@ -6670,11 +6478,6 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
},
"next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"node-addon-api": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
@ -6851,19 +6654,15 @@
"dev": true
},
"nostr-tools": {
"version": "0.24.1",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-0.24.1.tgz",
"integrity": "sha512-+aUWblwNTYra8ZsjmfzxStr4XSKAb0gPsehNP3oBiSouLevqD3FWngc++kh8l+zfMYEPPGS6kS0i9iaq/5ZF6A==",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.9.0.tgz",
"integrity": "sha512-ZvFf1uiBqWLWhLBHD2nY0KsdSdNWKb3PrQUmYMWxSzfT4k48cDrDJu2qgULkOhQbFX7oty8IpaKnLvixhqefqA==",
"requires": {
"@noble/hashes": "^0.5.7",
"@noble/secp256k1": "^1.5.2",
"browserify-cipher": ">=1",
"buffer": ">=5",
"create-hash": "^1.2.0",
"cross-fetch": "^3.1.4",
"micro-bip32": "^0.1.0",
"micro-bip39": "^0.1.3",
"websocket-polyfill": "^0.0.3"
"@noble/curves": "1.0.0",
"@noble/hashes": "1.3.0",
"@scure/base": "1.1.1",
"@scure/bip32": "1.3.0",
"@scure/bip39": "1.2.0"
}
},
"npmlog": {
@ -7194,6 +6993,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
"optional": true,
"requires": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1"
@ -7672,16 +7472,6 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
},
"tstl": {
"version": "2.5.13",
"resolved": "https://registry.npmjs.org/tstl/-/tstl-2.5.13.tgz",
"integrity": "sha512-h9wayHHFI5+yqt8iau0vqH96cTNhezhZ/Fk/hrIdpfkiMu3lg9nzyvMfs5bIdX51IVzZO6DudLqhkL/rVXpT6g=="
},
"type": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
},
"type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@ -7696,14 +7486,6 @@
"mime-types": "~2.1.24"
}
},
"typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"requires": {
"is-typedarray": "^1.0.0"
}
},
"typeorm": {
"version": "0.3.10",
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.10.tgz",
@ -7812,14 +7594,6 @@
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
"integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw=="
},
"utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"requires": {
"node-gyp-build": "^4.3.0"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -7851,28 +7625,6 @@
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"websocket": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
"integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
"requires": {
"bufferutil": "^4.0.1",
"debug": "^2.2.0",
"es5-ext": "^0.10.50",
"typedarray-to-buffer": "^3.1.5",
"utf-8-validate": "^5.0.2",
"yaeti": "^0.0.6"
}
},
"websocket-polyfill": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz",
"integrity": "sha512-pF3kR8Uaoau78MpUmFfzbIRxXj9PeQrCuPepGE6JIsfsJ/o/iXr07Q2iQNzKSSblQJ0FiGWlS64N4pVSm+O3Dg==",
"requires": {
"tstl": "^2.0.7",
"websocket": "^1.0.28"
}
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@ -7938,11 +7690,6 @@
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
},
"yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
"integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug=="
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",

View file

@ -43,7 +43,7 @@
"grpc-tools": "^1.11.2",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"nostr-tools": "^0.24.1",
"nostr-tools": "^1.9.0",
"pg": "^8.4.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
@ -63,9 +63,10 @@
"@types/jsonwebtoken": "^8.5.9",
"@types/lodash": "^4.14.182",
"@types/node": "^16.11.10",
"@types/node-fetch": "^2.6.3",
"@types/uuid": "^8.3.4",
"nodemon": "^2.0.20",
"ts-node": "10.7.0",
"typescript": "4.5.2"
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -7,12 +7,13 @@ export type Logger = { log: (v: any) => void, error: (v: any) => void }
export type ServerOptions = {
allowCors?: true
staticFiles?: string
allowNotImplementedMethods?: true
allowNotImplementedMethods?: number
logger?: Logger
throwErrors?: true
GuestAuthGuard: (authorizationHeader?: string) => Promise<Types.GuestContext>
UserAuthGuard: (authorizationHeader?: string) => Promise<Types.UserContext>
AdminAuthGuard: (authorizationHeader?: string) => Promise<Types.AdminContext>
AppAuthGuard: (authorizationHeader?: string) => Promise<Types.AppContext>
decryptCallback: (encryptionDeviceId: string, body: any) => Promise<any>
encryptCallback: (encryptionDeviceId: string, plain: any) => Promise<any>
}
@ -51,19 +52,129 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.LndGetInfo) throw new Error('method: LndGetInfo is not implemented')
app.post('/api/lnd/getinfo', async (req, res) => {
app.post('/api/admin/lnd/getinfo', async (req, res) => {
try {
if (!methods.LndGetInfo) throw new Error('method: LndGetInfo is not implemented')
const authContext = await opts.AdminAuthGuard(req.headers['authorization'])
const encryptionDeviceId = req.headers['x-e2ee-device-id-x']
if (typeof encryptionDeviceId !== 'string' || encryptionDeviceId === '') throw new Error('invalid encryption header provided')
const request = await opts.decryptCallback(encryptionDeviceId, req.body)
const request = req.body
const error = Types.LndGetInfoRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
const response = await methods.LndGetInfo({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...await opts.encryptCallback(encryptionDeviceId, response)})
res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddApp) throw new Error('method: AddApp is not implemented')
app.post('/api/admin/app/add', async (req, res) => {
try {
if (!methods.AddApp) throw new Error('method: AddApp is not implemented')
const authContext = await opts.AdminAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.AddAppRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
const response = await methods.AddApp({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddAppUser) throw new Error('method: AddAppUser is not implemented')
app.post('/api/app/user/add', async (req, res) => {
try {
if (!methods.AddAppUser) throw new Error('method: AddAppUser is not implemented')
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.AddAppUserRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
const response = await methods.AddAppUser({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddAppInvoice) throw new Error('method: AddAppInvoice is not implemented')
app.post('/api/app/add/invoice', async (req, res) => {
try {
if (!methods.AddAppInvoice) throw new Error('method: AddAppInvoice is not implemented')
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.AddAppInvoiceRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
const response = await methods.AddAppInvoice({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddAppUserInvoice) throw new Error('method: AddAppUserInvoice is not implemented')
app.post('/api/app/user/add/invoice', async (req, res) => {
try {
if (!methods.AddAppUserInvoice) throw new Error('method: AddAppUserInvoice is not implemented')
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.AddAppUserInvoiceRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
const response = await methods.AddAppUserInvoice({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetAppUser) throw new Error('method: GetAppUser is not implemented')
app.post('/api/app/user/get', async (req, res) => {
try {
if (!methods.GetAppUser) throw new Error('method: GetAppUser is not implemented')
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.GetAppUserRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
const response = await methods.GetAppUser({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.PayAppUserInvoice) throw new Error('method: PayAppUserInvoice is not implemented')
app.post('/api/app/invoice/pay', async (req, res) => {
try {
if (!methods.PayAppUserInvoice) throw new Error('method: PayAppUserInvoice is not implemented')
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.PayAppUserInvoiceRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
const response = await methods.PayAppUserInvoice({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.SendAppUserToAppUserPayment) throw new Error('method: SendAppUserToAppUserPayment is not implemented')
app.post('/api/app/user/internal/pay', async (req, res) => {
try {
if (!methods.SendAppUserToAppUserPayment) throw new Error('method: SendAppUserToAppUserPayment is not implemented')
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.SendAppUserToAppUserPaymentRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
await methods.SendAppUserToAppUserPayment({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK'})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.SendAppUserToAppPayment) throw new Error('method: SendAppUserToAppPayment is not implemented')
app.post('/api/app/internal/pay', async (req, res) => {
try {
if (!methods.SendAppUserToAppPayment) throw new Error('method: SendAppUserToAppPayment is not implemented')
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.SendAppUserToAppPaymentRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
await methods.SendAppUserToAppPayment({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK'})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddUser) throw new Error('method: AddUser is not implemented')

View file

@ -8,6 +8,7 @@ export type ClientParams = {
retrieveGuestAuth: () => Promise<string | null>
retrieveUserAuth: () => Promise<string | null>
retrieveAdminAuth: () => Promise<string | null>
retrieveAppAuth: () => Promise<string | null>
encryptCallback: (plain: any) => Promise<any>
decryptCallback: (encrypted: any) => Promise<any>
deviceId: string
@ -39,17 +40,123 @@ export default (params: ClientParams) => ({
LndGetInfo: async (request: Types.LndGetInfoRequest): Promise<ResultError | ({ status: 'OK' }& Types.LndGetInfoResponse)> => {
const auth = await params.retrieveAdminAuth()
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
let finalRoute = '/api/lnd/getinfo'
const { data } = await axios.post(params.baseUrl + finalRoute, await params.encryptCallback(request), { headers: { 'authorization': auth, 'x-e2ee-device-id-x': params.deviceId } })
let finalRoute = '/api/admin/lnd/getinfo'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = await params.decryptCallback(data)
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.LndGetInfoResponseValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
AddApp: async (request: Types.AddAppRequest): Promise<ResultError | ({ status: 'OK' }& Types.AddAppResponse)> => {
const auth = await params.retrieveAdminAuth()
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
let finalRoute = '/api/admin/app/add'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.AddAppResponseValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
AddAppUser: async (request: Types.AddAppUserRequest): Promise<ResultError | ({ status: 'OK' }& Types.AppUser)> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
let finalRoute = '/api/app/user/add'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.AppUserValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
AddAppInvoice: async (request: Types.AddAppInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.NewInvoiceResponse)> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
let finalRoute = '/api/app/add/invoice'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.NewInvoiceResponseValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
AddAppUserInvoice: async (request: Types.AddAppUserInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.NewInvoiceResponse)> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
let finalRoute = '/api/app/user/add/invoice'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.NewInvoiceResponseValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetAppUser: async (request: Types.GetAppUserRequest): Promise<ResultError | ({ status: 'OK' }& Types.AppUser)> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
let finalRoute = '/api/app/user/get'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.AppUserValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
PayAppUserInvoice: async (request: Types.PayAppUserInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayAppUserInvoiceResponse)> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
let finalRoute = '/api/app/invoice/pay'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.PayAppUserInvoiceResponseValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
SendAppUserToAppUserPayment: async (request: Types.SendAppUserToAppUserPaymentRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
let finalRoute = '/api/app/user/internal/pay'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
SendAppUserToAppPayment: async (request: Types.SendAppUserToAppPaymentRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
let finalRoute = '/api/app/internal/pay'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
AddUser: async (request: Types.AddUserRequest): Promise<ResultError | ({ status: 'OK' }& Types.AddUserResponse)> => {
const auth = await params.retrieveGuestAuth()
if (auth === null) throw new Error('retrieveGuestAuth() returned null')

File diff suppressed because it is too large Load diff

BIN
proto/protoc-gen-pub.exe → proto/protoc-gen-pub Normal file → Executable file

Binary file not shown.

View file

@ -25,11 +25,19 @@ option (file_options) = {
{
id: "admin",
name: "Admin",
encrypted:true,
//encrypted:true,
context:{
key:"admin_id",
value:"string"
}
},
{
id:"app",
name:"App",
context:{
key:"app_id",
value: "string"
}
}
];
};
@ -73,11 +81,65 @@ service LightningPub {
option (http_method) = "post";
option (http_route) = "/api/encryption/exchange";
};
rpc LndGetInfo(structs.LndGetInfoRequest) returns (structs.LndGetInfoResponse){
option (auth_type) = "Admin";
option (http_method) = "post";
option (http_route) = "/api/lnd/getinfo";
option (http_route) = "/api/admin/lnd/getinfo";
};
// <App>
rpc AddApp(structs.AddAppRequest) returns (structs.AddAppResponse) {
option (auth_type) = "Admin";
option (http_method) = "post";
option (http_route) = "/api/admin/app/add";
};
rpc AddAppUser(structs.AddAppUserRequest)returns (structs.AppUser) {
option (auth_type) = "App";
option (http_method) = "post";
option (http_route) = "/api/app/user/add";
};
rpc AddAppInvoice(structs.AddAppInvoiceRequest) returns (structs.NewInvoiceResponse) {
option (auth_type) = "App";
option (http_method) = "post";
option (http_route) = "/api/app/add/invoice";
}
rpc AddAppUserInvoice(structs.AddAppUserInvoiceRequest) returns (structs.NewInvoiceResponse) {
option (auth_type) = "App";
option (http_method) = "post";
option (http_route) = "/api/app/user/add/invoice";
}
rpc GetAppUser(structs.GetAppUserRequest) returns (structs.AppUser) {
option (auth_type) = "App";
option (http_method) = "post";
option (http_route) = "/api/app/user/get";
}
rpc PayAppUserInvoice(structs.PayAppUserInvoiceRequest) returns (structs.PayAppUserInvoiceResponse) {
option (auth_type) = "App";
option (http_method) = "post";
option (http_route) = "/api/app/invoice/pay";
}
rpc SendAppUserToAppUserPayment(structs.SendAppUserToAppUserPaymentRequest) returns (structs.Empty) {
option (auth_type) = "App";
option (http_method) = "post";
option (http_route) = "/api/app/user/internal/pay";
}
rpc SendAppUserToAppPayment(structs.SendAppUserToAppPaymentRequest) returns (structs.Empty) {
option (auth_type) = "App";
option (http_method) = "post";
option (http_route) = "/api/app/internal/pay";
}
// </App>
rpc AddUser(structs.AddUserRequest)returns (structs.AddUserResponse){
option (auth_type) = "Guest";
option (http_method) = "post";

View file

@ -19,6 +19,69 @@ message LndGetInfoRequest {
message LndGetInfoResponse {
string alias = 1;
}
message AddAppRequest {
string name = 1;
}
message AddAppResponse {
string name = 1;
string id = 2;
string auth_token = 3;
}
message AddAppUserRequest {
string identifier = 1;
bool fail_if_exists = 2;
int64 balance = 3;
}
message AppUser {
string identifier = 1;
UserInfo info = 2;
int64 max_withdrawable = 3;
}
message AddAppInvoiceRequest {
string payer_identifier = 1;
string http_callback_url = 2;
NewInvoiceRequest invoice_req = 3;
}
message AddAppUserInvoiceRequest {
string receiver_identifier = 1;
string payer_identifier = 2;
string http_callback_url = 3;
NewInvoiceRequest invoice_req = 4;
}
message GetAppUserRequest {
string user_identifier = 1;
}
message PayAppUserInvoiceRequest {
string user_identifier = 1;
string invoice = 2;
int64 amount = 3;
}
message PayAppUserInvoiceResponse {
string preimage = 1;
int64 amount_paid = 2;
}
message SendAppUserToAppUserPaymentRequest {
string from_user_identifier = 1;
string to_user_identifier = 2;
int64 amount = 3;
}
message SendAppUserToAppPaymentRequest {
string from_user_identifier = 1;
int64 amount = 2;
}
enum AddressType {
WITNESS_PUBKEY_HASH = 0;
NESTED_PUBKEY_HASH = 1;
@ -61,6 +124,7 @@ message PayInvoiceRequest{
message PayInvoiceResponse{
string preimage = 1;
int64 amount_paid = 2;
}
message OpenChannelRequest{
@ -129,13 +193,18 @@ message GetUserOperationsRequest{
int64 latestOutgoingInvoice = 2;
int64 latestIncomingTx = 3;
int64 latestOutgoingTx = 4;
int64 latestIncomingUserToUserPayment = 5;
int64 latestOutgoingUserToUserPayment = 6;
}
enum UserOperationType {
INCOMING_TX =0;
OUTGOING_TX =1;
INCOMING_INVOICE =2;
OUTGOING_INVOICE=3;
OUTGOING_USER_TO_USER=4;
INCOMING_USER_TO_USER=5;
}
message UserOperation{
int64 paidAtUnix=1;
UserOperationType type = 2;
@ -144,14 +213,16 @@ message UserOperation{
}
message UserOperations {
int64 fromIndex=1;
int64 toIndex=2;
repeated UserOperation operations=3;
int64 toIndex=2;
repeated UserOperation operations=3;
}
message GetUserOperationsResponse{
UserOperations latestOutgoingInvoiceOperations=1;
UserOperations latestIncomingInvoiceOperations=2;
UserOperations latestOutgoingTxOperations=3;
UserOperations latestIncomingTxOperations=4;
UserOperations latestOutgoingUserToUserPayemnts=5;
UserOperations latestIncomingUserToUserPayemnts=6 ;
}
message AddProductRequest {

View file

@ -1,13 +1,34 @@
import { ServerOptions } from "../proto/autogenerated/ts/express_server";
import { AdminContext } from "../proto/autogenerated/ts/types";
import Main from './services/main'
const serverOptions = (mainHandler: Main): ServerOptions => {
return {
AdminAuthGuard: async (authHeader) => { console.log("admin auth login with header: " + authHeader); return { admin_id: "__Admin__" } },
UserAuthGuard: async (authHeader) => { return { user_id: mainHandler.DecodeUserToken(authHeader) } },
AdminAuthGuard: adminAuth,
AppAuthGuard: async (authHeader) => { return { app_id: mainHandler.applicationManager.DecodeAppToken(authHeader) } },
UserAuthGuard: async (authHeader) => { return { user_id: mainHandler.userManager.DecodeUserToken(authHeader) } },
GuestAuthGuard: async (_) => ({}),
encryptCallback: async (_, b) => b,
decryptCallback: async (_, b) => b,
throwErrors: true
//throwErrors: true
}
}
const adminAuth = async (header: string | undefined): Promise<AdminContext> => {
const AdminToken = process.env.ADMIN_TOKEN
if (!AdminToken) {
throw new Error("admin auth disabled")
}
if (!header) {
throw new Error("admin header not found")
}
let h = header
if (header.startsWith("Bearer ")) {
h = header.substring("Bearer ".length)
}
if (h !== AdminToken) {
throw new Error("admin token invalid")
}
return { admin_id: "admin1" }
}
export default serverOptions

View file

@ -18,10 +18,10 @@ import { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
const settings = LoadNosrtSettingsFromEnv(true)
const clientPrivateKey = generatePrivateKey()
const clientPublicKey = getPublicKey(Buffer.from(clientPrivateKey, "hex"))
const clientPublicKey = getPublicKey(clientPrivateKey)
const serverPrivateKey = generatePrivateKey()
const serverPublicKey = getPublicKey(Buffer.from(serverPrivateKey, "hex"))
const serverPublicKey = getPublicKey(serverPrivateKey)
const testPort = 4000
var userAuthHeader = ""
@ -30,6 +30,7 @@ const client = NewClient({
retrieveAdminAuth: async () => (""),
retrieveGuestAuth: async () => (""),
retrieveUserAuth: async () => userAuthHeader,
retrieveAppAuth: async () => (""),
decryptCallback: async (b) => b,
encryptCallback: async (b) => b,
deviceId: "device0"
@ -111,7 +112,7 @@ export default async (d: (message: string, failure?: boolean) => void) => {
const res = await client.AddUser({ name: "test", callbackUrl: "http://...", secret: "shhhhhht" })
if (res.status === 'ERROR') throw new Error(res.reason)
console.log(res)
const user = await mainHandler.storage.GetUser(res.userId)
const user = await mainHandler.storage.userStorage.GetUser(res.userId)
console.log(user)
userAuthHeader = res.authToken
d("create user ok")

View file

@ -11,9 +11,9 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
if (!pub || !nostrSettings.allowedPubs.includes(pub)) {
throw new Error("nostr pub invalid or not allowed" + pub)
}
let nostrUser = await mainHandler.storage.FindNostrUser(pub)
let nostrUser = await mainHandler.storage.userStorage.FindNostrUser(pub)
if (!nostrUser) { // TODO: add POW
nostrUser = await mainHandler.storage.AddNostrUser(pub)
nostrUser = await mainHandler.storage.userStorage.AddNostrUser(pub)
}
return { user_id: nostrUser.user.user_id }
}

View file

@ -1,6 +1,6 @@
import { OpenChannelRequest, Invoice } from "../../../proto/lnd/lightning";
export const AddInvoiceReq = (value: number, memo = "", privateHints = false, expiry = 60 * 60): Invoice => ({
export const AddInvoiceReq = (value: number, memo = "", expiry = 60 * 60, privateHints = false): Invoice => ({
expiry: BigInt(expiry),
memo: memo,
private: privateHints,

View file

@ -21,7 +21,7 @@ export default async (d: (message: string, failure?: boolean) => void) => {
console.log(addr)
d("new address ok")
const invoice = await lnd.NewInvoice(1000, "")
const invoice = await lnd.NewInvoice(1000, "", 60 * 60)
console.log(invoice)
d("new invoice ok")

View file

@ -80,6 +80,16 @@ export default class {
const res = await this.lightning.getInfo({}, DeadLineMetadata())
return res.response
}
async Health() {
if (!this.ready) {
throw new Error("not ready")
}
const info = await this.GetInfo()
if (!info.syncedToChain || !info.syncedToGraph) {
throw new Error("not ready")
}
}
checkReady() {
if (!this.ready) throw new Error("lnd not ready, warmup required before usage")
}
@ -141,9 +151,9 @@ export default class {
return res.response
}
async NewInvoice(value: number, memo: string): Promise<AddInvoiceResponse> {
async NewInvoice(value: number, memo: string, expiry: number): Promise<AddInvoiceResponse> {
this.checkReady()
const res = await this.lightning.addInvoice(AddInvoiceReq(value, memo), DeadLineMetadata())
const res = await this.lightning.addInvoice(AddInvoiceReq(value, memo, expiry), DeadLineMetadata())
return res.response
}

View file

@ -0,0 +1,103 @@
import jwt from 'jsonwebtoken'
import Storage from '../storage/index.js'
import * as Types from '../../../proto/autogenerated/ts/types.js'
import UserManager from './userManager.js'
import { MainSettings } from './settings.js'
import PaymentManager from './paymentManager.js'
import { InboundOptionals, defaultInvoiceExpiry } from '../storage/paymentStorage.js'
import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
export default class {
storage: Storage
settings: MainSettings
userManager: UserManager
paymentManager: PaymentManager
constructor(storage: Storage, settings: MainSettings, userManager: UserManager, paymentManager: PaymentManager) {
this.storage = storage
this.settings = settings
this.userManager = userManager
this.paymentManager = paymentManager
}
SignAppToken(appId: string): string {
return jwt.sign({ appId }, this.settings.jwtSecret);
}
DecodeAppToken(token?: string): string {
if (!token) throw new Error("empty app token provided")
let t = token
if (token.startsWith("Bearer ")) {
t = token.substring("Bearer ".length)
}
if (!t) throw new Error("no app token provided")
return (jwt.verify(token, this.settings.jwtSecret) as { appId: string }).appId
}
async AddApp(req: Types.AddAppRequest): Promise<Types.AddAppResponse> {
const app = await this.storage.applicationStorage.AddApplication(req.name)
return {
id: app.app_id,
name: app.name,
auth_token: this.SignAppToken(app.app_id)
}
}
async AddAppUser(appId: string, req: Types.AddAppUserRequest): Promise<Types.AppUser> {
let u: ApplicationUser
if (req.fail_if_exists) {
u = await this.storage.applicationStorage.AddApplicationUser(appId, req.identifier, req.balance)
} else {
u = await this.storage.applicationStorage.GetOrCreateApplicationUser(appId, req.identifier, req.balance)
}
return {
identifier: u.identifier,
info: {
userId: u.user.user_id,
balance: u.user.balance_sats
},
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats)
}
}
async AddAppInvoice(appId: string, req: Types.AddAppInvoiceRequest): Promise<Types.NewInvoiceResponse> {
const app = await this.storage.applicationStorage.GetApplication(appId)
const payer = await this.storage.applicationStorage.GetOrCreateApplicationUser(appId, req.payer_identifier, 0)
const opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user }
return this.paymentManager.NewInvoice(app.owner.user_id, req.invoice_req, opts)
}
async AddAppUserInvoice(appId: string, req: Types.AddAppUserInvoiceRequest): Promise<Types.NewInvoiceResponse> {
const receiver = await this.storage.applicationStorage.GetApplicationUser(appId, req.receiver_identifier)
const payer = await this.storage.applicationStorage.GetOrCreateApplicationUser(appId, req.payer_identifier, 0)
const opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user }
const appUserInvoice = await this.paymentManager.NewInvoice(receiver.user.user_id, req.invoice_req, opts)
return {
invoice: appUserInvoice.invoice
}
}
async GetAppUser(appId: string, req: Types.GetAppUserRequest): Promise<Types.AppUser> {
const user = await this.storage.applicationStorage.GetApplicationUser(appId, req.user_identifier)
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats)
return {
max_withdrawable: max, identifier: req.user_identifier, info: {
userId: user.user.user_id, balance: user.user.balance_sats
}
}
}
async PayAppUserInvoice(appId: string, req: Types.PayAppUserInvoiceRequest): Promise<Types.PayAppUserInvoiceResponse> {
const appUser = await this.storage.applicationStorage.GetApplicationUser(appId, req.user_identifier)
return this.paymentManager.PayInvoice(appUser.user.user_id, req)
}
async SendAppUserToAppUserPayment(appId: string, req: Types.SendAppUserToAppUserPaymentRequest): Promise<void> {
const fromUser = await this.storage.applicationStorage.GetApplicationUser(appId, req.from_user_identifier)
const toUser = await this.storage.applicationStorage.GetApplicationUser(appId, req.to_user_identifier)
await this.paymentManager.SendUserToUserPayment(fromUser.user.user_id, toUser.user.user_id, req.amount)
}
async SendAppUserToAppPayment(appId: string, req: Types.SendAppUserToAppPaymentRequest): Promise<void> {
const fromUser = await this.storage.applicationStorage.GetApplicationUser(appId, req.from_user_identifier)
const app = await this.storage.applicationStorage.GetApplication(appId)
await this.paymentManager.SendUserToUserPayment(fromUser.user.user_id, app.owner.user_id, req.amount)
}
}

View file

@ -1,11 +1,12 @@
import jwt from 'jsonwebtoken'
import { bech32 } from 'bech32'
import Storage, { LoadStorageSettingsFromEnv, StorageSettings } from '../storage/index.js'
import fetch from "node-fetch"
import Storage, { LoadStorageSettingsFromEnv } from '../storage/index.js'
import * as Types from '../../../proto/autogenerated/ts/types.js'
import LND, { AddressPaidCb, InvoicePaidCb, LndSettings, LoadLndSettingsFromEnv } from '../lnd/index.js'
import LND, { AddressPaidCb, InvoicePaidCb, LoadLndSettingsFromEnv } from '../lnd/index.js'
import { EnvMustBeInteger, EnvMustBeNonEmptyString } from '../helpers/envParser.js'
import { UserReceivingInvoice } from '../storage/entity/UserReceivingInvoice.js'
import ProductManager from './product.js'
import ProductManager from './productManager.js'
import ApplicationManager from './applicationManager.js'
import UserManager from './userManager.js'
import PaymentManager from './paymentManager.js'
import { MainSettings } from './settings.js'
export const LoadMainSettingsFromEnv = (test = false): MainSettings => {
return {
@ -16,6 +17,7 @@ export const LoadMainSettingsFromEnv = (test = false): MainSettings => {
outgoingTxFee: EnvMustBeInteger("SERVICE_FEE_OUTGOING_TX_PERCENT") / 100,
incomingInvoiceFee: EnvMustBeInteger("SERVICE_FEE_INCOMING_INVOICE_PERCENT") / 100,
outgoingInvoiceFee: EnvMustBeInteger("SERVICE_FEE_OUTGOING_INVOICE_PERCENT") / 100,
userToUserFee: EnvMustBeInteger("SERVICE_FEE_USER_TO_USER_PERCENT") / 100,
serviceUrl: EnvMustBeNonEmptyString("SERVICE_URL")
}
}
@ -27,280 +29,69 @@ type UserOperationsSub = {
newIncomingTx: (operation: Types.UserOperation) => void
newOutgoingTx: (operation: Types.UserOperation) => void
}
interface UserOperationInfo {
serial_id: number
paid_amount: number
paid_at_unix: number
}
const defaultLnurlPayMetadata = '[["text/plain", "lnurl pay to Lightning.pub"]]'
export default class {
storage: Storage
lnd: LND
settings: MainSettings
userOperationsSub: UserOperationsSub | null = null
productManager: ProductManager
applicationManager: ApplicationManager
userManager: UserManager
paymentManager: PaymentManager
constructor(settings: MainSettings) {
this.settings = settings
this.storage = new Storage(settings.storageSettings)
this.lnd = new LND(settings.lndSettings, this.addressPaidCb, this.invoicePaidCb)
this.productManager = new ProductManager(this.storage, this.lnd, this.settings)
}
getServiceFee(action: Types.UserOperationType, amount: number): number {
switch (action) {
case Types.UserOperationType.INCOMING_TX:
return Math.ceil(this.settings.incomingTxFee * amount)
case Types.UserOperationType.OUTGOING_TX:
return Math.ceil(this.settings.outgoingTxFee * amount)
case Types.UserOperationType.INCOMING_INVOICE:
return Math.ceil(this.settings.incomingInvoiceFee * amount)
case Types.UserOperationType.OUTGOING_INVOICE:
return Math.ceil(this.settings.outgoingInvoiceFee * amount)
default:
throw new Error("Unknown service action type")
}
this.userManager = new UserManager(this.storage, this.settings)
this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings)
this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings)
this.applicationManager = new ApplicationManager(this.storage, this.settings, this.userManager, this.paymentManager)
}
addressPaidCb: AddressPaidCb = (txOutput, address, amount) => {
this.storage.StartTransaction(async tx => {
const userAddress = await this.storage.GetAddressOwner(address, tx)
const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx)
if (!userAddress) { return }
const fee = this.getServiceFee(Types.UserOperationType.INCOMING_TX, amount)
const fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_TX, amount)
try {
// This call will fail if the transaction is already registered
const addedTx = await this.storage.AddAddressReceivingTransaction(userAddress, txOutput.hash, txOutput.index, amount, fee, tx)
await this.storage.IncrementUserBalance(userAddress.user.user_id, addedTx.paid_amount, tx)
const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction(userAddress, txOutput.hash, txOutput.index, amount, fee, tx)
await this.storage.userStorage.IncrementUserBalance(userAddress.user.user_id, addedTx.paid_amount - fee, tx)
} catch {
}
})
}
invoicePaidCb: InvoicePaidCb = (paymentRequest, amount) => {
invoicePaidCb: InvoicePaidCb = (paymentRequest, amount) => {
this.storage.StartTransaction(async tx => {
const userInvoice = await this.storage.GetInvoiceOwner(paymentRequest, tx)
const userInvoice = await this.storage.paymentStorage.GetInvoiceOwner(paymentRequest, tx)
if (!userInvoice || userInvoice.paid_at_unix > 0) { return }
const fee = this.getServiceFee(Types.UserOperationType.INCOMING_INVOICE, amount)
const fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_INVOICE, amount)
const maybeApp = await this.storage.applicationStorage.IsApplicationUser(userInvoice.user.user_id)
try {
// This call will fail if the invoice is already registered
await this.storage.FlagInvoiceAsPaid(userInvoice, amount, fee, tx)
await this.storage.IncrementUserBalance(userInvoice.user.user_id, amount, tx)
} catch { }
})
}
SignUserToken(userId: string): string {
return jwt.sign({ userId }, this.settings.jwtSecret);
}
DecodeUserToken(token?: string): string {
if (!token) throw new Error("empty auth token provided")
return (jwt.verify(token, this.settings.jwtSecret) as { userId: string }).userId
}
async AddBasicUser(req: Types.AddUserRequest): Promise<Types.AddUserResponse> {
const { user } = await this.storage.AddBasicUser(req.name, req.secret)
return {
userId: user.user_id,
authToken: this.SignUserToken(user.user_id)
}
}
async NewAddress(userId: string, req: Types.NewAddressRequest): Promise<Types.NewAddressResponse> {
const res = await this.lnd.NewAddress(req.addressType)
const userAddress = await this.storage.AddUserAddress(userId, res.address)
return {
address: userAddress.address
}
}
async NewInvoice(userId: string, req: Types.NewInvoiceRequest): Promise<Types.NewInvoiceResponse> {
const user = await this.storage.GetUser(userId)
const res = await this.lnd.NewInvoice(req.amountSats, req.memo)
const userInvoice = await this.storage.AddUserInvoice(user, res.paymentRequest)
return {
invoice: userInvoice.invoice
}
}
async lockUserWithMinBalance(userId: string, minBalance: number) {
return this.storage.StartTransaction(async tx => {
const user = await this.storage.GetUser(userId, tx)
if (user.balance_sats < minBalance) {
throw new Error("insufficient balance")
await this.storage.paymentStorage.FlagInvoiceAsPaid(userInvoice, amount, fee, tx)
await this.storage.userStorage.IncrementUserBalance(userInvoice.user.user_id, amount - fee, tx)
await this.triggerPaidCallback(userInvoice.callbackUrl)
} catch {
//TODO
}
// this call will fail if the user is already locked
await this.storage.LockUser(userId, tx)
})
}
GetMaxPayableInvoice(balance: number): number {
const maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.outgoingInvoiceFee)))
return this.lnd.GetMaxWithinLimit(maxWithinServiceFee)
}
async DecodeInvoice(req: Types.DecodeInvoiceRequest): Promise<Types.DecodeInvoiceResponse> {
const decoded = await this.lnd.DecodeInvoice(req.invoice)
return {
amount: Number(decoded.numSatoshis)
async triggerPaidCallback(url: string) {
console.log(url)
if (!url) {
return
}
}
async PayInvoice(userId: string, req: Types.PayInvoiceRequest): Promise<Types.PayInvoiceResponse> {
const decoded = await this.lnd.DecodeInvoice(req.invoice)
if (decoded.numSatoshis !== 0n && req.amount !== 0) {
throw new Error("invoice has value, do not provide amount the the request")
try {
await fetch(url + "&ok=true")
} catch (err: any) {
console.log("error sending cb", err)
}
if (decoded.numSatoshis === 0n && req.amount === 0) {
throw new Error("invoice has no value, an amount must be provided in the request")
}
const payAmount = req.amount !== 0 ? req.amount : Number(decoded.numSatoshis)
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, payAmount)
const totalAmountToDecrement = payAmount + serviceFee
const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
await this.lockUserWithMinBalance(userId, totalAmountToDecrement + routingFeeLimit)
const payment = await this.lnd.PayInvoice(req.invoice, req.amount, routingFeeLimit)
await this.storage.DecrementUserBalance(userId, totalAmountToDecrement + Number(payment.feeSat))
await this.storage.UnlockUser(userId)
await this.storage.AddUserInvoicePayment(userId, req.invoice, payAmount, Number(payment.feeSat), serviceFee)
return {
preimage: payment.paymentPreimage
}
}
async PayAddress(userId: string, req: Types.PayAddressRequest): Promise<Types.PayAddressResponse> {
const estimate = await this.lnd.EstimateChainFees(req.address, req.amoutSats, req.targetConf)
const satPerVByte = Number(estimate.satPerVbyte)
const chainFees = Number(estimate.feeSat)
const total = req.amoutSats + chainFees
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, req.amoutSats)
await this.lockUserWithMinBalance(userId, total + serviceFee)
const payment = await this.lnd.PayAddress(req.address, req.amoutSats, satPerVByte)
await this.storage.DecrementUserBalance(userId, total + serviceFee)
await this.storage.UnlockUser(userId)
await this.storage.AddUserTransactionPayment(userId, req.address, payment.txid, 0, req.amoutSats, chainFees, serviceFee)
return {
txId: payment.txid
}
}
balanceCheckUrl(k1: string): string {
return `${this.settings.serviceUrl}/api/guest/lnurl_withdraw/info?k1=${k1}`
}
async GetLnurlChannelLink(userId: string): Promise<Types.LnurlLinkResponse> {
const key = await this.storage.AddUserEphemeralKey(userId, 'balanceCheck')
return {
lnurl: this.encodeLnurl(this.balanceCheckUrl(key.key)),
k1: key.key
}
}
async GetLnurlWithdrawInfo(balanceCheckK1: string): Promise<Types.LnurlWithdrawInfoResponse> {
const key = await this.storage.UseUserEphemeralKey(balanceCheckK1, 'balanceCheck')
const maxWithdrawable = this.GetMaxPayableInvoice(key.user.balance_sats)
const callbackK1 = await this.storage.AddUserEphemeralKey(key.user.user_id, 'withdraw')
const newBalanceCheckK1 = await this.storage.AddUserEphemeralKey(key.user.user_id, 'balanceCheck')
const payInfoK1 = await this.storage.AddUserEphemeralKey(key.user.user_id, 'pay')
return {
tag: "withdrawRequest",
callback: `${this.settings.serviceUrl}/api/guest/lnurl_withdraw/handle`,
defaultDescription: "lnurl withdraw from lightning.pub",
k1: callbackK1.key,
maxWithdrawable: maxWithdrawable,
minWithdrawable: 0,
balanceCheck: this.balanceCheckUrl(newBalanceCheckK1.key),
payLink: `${this.settings.serviceUrl}/api/guest/lnurl_pay/info?k1=${payInfoK1.key}`,
}
}
async HandleLnurlWithdraw(k1: string, invoice: string): Promise<void> {
const key = await this.storage.UseUserEphemeralKey(k1, 'withdraw')
this.PayInvoice(key.user.user_id, {
invoice: invoice,
amount: 0
}).catch(err => {
console.error("error sending payment for lnurl withdraw to ", key.user.user_id, err)
})
}
async GetLnurlPayInfo(payInfoK1: string): Promise<Types.LnurlPayInfoResponse> {
const key = await this.storage.UseUserEphemeralKey(payInfoK1, 'payInfo')
const payK1 = await this.storage.AddUserEphemeralKey(key.user.user_id, 'pay')
return {
tag: 'payRequest',
callback: `${this.settings.serviceUrl}/api/guest/lnurl_pay/handle?k1=${payK1.key}`,
maxSendable: 10000000,
minSendable: 0,
metadata: defaultLnurlPayMetadata
}
}
async HandleLnurlPay(payK1: string, amountMillis: number): Promise<Types.HandleLnurlPayResponse> {
const key = await this.storage.UseUserEphemeralKey(payK1, 'pay')
const sats = amountMillis / 1000
if (!Number.isInteger(sats)) {
throw new Error("millisats amount must be integer sats amount")
}
const invoice = await this.NewInvoice(key.user.user_id, {
amountSats: sats,
memo: defaultLnurlPayMetadata
})
return {
pr: invoice.invoice,
routes: []
}
}
async OpenChannel(userId: string, req: Types.OpenChannelRequest): Promise<Types.OpenChannelResponse> { throw new Error("WIP") }
mapOperations(operations: UserOperationInfo[], type: Types.UserOperationType, inbound: boolean): Types.UserOperations {
if (operations.length === 0) {
return {
fromIndex: 0,
toIndex: 0,
operations: []
}
}
return {
toIndex: operations[0].serial_id,
fromIndex: operations[operations.length - 1].serial_id,
operations: operations.map((o: UserOperationInfo): Types.UserOperation => ({
inbound,
type,
amount: o.paid_amount,
paidAtUnix: o.paid_at_unix
}))
}
}
async GetUserInfo(userId: string): Promise<Types.UserInfo> {
const user = await this.storage.GetUser(userId)
return {
userId: userId,
balance: user.balance_sats
}
}
async GetUserOperations(userId: string, req: Types.GetUserOperationsRequest): Promise<Types.GetUserOperationsResponse> {
const [outgoingInvoices, outgoingTransactions, incomingInvoices, incomingTransactions] = await Promise.all([
this.storage.GetUserInvoicePayments(userId, req.latestOutgoingInvoice),
this.storage.GetUserTransactionPayments(userId, req.latestOutgoingTx),
this.storage.GetUserInvoicesFlaggedAsPaid(userId, req.latestIncomingInvoice),
this.storage.GetUserReceivingTransactions(userId, req.latestIncomingTx)
])
return {
latestIncomingInvoiceOperations: this.mapOperations(incomingInvoices, Types.UserOperationType.INCOMING_INVOICE, true),
latestIncomingTxOperations: this.mapOperations(incomingTransactions, Types.UserOperationType.INCOMING_TX, true),
latestOutgoingInvoiceOperations: this.mapOperations(outgoingInvoices, Types.UserOperationType.OUTGOING_INVOICE, false),
latestOutgoingTxOperations: this.mapOperations(outgoingTransactions, Types.UserOperationType.OUTGOING_TX, false)
}
}
encodeLnurl(base: string) {
if (!base || typeof base !== 'string') {
throw new Error("provided string for lnurl encode is not a string or is an empty string")
}
let words = bech32.toWords(Buffer.from(base, 'utf8'));
return bech32.encode('lnurl', words, 1023);
}
}

View file

@ -0,0 +1,252 @@
import { bech32 } from 'bech32'
import Storage from '../storage/index.js'
import * as Types from '../../../proto/autogenerated/ts/types.js'
import LND from '../lnd/index.js'
import { MainSettings } from './settings.js'
import { InboundOptionals, defaultInvoiceExpiry } from '../storage/paymentStorage.js'
interface UserOperationInfo {
serial_id: number
paid_amount: number
paid_at_unix: number
}
const defaultLnurlPayMetadata = '[["text/plain", "lnurl pay to Lightning.pub"]]'
export default class {
storage: Storage
settings: MainSettings
lnd: LND
constructor(storage: Storage, lnd: LND, settings: MainSettings) {
this.storage = storage
this.settings = settings
this.lnd = lnd
}
getServiceFee(action: Types.UserOperationType, amount: number): number {
switch (action) {
case Types.UserOperationType.INCOMING_TX:
return Math.ceil(this.settings.incomingTxFee * amount)
case Types.UserOperationType.OUTGOING_TX:
return Math.ceil(this.settings.outgoingTxFee * amount)
case Types.UserOperationType.INCOMING_INVOICE:
return Math.ceil(this.settings.incomingInvoiceFee * amount)
case Types.UserOperationType.OUTGOING_INVOICE:
return Math.ceil(this.settings.outgoingInvoiceFee * amount)
case Types.UserOperationType.OUTGOING_USER_TO_USER || Types.UserOperationType.INCOMING_USER_TO_USER:
return Math.ceil(this.settings.userToUserFee * amount)
default:
throw new Error("Unknown service action type")
}
}
async NewAddress(userId: string, req: Types.NewAddressRequest): Promise<Types.NewAddressResponse> {
const res = await this.lnd.NewAddress(req.addressType)
const userAddress = await this.storage.paymentStorage.AddUserAddress(userId, res.address)
return {
address: userAddress.address
}
}
async NewInvoice(userId: string, req: Types.NewInvoiceRequest, options: InboundOptionals = { expiry: defaultInvoiceExpiry }): Promise<Types.NewInvoiceResponse> {
const user = await this.storage.userStorage.GetUser(userId)
const res = await this.lnd.NewInvoice(req.amountSats, req.memo, options.expiry)
const userInvoice = await this.storage.paymentStorage.AddUserInvoice(user, res.paymentRequest, options)
return {
invoice: userInvoice.invoice
}
}
async lockUserWithMinBalance(userId: string, minBalance: number) {
return this.storage.StartTransaction(async tx => {
const user = await this.storage.userStorage.GetUser(userId, tx)
if (user.balance_sats < minBalance) {
throw new Error("insufficient balance")
}
// this call will fail if the user is already locked
await this.storage.userStorage.LockUser(userId, tx)
})
}
GetMaxPayableInvoice(balance: number): number {
const maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.outgoingInvoiceFee)))
return this.lnd.GetMaxWithinLimit(maxWithinServiceFee)
}
async DecodeInvoice(req: Types.DecodeInvoiceRequest): Promise<Types.DecodeInvoiceResponse> {
const decoded = await this.lnd.DecodeInvoice(req.invoice)
return {
amount: Number(decoded.numSatoshis)
}
}
async PayInvoice(userId: string, req: Types.PayInvoiceRequest): Promise<Types.PayInvoiceResponse> {
const decoded = await this.lnd.DecodeInvoice(req.invoice)
if (decoded.numSatoshis !== 0n && req.amount !== 0) {
throw new Error("invoice has value, do not provide amount the the request")
}
if (decoded.numSatoshis === 0n && req.amount === 0) {
throw new Error("invoice has no value, an amount must be provided in the request")
}
const payAmount = req.amount !== 0 ? req.amount : Number(decoded.numSatoshis)
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, payAmount)
const totalAmountToDecrement = payAmount + serviceFee
const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
await this.lockUserWithMinBalance(userId, totalAmountToDecrement + routingFeeLimit)
const payment = await this.lnd.PayInvoice(req.invoice, req.amount, routingFeeLimit)
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + Number(payment.feeSat))
await this.storage.userStorage.UnlockUser(userId)
await this.storage.paymentStorage.AddUserInvoicePayment(userId, req.invoice, payAmount, Number(payment.feeSat), serviceFee)
return {
preimage: payment.paymentPreimage,
amount_paid: Number(payment.valueSat)
}
}
async PayAddress(userId: string, req: Types.PayAddressRequest): Promise<Types.PayAddressResponse> {
const estimate = await this.lnd.EstimateChainFees(req.address, req.amoutSats, req.targetConf)
const satPerVByte = Number(estimate.satPerVbyte)
const chainFees = Number(estimate.feeSat)
const total = req.amoutSats + chainFees
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, req.amoutSats)
await this.lockUserWithMinBalance(userId, total + serviceFee)
const payment = await this.lnd.PayAddress(req.address, req.amoutSats, satPerVByte)
await this.storage.userStorage.DecrementUserBalance(userId, total + serviceFee)
await this.storage.userStorage.UnlockUser(userId)
await this.storage.paymentStorage.AddUserTransactionPayment(userId, req.address, payment.txid, 0, req.amoutSats, chainFees, serviceFee)
return {
txId: payment.txid
}
}
balanceCheckUrl(k1: string): string {
return `${this.settings.serviceUrl}/api/guest/lnurl_withdraw/info?k1=${k1}`
}
async GetLnurlChannelLink(userId: string): Promise<Types.LnurlLinkResponse> {
const key = await this.storage.paymentStorage.AddUserEphemeralKey(userId, 'balanceCheck')
return {
lnurl: this.encodeLnurl(this.balanceCheckUrl(key.key)),
k1: key.key
}
}
async GetLnurlWithdrawInfo(balanceCheckK1: string): Promise<Types.LnurlWithdrawInfoResponse> {
const key = await this.storage.paymentStorage.UseUserEphemeralKey(balanceCheckK1, 'balanceCheck')
const maxWithdrawable = this.GetMaxPayableInvoice(key.user.balance_sats)
const callbackK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'withdraw')
const newBalanceCheckK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'balanceCheck')
const payInfoK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'pay')
return {
tag: "withdrawRequest",
callback: `${this.settings.serviceUrl}/api/guest/lnurl_withdraw/handle`,
defaultDescription: "lnurl withdraw from lightning.pub",
k1: callbackK1.key,
maxWithdrawable: maxWithdrawable,
minWithdrawable: 0,
balanceCheck: this.balanceCheckUrl(newBalanceCheckK1.key),
payLink: `${this.settings.serviceUrl}/api/guest/lnurl_pay/info?k1=${payInfoK1.key}`,
}
}
async HandleLnurlWithdraw(k1: string, invoice: string): Promise<void> {
const key = await this.storage.paymentStorage.UseUserEphemeralKey(k1, 'withdraw')
this.PayInvoice(key.user.user_id, {
invoice: invoice,
amount: 0
}).catch(err => {
console.error("error sending payment for lnurl withdraw to ", key.user.user_id, err)
})
}
async GetLnurlPayInfo(payInfoK1: string): Promise<Types.LnurlPayInfoResponse> {
const key = await this.storage.paymentStorage.UseUserEphemeralKey(payInfoK1, 'payInfo')
const payK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'pay')
return {
tag: 'payRequest',
callback: `${this.settings.serviceUrl}/api/guest/lnurl_pay/handle?k1=${payK1.key}`,
maxSendable: 10000000,
minSendable: 0,
metadata: defaultLnurlPayMetadata
}
}
async HandleLnurlPay(payK1: string, amountMillis: number): Promise<Types.HandleLnurlPayResponse> {
const key = await this.storage.paymentStorage.UseUserEphemeralKey(payK1, 'pay')
const sats = amountMillis / 1000
if (!Number.isInteger(sats)) {
throw new Error("millisats amount must be integer sats amount")
}
const invoice = await this.NewInvoice(key.user.user_id, {
amountSats: sats,
memo: defaultLnurlPayMetadata
})
return {
pr: invoice.invoice,
routes: []
}
}
async OpenChannel(userId: string, req: Types.OpenChannelRequest): Promise<Types.OpenChannelResponse> { throw new Error("WIP") }
mapOperations(operations: UserOperationInfo[], type: Types.UserOperationType, inbound: boolean): Types.UserOperations {
if (operations.length === 0) {
return {
fromIndex: 0,
toIndex: 0,
operations: []
}
}
return {
toIndex: operations[0].serial_id,
fromIndex: operations[operations.length - 1].serial_id,
operations: operations.map((o: UserOperationInfo): Types.UserOperation => ({
inbound,
type,
amount: o.paid_amount,
paidAtUnix: o.paid_at_unix
}))
}
}
async GetUserOperations(userId: string, req: Types.GetUserOperationsRequest): Promise<Types.GetUserOperationsResponse> {
const [outgoingInvoices, outgoingTransactions, incomingInvoices, incomingTransactions, incomingUserToUser, outgoingUserToUser] = await Promise.all([
this.storage.paymentStorage.GetUserInvoicePayments(userId, req.latestOutgoingInvoice),
this.storage.paymentStorage.GetUserTransactionPayments(userId, req.latestOutgoingTx),
this.storage.paymentStorage.GetUserInvoicesFlaggedAsPaid(userId, req.latestIncomingInvoice),
this.storage.paymentStorage.GetUserReceivingTransactions(userId, req.latestIncomingTx),
this.storage.paymentStorage.GetUserToUserReceivedPayments(userId, req.latestIncomingUserToUserPayment),
this.storage.paymentStorage.GetUserToUserSentPayments(userId, req.latestOutgoingUserToUserPayment)
])
return {
latestIncomingInvoiceOperations: this.mapOperations(incomingInvoices, Types.UserOperationType.INCOMING_INVOICE, true),
latestIncomingTxOperations: this.mapOperations(incomingTransactions, Types.UserOperationType.INCOMING_TX, true),
latestOutgoingInvoiceOperations: this.mapOperations(outgoingInvoices, Types.UserOperationType.OUTGOING_INVOICE, false),
latestOutgoingTxOperations: this.mapOperations(outgoingTransactions, Types.UserOperationType.OUTGOING_TX, false),
latestIncomingUserToUserPayemnts: this.mapOperations(incomingUserToUser, Types.UserOperationType.INCOMING_USER_TO_USER, true),
latestOutgoingUserToUserPayemnts: this.mapOperations(outgoingUserToUser, Types.UserOperationType.OUTGOING_USER_TO_USER, false)
}
}
async SendUserToUserPayment(fromUserId: string, toUserId: string, amount: number) {
await this.storage.StartTransaction(async tx => {
const fromUser = await this.storage.userStorage.GetUser(fromUserId, tx)
const toUser = await this.storage.userStorage.GetUser(toUserId, tx)
if (fromUser.balance_sats < amount) {
throw new Error("not enough balance to send user to user payment")
}
const fee = this.getServiceFee(Types.UserOperationType.OUTGOING_USER_TO_USER, amount)
const toIncrement = amount - fee
await this.storage.userStorage.DecrementUserBalance(fromUser.user_id, amount, tx)
await this.storage.userStorage.IncrementUserBalance(toUser.user_id, toIncrement, tx)
await this.storage.paymentStorage.AddUserToUserPayment(fromUserId, toUserId, amount, fee)
})
}
encodeLnurl(base: string) {
if (!base || typeof base !== 'string') {
throw new Error("provided string for lnurl encode is not a string or is an empty string")
}
let words = bech32.toWords(Buffer.from(base, 'utf8'));
return bech32.encode('lnurl', words, 1023);
}
}

View file

@ -3,20 +3,22 @@ import * as Types from '../../../proto/autogenerated/ts/types.js'
import LND from '../lnd/index.js'
import { MainSettings } from './settings.js'
import PaymentManager from './paymentManager.js'
import { defaultInvoiceExpiry } from '../storage/paymentStorage.js'
export default class {
storage: Storage
settings: MainSettings
lnd: LND
constructor(storage: Storage, lnd: LND, settings: MainSettings) {
paymentManager: PaymentManager
constructor(storage: Storage, paymentManager: PaymentManager, settings: MainSettings) {
this.storage = storage
this.settings = settings
this.lnd = lnd
this.paymentManager = paymentManager
}
async AddProduct(userId: string, req: Types.AddProductRequest): Promise<Types.Product> {
const user = await this.storage.GetUser(userId)
const user = await this.storage.userStorage.GetUser(userId)
const newProduct = await this.storage.productStorage.AddProduct(req.name, req.price_sats, user)
return {
id: newProduct.product_id,
@ -27,10 +29,11 @@ export default class {
async NewProductInvoice(id: string): Promise<Types.NewInvoiceResponse> {
const product = await this.storage.productStorage.GetProduct(id)
const newInvoice = await this.lnd.NewInvoice(product.price_sats, product.name)
await this.storage.AddUserInvoice(product.owner, newInvoice.paymentRequest, product)
const productInvoice = await this.paymentManager.NewInvoice(product.owner.user_id, {
amountSats: product.price_sats, memo: product.name
}, { product, expiry: defaultInvoiceExpiry })
return {
invoice: newInvoice.paymentRequest
invoice: productInvoice.invoice
}
}
}

View file

@ -8,5 +8,7 @@ export type MainSettings = {
outgoingTxFee: number
incomingInvoiceFee: number
outgoingInvoiceFee: number
userToUserFee: number
serviceUrl: string
}

View file

@ -0,0 +1,41 @@
import jwt from 'jsonwebtoken'
import Storage from '../storage/index.js'
import * as Types from '../../../proto/autogenerated/ts/types.js'
import { MainSettings } from './settings.js'
export default class {
storage: Storage
settings: MainSettings
constructor(storage: Storage, settings: MainSettings) {
this.storage = storage
this.settings = settings
}
SignUserToken(userId: string): string {
return jwt.sign({ userId }, this.settings.jwtSecret);
}
DecodeUserToken(token?: string): string {
if (!token) throw new Error("empty user token provided")
let t = token
if (token.startsWith("Bearer ")) {
t = token.substring("Bearer ".length)
}
if (!t) throw new Error("no user token provided")
return (jwt.verify(token, this.settings.jwtSecret) as { userId: string }).userId
}
async AddBasicUser(req: Types.AddUserRequest): Promise<Types.AddUserResponse> {
const { user } = await this.storage.userStorage.AddBasicUser(req.name, req.secret)
return {
userId: user.user_id,
authToken: this.SignUserToken(user.user_id)
}
}
async GetUserInfo(userId: string): Promise<Types.UserInfo> {
const user = await this.storage.userStorage.GetUser(userId)
return {
userId: userId,
balance: user.balance_sats
}
}
}

View file

@ -1,6 +1,4 @@
import { relayPool, Subscription, Event } from 'nostr-tools'
//@ts-ignore
import { decrypt, encrypt } from 'nostr-tools/nip04.js'
import { SimplePool, Sub, Event, UnsignedEvent, nip04, getEventHash, signEvent } from 'nostr-tools'
const handledEvents: string[] = [] // TODO: - big memory leak here, add TTL
export type NostrSettings = {
privateKey: string
@ -74,52 +72,46 @@ const sendToNostr = (pub: string, message: string) => {
send({ type: 'ready' })
export default class Handler {
pool = relayPool()
pool = new SimplePool()
settings: NostrSettings
sub: Subscription
sub: Sub
constructor(settings: NostrSettings, eventCallback: (event: NostrEvent) => void) {
this.settings = settings
this.pool.setPrivateKey(settings.privateKey)
settings.relays.forEach(relay => {
try {
this.pool.addRelay(relay, { read: true, write: true })
} catch (e) {
console.error("cannot add relay:", relay)
}
});
this.sub = this.pool.sub({
//@ts-ignore
filter: {
console.log(settings)
this.sub = this.pool.sub(settings.relays, [
{
since: Math.ceil(Date.now() / 1000),
kinds: [4],
'#p': [settings.publicKey],
},
cb: async (e, relay) => {
if (e.kind !== 4 || !e.pubkey) {
return
}
//@ts-ignore
const eventId = e.id as string
if (handledEvents.includes(eventId)) {
console.log("event already handled")
return
}
handledEvents.push(eventId)
eventCallback({ id: eventId, content: decrypt(this.settings.privateKey, e.pubkey, e.content), pub: e.pubkey })
}
])
this.sub.on("event", async (e) => {
if (e.kind !== 4 || !e.pubkey) {
return
}
//@ts-ignore
const eventId = e.id as string
if (handledEvents.includes(eventId)) {
console.log("event already handled")
return
}
handledEvents.push(eventId)
eventCallback({ id: eventId, content: await nip04.decrypt(this.settings.privateKey, e.pubkey, e.content), pub: e.pubkey })
})
}
Send(nostrPub: string, message: string) {
this.pool.publish({
content: encrypt(this.settings.privateKey, nostrPub, message),
async Send(nostrPub: string, message: string) {
const event: UnsignedEvent = {
content: await nip04.encrypt(this.settings.privateKey, nostrPub, message),
created_at: Math.floor(Date.now() / 1000),
kind: 4,
pubkey: this.settings.publicKey,
//@ts-ignore
tags: [['p', nostrPub]]
}, (status, url) => {
console.log("sent message to", nostrPub) // TODO
})
tags: [['p', nostrPub]],
}
const eventId = getEventHash(event)
const sign = signEvent(event, this.settings.privateKey)
const op = this.pool.publish(this.settings.relays,
{ ...event, id: eventId, sig: sign })
op.on('failed', (reason: string) => { console.log('failed to send message cuz: ', reason) })
}
}

View file

@ -8,10 +8,10 @@ export const ignore = true
const settings = LoadNosrtSettingsFromEnv(true)
const clientPrivateKey = generatePrivateKey()
const clientPublicKey = getPublicKey(Buffer.from(clientPrivateKey, "hex"))
const clientPublicKey = getPublicKey(clientPrivateKey)
const serverPrivateKey = generatePrivateKey()
const serverPublicKey = getPublicKey(Buffer.from(serverPrivateKey, "hex"))
const serverPublicKey = getPublicKey(serverPrivateKey)
let clientNostr: NostrHandler
let serverNostr: NostrHandler

View file

@ -3,7 +3,7 @@ import Main from '../main/index.js'
export default (mainHandler: Main): Types.ServerMethods => {
return {
EncryptionExchange: async (ctx, req) => { },
Health: async (ctx) => { },
Health: async (ctx) => { await mainHandler.lnd.Health() },
LndGetInfo: async (ctx) => {
const info = await mainHandler.lnd.GetInfo()
return { alias: info.alias }
@ -15,16 +15,16 @@ export default (mainHandler: Main): Types.ServerMethods => {
secret_CustomCheck: secret => secret.length >= 8
})
if (err != null) throw new Error(err.message)
return mainHandler.AddBasicUser(req)
return mainHandler.userManager.AddBasicUser(req)
},
AuthUser: async (ctx, req) => {
throw new Error("unimplemented")
},
GetUserInfo: async (ctx) => {
return mainHandler.GetUserInfo(ctx.user_id)
return mainHandler.userManager.GetUserInfo(ctx.user_id)
},
GetUserOperations: async (ctx, req) => {
return mainHandler.GetUserOperations(ctx.user_id, req)
return mainHandler.paymentManager.GetUserOperations(ctx.user_id, req)
},
OpenChannel: async (ctx, req) => {
const err = Types.OpenChannelRequestValidate(req, {
@ -33,10 +33,10 @@ export default (mainHandler: Main): Types.ServerMethods => {
destination_CustomCheck: dest => dest !== ""
})
if (err != null) throw new Error(err.message)
return mainHandler.OpenChannel(ctx.user_id, req)
return mainHandler.paymentManager.OpenChannel(ctx.user_id, req)
},
NewAddress: async (ctx, req) => {
return mainHandler.NewAddress(ctx.user_id, req)
return mainHandler.paymentManager.NewAddress(ctx.user_id, req)
},
PayAddress: async (ctx, req) => {
const err = Types.PayAddressRequestValidate(req, {
@ -45,41 +45,41 @@ export default (mainHandler: Main): Types.ServerMethods => {
targetConf_CustomCheck: target => target > 0
})
if (err != null) throw new Error(err.message)
return mainHandler.PayAddress(ctx.user_id, req)
return mainHandler.paymentManager.PayAddress(ctx.user_id, req)
},
NewInvoice: async (ctx, req) => {
return mainHandler.NewInvoice(ctx.user_id, req)
return mainHandler.paymentManager.NewInvoice(ctx.user_id, req)
},
DecodeInvoice: async (ctx, req) => {
return mainHandler.DecodeInvoice(req)
return mainHandler.paymentManager.DecodeInvoice(req)
},
PayInvoice: async (ctx, req) => {
const err = Types.PayInvoiceRequestValidate(req, {
invoice_CustomCheck: invoice => invoice !== ''
})
if (err != null) throw new Error(err.message)
return mainHandler.PayInvoice(ctx.user_id, req)
return mainHandler.paymentManager.PayInvoice(ctx.user_id, req)
},
GetLnurlWithdrawLink: async (ctx) => {
return mainHandler.GetLnurlChannelLink(ctx.user_id)
return mainHandler.paymentManager.GetLnurlChannelLink(ctx.user_id)
},
GetLnurlWithdrawInfo: async (ctx) => {
if (!ctx.k1) {
throw new Error("invalid lnurl withdraw to get info")
}
return mainHandler.GetLnurlWithdrawInfo(ctx.k1)
return mainHandler.paymentManager.GetLnurlWithdrawInfo(ctx.k1)
},
HandleLnurlWithdraw: async (ctx) => {
if (!ctx.k1 || !ctx.pr) {
throw new Error("invalid lnurl withdraw to handle")
}
return mainHandler.HandleLnurlWithdraw(ctx.k1, ctx.pr)
return mainHandler.paymentManager.HandleLnurlWithdraw(ctx.k1, ctx.pr)
},
GetLnurlPayInfo: async (ctx) => {
if (!ctx.k1) {
throw new Error("invalid lnurl pay to get info")
}
return mainHandler.GetLnurlPayInfo(ctx.k1)
return mainHandler.paymentManager.GetLnurlPayInfo(ctx.k1)
},
HandleLnurlPay: async (ctx) => {
if (!ctx.k1 || !ctx.amount) {
@ -88,7 +88,7 @@ export default (mainHandler: Main): Types.ServerMethods => {
if (isNaN(+ctx.amount)) {
throw new Error("invalid amount in lnurl pay to handle")
}
return mainHandler.HandleLnurlPay(ctx.k1, +ctx.amount)
return mainHandler.paymentManager.HandleLnurlPay(ctx.k1, +ctx.amount)
},
AddProduct: async (ctx, req) => {
return mainHandler.productManager.AddProduct(ctx.user_id, req)
@ -102,5 +102,66 @@ export default (mainHandler: Main): Types.ServerMethods => {
GetLNURLChannelLink: async (ctx) => {
throw new Error("unimplemented")
},
AddApp: async (ctx, req) => {
const err = Types.AddAppRequestValidate(req, {
name_CustomCheck: name => name !== ''
})
if (err != null) throw new Error(err.message)
return mainHandler.applicationManager.AddApp(req)
},
AddAppUser: async (ctx, req) => {
const err = Types.AddAppUserRequestValidate(req, {
identifier_CustomCheck: id => id !== ''
})
if (err != null) throw new Error(err.message)
return mainHandler.applicationManager.AddAppUser(ctx.app_id, req)
},
AddAppInvoice: async (ctx, req) => {
const err = Types.AddAppInvoiceRequestValidate(req, {
payer_identifier_CustomCheck: id => id !== '',
})
if (err != null) throw new Error(err.message)
console.log(req)
return mainHandler.applicationManager.AddAppInvoice(ctx.app_id, req)
},
AddAppUserInvoice: async (ctx, req) => {
const err = Types.AddAppUserInvoiceRequestValidate(req, {
payer_identifier_CustomCheck: id => id !== '',
receiver_identifier_CustomCheck: id => id !== '',
})
if (err != null) throw new Error(err.message)
return mainHandler.applicationManager.AddAppUserInvoice(ctx.app_id, req)
},
GetAppUser: async (ctx, req) => {
const err = Types.GetAppUserRequestValidate(req, {
user_identifier_CustomCheck: id => id !== '',
})
if (err != null) throw new Error(err.message)
return mainHandler.applicationManager.GetAppUser(ctx.app_id, req)
},
PayAppUserInvoice: async (ctx, req) => {
const err = Types.PayAppUserInvoiceRequestValidate(req, {
user_identifier_CustomCheck: id => id !== '',
})
if (err != null) throw new Error(err.message)
return mainHandler.applicationManager.PayAppUserInvoice(ctx.app_id, req)
},
SendAppUserToAppUserPayment: async (ctx, req) => {
const err = Types.SendAppUserToAppUserPaymentRequestValidate(req, {
to_user_identifier_CustomCheck: id => id !== '',
from_user_identifier_CustomCheck: id => id !== '',
amount_CustomCheck: amount => amount > 0
})
if (err != null) throw new Error(err.message)
mainHandler.applicationManager.SendAppUserToAppUserPayment(ctx.app_id, req)
},
SendAppUserToAppPayment: async (ctx, req) => {
const err = Types.SendAppUserToAppPaymentRequestValidate(req, {
from_user_identifier_CustomCheck: id => id !== '',
amount_CustomCheck: amount => amount > 0
})
if (err != null) throw new Error(err.message)
mainHandler.applicationManager.SendAppUserToAppPayment(ctx.app_id, req)
}
}
}

View file

@ -0,0 +1,78 @@
import crypto from 'crypto';
import { DataSource, EntityManager } from "typeorm"
import { Application } from "./entity/Application.js"
import UserStorage from './userStorage.js';
import { ApplicationUser } from './entity/ApplicationUser.js';
export default class {
DB: DataSource | EntityManager
userStorage: UserStorage
constructor(DB: DataSource | EntityManager, userStorage: UserStorage) {
this.DB = DB
this.userStorage = userStorage
}
async AddApplication(name: string, entityManager = this.DB): Promise<Application> {
const owner = await this.userStorage.AddUser(0, entityManager)
const repo = entityManager.getRepository(Application)
const newApplication = repo.create({
app_id: crypto.randomBytes(32).toString('hex'),
name,
owner
})
return repo.save(newApplication)
}
async GetApplication(appId: string, entityManager = this.DB): Promise<Application> {
const found = await entityManager.getRepository(Application).findOne({
where: {
app_id: appId
}
})
if (!found) {
throw new Error(`application ${appId} not found`)
}
return found
}
async AddApplicationUser(appId: string, userIdentifier: string, balance: number) {
return this.DB.transaction(async tx => {
const user = await this.userStorage.AddUser(balance, tx)
const repo = tx.getRepository(ApplicationUser)
const appUser = repo.create({
user: user,
application: await this.GetApplication(appId),
identifier: userIdentifier,
})
return repo.save(appUser)
})
}
GetApplicationUserIfExists(appId: string, userIdentifier: string, entityManager = this.DB): Promise<ApplicationUser | null> {
return entityManager.getRepository(ApplicationUser).findOne({ where: { identifier: userIdentifier, application: { app_id: appId } } })
}
async GetOrCreateApplicationUser(appId: string, userIdentifier: string, balance: number, entityManager = this.DB): Promise<ApplicationUser> {
const found = await this.GetApplicationUserIfExists(appId, userIdentifier, entityManager)
if (found) {
return found
}
return this.AddApplicationUser(appId, userIdentifier, balance)
}
async GetApplicationUser(appId: string, userIdentifier: string, entityManager = this.DB): Promise<ApplicationUser> {
const found = await this.GetApplicationUserIfExists(appId, userIdentifier, entityManager)
if (!found) {
throw new Error(`application user ${userIdentifier} not found`)
}
if (found.application.app_id !== appId) {
console.log(found, appId)
throw new Error("requested user does not belong to requestor application")
}
return found
}
async IsApplicationUser(userId: string, entityManager = this.DB): Promise<ApplicationUser | null> {
return await entityManager.getRepository(ApplicationUser).findOne({ where: { user: { user_id: userId } } })
}
}

View file

@ -11,6 +11,9 @@ import { UserNostrAuth } from "./entity/UserNostrAuth.js"
import { UserBasicAuth } from "./entity/UserBasicAuth.js"
import { UserEphemeralKey } from "./entity/UserEphemeralKey.js"
import { Product } from "./entity/Product.js"
import { UserToUserPayment } from "./entity/UserToUserPayment.js"
import { Application } from "./entity/Application.js"
import { ApplicationUser } from "./entity/ApplicationUser.js"
export type DbSettings = {
databaseFile: string
}
@ -22,7 +25,7 @@ export default async (settings: DbSettings) => {
type: "sqlite",
database: settings.databaseFile,
//logging: true,
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, UserNostrAuth, UserBasicAuth, UserEphemeralKey, Product],
synchronize: true
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, UserNostrAuth, UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment],
synchronize: true,
}).initialize()
}
}

View file

@ -1,4 +1,4 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn } from "typeorm"
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { UserReceivingAddress } from "./UserReceivingAddress.js"
@ -27,4 +27,10 @@ export class AddressReceivingTransaction {
@Column()
paid_at_unix: number
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -0,0 +1,26 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn, JoinColumn, ManyToOne } from "typeorm"
import { User } from "./User.js"
@Entity()
export class Application {
@PrimaryGeneratedColumn()
serial_id: number
@Column()
@Index({ unique: true })
app_id: string
@Column({ unique: true })
name: string
@ManyToOne(type => User, { eager: true })
@JoinColumn()
owner: User
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -0,0 +1,28 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, JoinColumn, OneToOne, OneToMany, ManyToOne, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { User } from "./User.js"
import { Application } from "./Application.js"
@Entity()
export class ApplicationUser {
@PrimaryGeneratedColumn()
serial_id: number
@OneToOne(type => User, { eager: true })
@JoinColumn()
user: User
@ManyToOne(type => Application, { eager: true })
@JoinColumn()
application: Application
@Column()
@Index({ unique: true })
identifier: string
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -1,4 +1,4 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm"
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { User } from "./User.js"
@Entity()
@ -15,4 +15,10 @@ export class Product {
@Column()
price_sats: number
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -1,8 +1,7 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check } from "typeorm"
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
serial_id: number
@ -15,4 +14,10 @@ export class User {
@Column({ default: false })
locked: boolean
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -1,4 +1,4 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn } from "typeorm"
import { Entity, PrimaryGeneratedColumn, Column, Index, JoinColumn, OneToOne, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { User } from "./User.js"
@Entity()
@ -7,7 +7,7 @@ export class UserBasicAuth {
@PrimaryGeneratedColumn()
serial_id: number
@ManyToOne(type => User, { eager: true })
@OneToOne(type => User, { eager: true })
@JoinColumn()
user: User
@ -17,4 +17,10 @@ export class UserBasicAuth {
@Column()
secret_sha256: string
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -1,4 +1,4 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn } from "typeorm"
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { User } from "./User.js"
export type EphemeralKeyType = 'balanceCheck' | 'payInfo' | 'pay' | 'withdraw'
@Entity()
@ -17,4 +17,10 @@ export class UserEphemeralKey {
@Column()
type: EphemeralKeyType
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -1,4 +1,4 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn } from "typeorm"
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { User } from "./User.js"
@Entity()
@ -26,4 +26,10 @@ export class UserInvoicePayment {
@Column()
paid_at_unix: number
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -1,4 +1,4 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn } from "typeorm"
import { Entity, PrimaryGeneratedColumn, Column, Index, JoinColumn, OneToOne, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { User } from "./User.js"
@Entity()
@ -7,11 +7,17 @@ export class UserNostrAuth {
@PrimaryGeneratedColumn()
serial_id: number
@ManyToOne(type => User, { eager: true })
@OneToOne(type => User, { eager: true })
@JoinColumn()
user: User
@Column()
@Index({ unique: true })
nostr_pub: string
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -1,4 +1,4 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn } from "typeorm"
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { User } from "./User.js"
@Entity()
@ -17,4 +17,10 @@ export class UserReceivingAddress {
@Column()
callbackUrl: string
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -1,4 +1,4 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn } from "typeorm"
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { Product } from "./Product.js"
import { User } from "./User.js"
@ -16,10 +16,13 @@ export class UserReceivingInvoice {
@Index({ unique: true })
invoice: string
@Column()
expires_at_unix: number
@Column({ default: 0 })
paid_at_unix: number
@Column()
@Column({ default: "" })
callbackUrl: string
@Column({ default: 0 })
@ -30,4 +33,13 @@ export class UserReceivingInvoice {
@ManyToOne(type => Product, { eager: true })
product: Product | null
@ManyToOne(type => User, { eager: true })
payer: User | null
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -0,0 +1,32 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { User } from "./User.js"
@Entity()
export class UserToUserPayment {
@PrimaryGeneratedColumn()
serial_id: number
@ManyToOne(type => User, { eager: true })
@JoinColumn()
from_user: User
@ManyToOne(type => User, { eager: true })
@JoinColumn()
to_user: User
@Column()
paid_amount: number
@Column()
service_fees: number
@Column()
paid_at_unix: number
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -1,4 +1,4 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn } from "typeorm"
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { User } from "./User.js"
@Entity()
@ -32,4 +32,10 @@ export class UserTransactionPayment {
@Column()
paid_at_unix: number
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -11,7 +11,10 @@ import { UserNostrAuth } from "./entity/UserNostrAuth.js";
import { UserBasicAuth } from "./entity/UserBasicAuth.js";
import { EphemeralKeyType, UserEphemeralKey } from "./entity/UserEphemeralKey.js";
import ProductStorage from './productStorage.js'
import ApplicationStorage from './applicationStorage.js'
import { Product } from "./entity/Product.js";
import UserStorage from "./userStorage.js";
import PaymentStorage from "./paymentStorage.js";
export type StorageSettings = {
dbSettings: DbSettings
}
@ -23,255 +26,20 @@ export default class {
DB: DataSource | EntityManager
settings: StorageSettings
productStorage: ProductStorage
applicationStorage: ApplicationStorage
userStorage: UserStorage
paymentStorage: PaymentStorage
constructor(settings: StorageSettings) {
this.settings = settings
}
async Connect() {
this.DB = await NewDB(this.settings.dbSettings)
this.userStorage = new UserStorage(this.DB)
this.productStorage = new ProductStorage(this.DB)
this.applicationStorage = new ApplicationStorage(this.DB, this.userStorage)
this.paymentStorage = new PaymentStorage(this.DB, this.userStorage)
}
StartTransaction(exec: (entityManager: EntityManager) => Promise<void>) {
return this.DB.transaction(exec)
}
async AddUser(entityManager = this.DB): Promise<User> {
const newUser = entityManager.getRepository(User).create({
user_id: crypto.randomBytes(32).toString('hex')
})
return entityManager.getRepository(User).save(newUser)
}
async AddBasicUser(name: string, secret: string): Promise<UserBasicAuth> {
return this.DB.transaction(async tx => {
const user = await this.AddUser(tx)
const newUserAuth = tx.getRepository(UserBasicAuth).create({
user: user,
name: name,
secret_sha256: crypto.createHash('sha256').update(secret).digest('base64')
})
return tx.getRepository(UserBasicAuth).save(newUserAuth)
})
}
FindUser(userId: string, entityManager = this.DB) {
return entityManager.getRepository(User).findOne({
where: {
user_id: userId
}
})
}
async GetUser(userId: string, entityManager = this.DB): Promise<User> {
const user = await this.FindUser(userId, entityManager)
if (!user) {
throw new Error(`user ${userId} not found`) // TODO: fix logs doxing
}
return user
}
async FindNostrUser(nostrPub: string, entityManager = this.DB): Promise<UserNostrAuth | null> {
return entityManager.getRepository(UserNostrAuth).findOne({
where: { nostr_pub: nostrPub }
})
}
async AddNostrUser(nostrPub: string): Promise<UserNostrAuth> {
return this.DB.transaction(async tx => {
const user = await this.AddUser(tx)
const newAuth = tx.getRepository(UserNostrAuth).create({
user: user,
nostr_pub: nostrPub
})
return tx.getRepository(UserNostrAuth).save(newAuth)
})
}
async AddAddressReceivingTransaction(address: UserReceivingAddress, txHash: string, outputIndex: number, amount: number, serviceFee: number, entityManager = this.DB) {
const newAddressTransaction = entityManager.getRepository(AddressReceivingTransaction).create({
user_address: address,
tx_hash: txHash,
output_index: outputIndex,
paid_amount: amount,
service_fee: serviceFee,
paid_at_unix: Math.floor(Date.now() / 1000)
})
return entityManager.getRepository(AddressReceivingTransaction).save(newAddressTransaction)
}
GetUserReceivingTransactions(userId: string, fromIndex: number, entityManager = this.DB): Promise<AddressReceivingTransaction[]> {
return entityManager.getRepository(AddressReceivingTransaction).find({
where: {
user_address: { user: { user_id: userId } },
serial_id: MoreThanOrEqual(fromIndex),
paid_at_unix: MoreThan(0),
},
order: {
paid_at_unix: 'DESC'
}
})
}
async AddUserAddress(userId: string, address: string, callbackUrl = "", entityManager = this.DB): Promise<UserReceivingAddress> {
const newUserAddress = entityManager.getRepository(UserReceivingAddress).create({
address,
callbackUrl,
user: await this.GetUser(userId, entityManager)
})
return entityManager.getRepository(UserReceivingAddress).save(newUserAddress)
}
async FlagInvoiceAsPaid(invoice: UserReceivingInvoice, amount: number, serviceFee: number, entityManager = this.DB) {
return entityManager.getRepository(UserReceivingInvoice).update(invoice.serial_id, { paid_at_unix: Math.floor(Date.now() / 1000), paid_amount: amount, service_fee: serviceFee })
}
GetUserInvoicesFlaggedAsPaid(userId: string, fromIndex: number, entityManager = this.DB): Promise<UserReceivingInvoice[]> {
return entityManager.getRepository(UserReceivingInvoice).find({
where: {
user: {
user_id: userId
},
serial_id: MoreThanOrEqual(fromIndex),
paid_at_unix: MoreThan(0),
},
order: {
paid_at_unix: 'DESC'
}
})
}
async AddUserInvoice(user: User, invoice: string, product?: Product, callbackUrl = "", entityManager = this.DB): Promise<UserReceivingInvoice> {
const newUserInvoice = entityManager.getRepository(UserReceivingInvoice).create({
invoice: invoice,
callbackUrl,
user: user,
product: product
})
return entityManager.getRepository(UserReceivingInvoice).save(newUserInvoice)
}
async GetAddressOwner(address: string, entityManager = this.DB): Promise<UserReceivingAddress | null> {
return entityManager.getRepository(UserReceivingAddress).findOne({
where: {
address
}
})
}
async GetInvoiceOwner(paymentRequest: string, entityManager = this.DB): Promise<UserReceivingInvoice | null> {
return entityManager.getRepository(UserReceivingInvoice).findOne({
where: {
invoice: paymentRequest
}
})
}
async AddUserInvoicePayment(userId: string, invoice: string, amount: number, routingFees: number, serviceFees: number, entityManager = this.DB): Promise<UserInvoicePayment> {
const newPayment = entityManager.getRepository(UserInvoicePayment).create({
user: await this.GetUser(userId),
paid_amount: amount,
invoice,
routing_fees: routingFees,
service_fees: serviceFees,
paid_at_unix: Math.floor(Date.now() / 1000)
})
return entityManager.getRepository(UserInvoicePayment).save(newPayment)
}
GetUserInvoicePayments(userId: string, fromIndex: number, entityManager = this.DB): Promise<UserInvoicePayment[]> {
return entityManager.getRepository(UserInvoicePayment).find({
where: {
user: {
user_id: userId
},
serial_id: MoreThanOrEqual(fromIndex),
paid_at_unix: MoreThan(0),
},
order: {
paid_at_unix: 'DESC'
}
})
}
async AddUserTransactionPayment(userId: string, address: string, txHash: string, txOutput: number, amount: number, chainFees: number, serviceFees: number, entityManager = this.DB): Promise<UserTransactionPayment> {
const newTx = entityManager.getRepository(UserTransactionPayment).create({
user: await this.GetUser(userId),
address,
paid_amount: amount,
chain_fees: chainFees,
output_index: txOutput,
tx_hash: txHash,
service_fees: serviceFees,
paid_at_unix: Math.floor(Date.now() / 1000)
})
return entityManager.getRepository(UserTransactionPayment).save(newTx)
}
GetUserTransactionPayments(userId: string, fromIndex: number, entityManager = this.DB): Promise<UserTransactionPayment[]> {
return entityManager.getRepository(UserTransactionPayment).find({
where: {
user: {
user_id: userId
},
serial_id: MoreThanOrEqual(fromIndex),
paid_at_unix: MoreThan(0),
},
order: {
paid_at_unix: 'DESC'
}
})
}
async LockUser(userId: string, entityManager = this.DB) {
const res = await entityManager.getRepository(User).update({
user_id: userId
}, { locked: true })
if (!res.affected) {
throw new Error("unaffected user lock for " + userId) // TODO: fix logs doxing
}
}
async UnlockUser(userId: string, entityManager = this.DB) {
const res = await entityManager.getRepository(User).update({
user_id: userId
}, { locked: false })
if (!res.affected) {
throw new Error("unaffected user unlock for " + userId) // TODO: fix logs doxing
}
}
async IncrementUserBalance(userId: string, increment: number, entityManager = this.DB) {
const res = await entityManager.getRepository(User).increment({
user_id: userId,
}, "balance_sats", increment)
if (!res.affected) {
throw new Error("unaffected balance increment for " + userId) // TODO: fix logs doxing
}
}
async DecrementUserBalance(userId: string, decrement: number, entityManager = this.DB) {
const res = await entityManager.getRepository(User).decrement({
user_id: userId,
}, "balance_sats", decrement)
if (!res.affected) {
throw new Error("unaffected balance decrement for " + userId) // TODO: fix logs doxing
}
}
async AddUserEphemeralKey(userId: string, keyType: EphemeralKeyType, entityManager = this.DB): Promise<UserEphemeralKey> {
const newKey = entityManager.getRepository(UserEphemeralKey).create({
user: await this.GetUser(userId, entityManager),
key: crypto.randomBytes(31).toString('hex'),
type: keyType
})
return entityManager.getRepository(UserEphemeralKey).save(newKey)
}
async UseUserEphemeralKey(key: string, keyType: EphemeralKeyType, entityManager = this.DB): Promise<UserEphemeralKey> {
const found = await entityManager.getRepository(UserEphemeralKey).findOne({
where: {
key: key,
type: keyType
}
})
if (!found) {
throw new Error("the provided ephemeral key is invalid")
}
await entityManager.getRepository(UserEphemeralKey).delete(found.serial_id)
return found
}
}

View file

@ -0,0 +1,228 @@
import crypto from 'crypto';
import { DataSource, EntityManager, MoreThan, MoreThanOrEqual } from "typeorm"
import { User } from './entity/User.js';
import { UserBasicAuth } from './entity/UserBasicAuth.js';
import { UserNostrAuth } from './entity/UserNostrAuth.js';
import { UserTransactionPayment } from './entity/UserTransactionPayment.js';
import { EphemeralKeyType, UserEphemeralKey } from './entity/UserEphemeralKey.js';
import { UserReceivingInvoice } from './entity/UserReceivingInvoice.js';
import { UserReceivingAddress } from './entity/UserReceivingAddress.js';
import { Product } from './entity/Product.js';
import UserStorage from './userStorage.js';
import { AddressReceivingTransaction } from './entity/AddressReceivingTransaction.js';
import { UserInvoicePayment } from './entity/UserInvoicePayment.js';
import { UserToUserPayment } from './entity/UserToUserPayment.js';
export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User }
export const defaultInvoiceExpiry = 60 * 60
export default class {
DB: DataSource | EntityManager
userStorage: UserStorage
constructor(DB: DataSource | EntityManager, userStorage: UserStorage) {
this.DB = DB
this.userStorage = userStorage
}
async AddAddressReceivingTransaction(address: UserReceivingAddress, txHash: string, outputIndex: number, amount: number, serviceFee: number, entityManager = this.DB) {
const newAddressTransaction = entityManager.getRepository(AddressReceivingTransaction).create({
user_address: address,
tx_hash: txHash,
output_index: outputIndex,
paid_amount: amount,
service_fee: serviceFee,
paid_at_unix: Math.floor(Date.now() / 1000)
})
return entityManager.getRepository(AddressReceivingTransaction).save(newAddressTransaction)
}
GetUserReceivingTransactions(userId: string, fromIndex: number, entityManager = this.DB): Promise<AddressReceivingTransaction[]> {
return entityManager.getRepository(AddressReceivingTransaction).find({
where: {
user_address: { user: { user_id: userId } },
serial_id: MoreThanOrEqual(fromIndex),
paid_at_unix: MoreThan(0),
},
order: {
paid_at_unix: 'DESC'
}
})
}
async AddUserAddress(userId: string, address: string, callbackUrl = "", entityManager = this.DB): Promise<UserReceivingAddress> {
const newUserAddress = entityManager.getRepository(UserReceivingAddress).create({
address,
callbackUrl,
user: await this.userStorage.GetUser(userId, entityManager)
})
return entityManager.getRepository(UserReceivingAddress).save(newUserAddress)
}
async FlagInvoiceAsPaid(invoice: UserReceivingInvoice, amount: number, serviceFee: number, entityManager = this.DB) {
return entityManager.getRepository(UserReceivingInvoice).update(invoice.serial_id, { paid_at_unix: Math.floor(Date.now() / 1000), paid_amount: amount, service_fee: serviceFee })
}
GetUserInvoicesFlaggedAsPaid(userId: string, fromIndex: number, entityManager = this.DB): Promise<UserReceivingInvoice[]> {
return entityManager.getRepository(UserReceivingInvoice).find({
where: {
user: {
user_id: userId
},
serial_id: MoreThanOrEqual(fromIndex),
paid_at_unix: MoreThan(0),
},
order: {
paid_at_unix: 'DESC'
}
})
}
async AddUserInvoice(user: User, invoice: string, options: InboundOptionals = { expiry: defaultInvoiceExpiry }, entityManager = this.DB): Promise<UserReceivingInvoice> {
const newUserInvoice = entityManager.getRepository(UserReceivingInvoice).create({
invoice: invoice,
callbackUrl: options.callbackUrl,
user: user,
product: options.product,
expires_at_unix: Math.floor(Date.now() / 1000) + options.expiry,
payer: options.expectedPayer
})
return entityManager.getRepository(UserReceivingInvoice).save(newUserInvoice)
}
async GetAddressOwner(address: string, entityManager = this.DB): Promise<UserReceivingAddress | null> {
return entityManager.getRepository(UserReceivingAddress).findOne({
where: {
address
}
})
}
async GetInvoiceOwner(paymentRequest: string, entityManager = this.DB): Promise<UserReceivingInvoice | null> {
return entityManager.getRepository(UserReceivingInvoice).findOne({
where: {
invoice: paymentRequest
}
})
}
async AddUserInvoicePayment(userId: string, invoice: string, amount: number, routingFees: number, serviceFees: number, entityManager = this.DB): Promise<UserInvoicePayment> {
const newPayment = entityManager.getRepository(UserInvoicePayment).create({
user: await this.userStorage.GetUser(userId),
paid_amount: amount,
invoice,
routing_fees: routingFees,
service_fees: serviceFees,
paid_at_unix: Math.floor(Date.now() / 1000)
})
return entityManager.getRepository(UserInvoicePayment).save(newPayment)
}
GetUserInvoicePayments(userId: string, fromIndex: number, entityManager = this.DB): Promise<UserInvoicePayment[]> {
return entityManager.getRepository(UserInvoicePayment).find({
where: {
user: {
user_id: userId
},
serial_id: MoreThanOrEqual(fromIndex),
paid_at_unix: MoreThan(0),
},
order: {
paid_at_unix: 'DESC'
}
})
}
async AddUserTransactionPayment(userId: string, address: string, txHash: string, txOutput: number, amount: number, chainFees: number, serviceFees: number, entityManager = this.DB): Promise<UserTransactionPayment> {
const newTx = entityManager.getRepository(UserTransactionPayment).create({
user: await this.userStorage.GetUser(userId),
address,
paid_amount: amount,
chain_fees: chainFees,
output_index: txOutput,
tx_hash: txHash,
service_fees: serviceFees,
paid_at_unix: Math.floor(Date.now() / 1000)
})
return entityManager.getRepository(UserTransactionPayment).save(newTx)
}
GetUserTransactionPayments(userId: string, fromIndex: number, entityManager = this.DB): Promise<UserTransactionPayment[]> {
return entityManager.getRepository(UserTransactionPayment).find({
where: {
user: {
user_id: userId
},
serial_id: MoreThanOrEqual(fromIndex),
paid_at_unix: MoreThan(0),
},
order: {
paid_at_unix: 'DESC'
}
})
}
async AddUserEphemeralKey(userId: string, keyType: EphemeralKeyType, entityManager = this.DB): Promise<UserEphemeralKey> {
const newKey = entityManager.getRepository(UserEphemeralKey).create({
user: await this.userStorage.GetUser(userId, entityManager),
key: crypto.randomBytes(31).toString('hex'),
type: keyType
})
return entityManager.getRepository(UserEphemeralKey).save(newKey)
}
async UseUserEphemeralKey(key: string, keyType: EphemeralKeyType, entityManager = this.DB): Promise<UserEphemeralKey> {
const found = await entityManager.getRepository(UserEphemeralKey).findOne({
where: {
key: key,
type: keyType
}
})
if (!found) {
throw new Error("the provided ephemeral key is invalid")
}
await entityManager.getRepository(UserEphemeralKey).delete(found.serial_id)
return found
}
async AddUserToUserPayment(fromUserId: string, toUserId: string, amount: number, fee: number, entityManager = this.DB) {
const newKey = entityManager.getRepository(UserToUserPayment).create({
from_user: await this.userStorage.GetUser(fromUserId, entityManager),
to_user: await this.userStorage.GetUser(toUserId, entityManager),
paid_at_unix: Math.floor(Date.now() / 1000),
paid_amount: amount,
service_fees: fee
})
return entityManager.getRepository(UserToUserPayment).save(newKey)
}
GetUserToUserReceivedPayments(userId: string, fromIndex: number, entityManager = this.DB) {
return entityManager.getRepository(UserToUserPayment).find({
where: {
to_user: {
user_id: userId
},
serial_id: MoreThanOrEqual(fromIndex),
paid_at_unix: MoreThan(0),
},
order: {
paid_at_unix: 'DESC'
}
})
}
GetUserToUserSentPayments(userId: string, fromIndex: number, entityManager = this.DB) {
return entityManager.getRepository(UserToUserPayment).find({
where: {
from_user: {
user_id: userId
},
serial_id: MoreThanOrEqual(fromIndex),
paid_at_unix: MoreThan(0),
},
order: {
paid_at_unix: 'DESC'
}
})
}
}

View file

@ -0,0 +1,105 @@
import crypto from 'crypto';
import { DataSource, EntityManager } from "typeorm"
import { User } from './entity/User.js';
import { UserBasicAuth } from './entity/UserBasicAuth.js';
import { UserNostrAuth } from './entity/UserNostrAuth.js';
export default class {
DB: DataSource | EntityManager
constructor(DB: DataSource | EntityManager) {
this.DB = DB
}
async AddUser(balance: number, entityManager = this.DB): Promise<User> {
if (balance && process.env.ALLOW_BALANCE_MIGRATION !== 'true') {
throw new Error("balance migration is not allowed")
}
console.log("Adding user with balance", balance)
const newUser = entityManager.getRepository(User).create({
user_id: crypto.randomBytes(32).toString('hex'),
balance_sats: balance
})
return entityManager.getRepository(User).save(newUser)
}
async AddBasicUser(name: string, secret: string): Promise<UserBasicAuth> {
return this.DB.transaction(async tx => {
const user = await this.AddUser(0, tx)
const newUserAuth = tx.getRepository(UserBasicAuth).create({
user: user,
name: name,
secret_sha256: crypto.createHash('sha256').update(secret).digest('base64')
})
return tx.getRepository(UserBasicAuth).save(newUserAuth)
})
}
FindUser(userId: string, entityManager = this.DB) {
return entityManager.getRepository(User).findOne({
where: {
user_id: userId
}
})
}
async GetUser(userId: string, entityManager = this.DB): Promise<User> {
const user = await this.FindUser(userId, entityManager)
if (!user) {
throw new Error(`user ${userId} not found`) // TODO: fix logs doxing
}
return user
}
async FindNostrUser(nostrPub: string, entityManager = this.DB): Promise<UserNostrAuth | null> {
return entityManager.getRepository(UserNostrAuth).findOne({
where: { nostr_pub: nostrPub }
})
}
async AddNostrUser(nostrPub: string): Promise<UserNostrAuth> {
return this.DB.transaction(async tx => {
const user = await this.AddUser(0, tx)
const newAuth = tx.getRepository(UserNostrAuth).create({
user: user,
nostr_pub: nostrPub
})
return tx.getRepository(UserNostrAuth).save(newAuth)
})
}
async LockUser(userId: string, entityManager = this.DB) {
const res = await entityManager.getRepository(User).update({
user_id: userId
}, { locked: true })
if (!res.affected) {
throw new Error("unaffected user lock for " + userId) // TODO: fix logs doxing
}
}
async UnlockUser(userId: string, entityManager = this.DB) {
const res = await entityManager.getRepository(User).update({
user_id: userId
}, { locked: false })
if (!res.affected) {
throw new Error("unaffected user unlock for " + userId) // TODO: fix logs doxing
}
}
async IncrementUserBalance(userId: string, increment: number, entityManager = this.DB) {
const res = await entityManager.getRepository(User).increment({
user_id: userId,
}, "balance_sats", increment)
if (!res.affected) {
throw new Error("unaffected balance increment for " + userId) // TODO: fix logs doxing
}
}
async DecrementUserBalance(userId: string, decrement: number, entityManager = this.DB) {
const user = await this.GetUser(userId, entityManager)
if (!user || user.balance_sats < decrement) {
throw new Error("not enough balance to decrement")
}
const res = await entityManager.getRepository(User).decrement({
user_id: userId,
}, "balance_sats", decrement)
if (!res.affected) {
throw new Error("unaffected balance decrement for " + userId) // TODO: fix logs doxing
}
}
}