Merge branch 'umbrel-works' of https://github.com/shocknet/Lightning.Pub into umbrel-works
This commit is contained in:
commit
4c8fdee612
121 changed files with 63411 additions and 63411 deletions
|
|
@ -1,4 +1,4 @@
|
|||
.git
|
||||
.github
|
||||
build
|
||||
node_modules
|
||||
.git
|
||||
.github
|
||||
build
|
||||
node_modules
|
||||
|
|
|
|||
6
.github/FUNDING.yml
vendored
6
.github/FUNDING.yml
vendored
|
|
@ -1,3 +1,3 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: [shocknet,]
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [shocknet,]
|
||||
|
|
|
|||
126
.github/workflows/push.yml
vendored
126
.github/workflows/push.yml
vendored
|
|
@ -1,63 +1,63 @@
|
|||
name: Create and publish a Docker image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created, published, prereleased]
|
||||
workflow_dispatch: # This allows manual triggering of the workflow
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ghcr.io/${{ github.repository_owner }}/lightning-pub:latest
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: Capture image digest
|
||||
id: capture-digest
|
||||
run: |
|
||||
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/${{ github.repository_owner }}/lightning-pub:latest | cut -d'@' -f2)
|
||||
echo "Raw Digest is $DIGEST"
|
||||
echo "::set-output name=digest::$DIGEST"
|
||||
|
||||
- name: Debug Print Digest
|
||||
run: echo "Digest is ${{ steps.capture-digest.outputs.digest }}"
|
||||
|
||||
- name: Attest build provenance
|
||||
uses: actions/attest-build-provenance@v1
|
||||
with:
|
||||
subject-digest: ${{ steps.capture-digest.outputs.digest }}
|
||||
subject-name: ghcr.io/${{ github.repository_owner }}/lightning-pub:latest
|
||||
github-token: ${{ secrets.PAT_TOKEN }}
|
||||
name: Create and publish a Docker image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created, published, prereleased]
|
||||
workflow_dispatch: # This allows manual triggering of the workflow
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ghcr.io/${{ github.repository_owner }}/lightning-pub:latest
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: Capture image digest
|
||||
id: capture-digest
|
||||
run: |
|
||||
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/${{ github.repository_owner }}/lightning-pub:latest | cut -d'@' -f2)
|
||||
echo "Raw Digest is $DIGEST"
|
||||
echo "::set-output name=digest::$DIGEST"
|
||||
|
||||
- name: Debug Print Digest
|
||||
run: echo "Digest is ${{ steps.capture-digest.outputs.digest }}"
|
||||
|
||||
- name: Attest build provenance
|
||||
uses: actions/attest-build-provenance@v1
|
||||
with:
|
||||
subject-digest: ${{ steps.capture-digest.outputs.digest }}
|
||||
subject-name: ghcr.io/${{ github.repository_owner }}/lightning-pub:latest
|
||||
github-token: ${{ secrets.PAT_TOKEN }}
|
||||
|
|
|
|||
110
.github/workflows/test.yaml
vendored
110
.github/workflows/test.yaml
vendored
|
|
@ -1,55 +1,55 @@
|
|||
name: Docker Compose Actions Workflow
|
||||
on: push
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: unzip the file
|
||||
run: unzip src/tests/regtestNetwork.zip
|
||||
- name: list files
|
||||
run: ls -la
|
||||
- name: Build the stack
|
||||
run: docker-compose --project-directory ./ -f src/tests/docker-compose.yml up -d
|
||||
- name: Copy alice cert file
|
||||
run: docker cp polar-n2-alice:/home/lnd/.lnd/tls.cert alice-tls.cert
|
||||
- name: Copy bob cert file
|
||||
run: docker cp polar-n2-bob:/home/lnd/.lnd/tls.cert bob-tls.cert
|
||||
- name: Copy carol cert file
|
||||
run: docker cp polar-n2-carol:/home/lnd/.lnd/tls.cert carol-tls.cert
|
||||
- name: Copy dave cert file
|
||||
run: docker cp polar-n2-dave:/home/lnd/.lnd/tls.cert dave-tls.cert
|
||||
- name: Copy alice macaroon file
|
||||
run: docker cp polar-n2-alice:/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon alice-admin.macaroon
|
||||
- name: Copy bob macaroon file
|
||||
run: docker cp polar-n2-bob:/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon bob-admin.macaroon
|
||||
- name: Copy carol macaroon file
|
||||
run: docker cp polar-n2-carol:/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon carol-admin.macaroon
|
||||
- name: Copy dave macaroon file
|
||||
run: docker cp polar-n2-dave:/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon dave-admin.macaroon
|
||||
- name: copy env file
|
||||
run: cp src/tests/.env.test .env
|
||||
- name: List files
|
||||
run: ls -la
|
||||
- name: Cache node modules
|
||||
id: cache-npm
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
|
||||
name: List the state of node modules
|
||||
continue-on-error: true
|
||||
run: npm list
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
name: Docker Compose Actions Workflow
|
||||
on: push
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: unzip the file
|
||||
run: unzip src/tests/regtestNetwork.zip
|
||||
- name: list files
|
||||
run: ls -la
|
||||
- name: Build the stack
|
||||
run: docker-compose --project-directory ./ -f src/tests/docker-compose.yml up -d
|
||||
- name: Copy alice cert file
|
||||
run: docker cp polar-n2-alice:/home/lnd/.lnd/tls.cert alice-tls.cert
|
||||
- name: Copy bob cert file
|
||||
run: docker cp polar-n2-bob:/home/lnd/.lnd/tls.cert bob-tls.cert
|
||||
- name: Copy carol cert file
|
||||
run: docker cp polar-n2-carol:/home/lnd/.lnd/tls.cert carol-tls.cert
|
||||
- name: Copy dave cert file
|
||||
run: docker cp polar-n2-dave:/home/lnd/.lnd/tls.cert dave-tls.cert
|
||||
- name: Copy alice macaroon file
|
||||
run: docker cp polar-n2-alice:/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon alice-admin.macaroon
|
||||
- name: Copy bob macaroon file
|
||||
run: docker cp polar-n2-bob:/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon bob-admin.macaroon
|
||||
- name: Copy carol macaroon file
|
||||
run: docker cp polar-n2-carol:/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon carol-admin.macaroon
|
||||
- name: Copy dave macaroon file
|
||||
run: docker cp polar-n2-dave:/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon dave-admin.macaroon
|
||||
- name: copy env file
|
||||
run: cp src/tests/.env.test .env
|
||||
- name: List files
|
||||
run: ls -la
|
||||
- name: Cache node modules
|
||||
id: cache-npm
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
|
||||
name: List the state of node modules
|
||||
continue-on-error: true
|
||||
run: npm list
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
|
|
|||
24
.gitignore
vendored
24
.gitignore
vendored
|
|
@ -1,13 +1,13 @@
|
|||
.idea/
|
||||
.vscode/
|
||||
node_modules/
|
||||
build/
|
||||
tmp/
|
||||
temp/
|
||||
.env
|
||||
build/
|
||||
db.sqlite
|
||||
metrics.sqlite
|
||||
.key/
|
||||
logs
|
||||
.idea/
|
||||
.vscode/
|
||||
node_modules/
|
||||
build/
|
||||
tmp/
|
||||
temp/
|
||||
.env
|
||||
build/
|
||||
db.sqlite
|
||||
metrics.sqlite
|
||||
.key/
|
||||
logs
|
||||
.jwt_secret
|
||||
22
Dockerfile
22
Dockerfile
|
|
@ -1,11 +1,11 @@
|
|||
FROM node:18
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json /app
|
||||
|
||||
RUN npm i
|
||||
|
||||
COPY . /app
|
||||
|
||||
CMD [ "npm", "start" ]
|
||||
FROM node:18
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json /app
|
||||
|
||||
RUN npm i
|
||||
|
||||
COPY . /app
|
||||
|
||||
CMD [ "npm", "start" ]
|
||||
|
|
|
|||
226
README.md
226
README.md
|
|
@ -1,113 +1,113 @@
|
|||

|
||||
|
||||

|
||||
[](http://makeapullrequest.com)
|
||||
[](https://t.me/ShockBTC)
|
||||

|
||||
|
||||
|
||||
### Don't just run a Lightning Node, run a Lightning Pub.
|
||||
|
||||
"Pub" is a [Nostr](https://nostr.info)-native account system designed to make running Lightning infrastructure for your friends/family/customers easier than previously thought possible.
|
||||
|
||||
It may come as a surprise that the biggest hurdle to more Uncle Jim nodes hasn't been with Bitcoin/Lightning node management itself, as we've seen liquidity easily automated even in unreliable environments like mobile nodes.
|
||||
|
||||
It's the legacy baggage of traditional Client-Server web infrastructure, things like IP4, Reverse Proxies, DNS, Firewalls and SSL certificates, all of which require a personal configuration that is a hurdle for most.
|
||||
|
||||
Tor as a workaround has proven too slow and unreliable, and a dead-end for clearnet-web usecases. Bxlt12, being a re-implementation of Tor, appears destined for the same fate.
|
||||
|
||||
Pub solves these challenges with a P2P-like design that is also web-friendly, by implementing a full RPC that is Nostr-native. Being Nostr-native eliminates the complexity of configuring your node like a server by using commodity Nostr relays. These relays, unlike LNURL proxies, are trustless by nature of Nostr's own encryption spec (NIP44).
|
||||
|
||||
Additionally, support for optional services are integrated into Pub for operators seeking backward compatibility with legacy LNURLs and Lightning Addresses.
|
||||
|
||||
By solving the networking and programability hurdles, Pub provides Lightning with a 3rd Layer that enables node-runners and Uncle Jims to more easily bring their personal network into Bitcoin's permissionless economy. In doing so, Pub runners can keep the Lightning Network decentralized, with custodial scaling that is free of fiat rails, large banks, and other forms of high-time-preference shitcoinery.
|
||||
|
||||
#### Features:
|
||||
|
||||
- Wrapper for [`LND`](https://github.com/lightningnetwork/lnd/releases) that can serve accounts over LNURL and NOSTR
|
||||
- A growing number of [methods](https://github.com/shocknet/Lightning.Pub/blob/master/proto/autogenerated/client.md)
|
||||
- Accounting SubLayers for Application Pools and Users
|
||||
- A fee regime allows applications owners to monetize users, or node operators to host distinctly monetized applications.
|
||||
|
||||

|
||||
|
||||
- Connecting via ShockWallet is as easy as pasting an nprofile
|
||||
- Or use a link to share your nprofile with friends and family
|
||||
|
||||
<img src="https://cdn.shockwallet.app/add_src_sm.png" height="20%" alt="Connect Wallet"> <img src="https://cdn.shockwallet.app/src_invite_sm.png" height="20%" alt="Invite Guests">
|
||||
|
||||
|
||||
#### Planned
|
||||
- [ ] A management dashboard is actively being integrated into [ShockWallet](https://github.com/shocknet/wallet2)
|
||||
- [ ] Nostr native "offers" (successor to LNURL-Pay, Lightning Address, Bxlt12)
|
||||
- [ ] Automated Channels
|
||||
- [ ] Bootstrap Peering (Passive "LSP")
|
||||
- [ ] Event Notifications
|
||||
- [ ] Swap integration
|
||||
- [ ] High-Availabilty / Clustering
|
||||
|
||||
Dashboard Wireframe:
|
||||
|
||||
<img src="https://shockwallet.b-cdn.net/pub_home_ss.png" alt="Pub Dashboard" width="240">
|
||||
|
||||
> [!IMPORTANT]
|
||||
> ShockWallet and Lightning.Pub are free software. If you would like to see continued development, please show your [**support**](https://github.com/sponsors/shocknet) 😊<br>
|
||||
|
||||
<img src="https://www.gnu.org/graphics/agplv3-with-text-162x68.png" alt="License">
|
||||
<br>
|
||||
|
||||
> [!WARNING]
|
||||
> While this software has been used in a high-profile production environment for over a year, it should still be considered bleeding edge. Special care has been taken to mitigate the risk of drainage attacks, which is a common risk to all Lightning API's. An integrated Watchdog service will terminate spends if it detects a discrepency between LND and the database, for this reason **IT IS NOT RECOMMENDED TO USE PUB ALONGSIDE OTHER ACCOUNT SYSTEMS**. While we give the utmost care and attention to security, **the internet is an adversarial environment and SECURITY/RELIABILITY ARE NOT GUARANTEED- USE AT YOUR OWN RISK**.
|
||||
|
||||
## Umbrel Installation
|
||||
|
||||
Coming Soon
|
||||
|
||||
## Desktop Installation
|
||||
|
||||
Coming Soon
|
||||
|
||||
## Docker
|
||||
|
||||
`docker pull ghcr.io/shocknet/lightning-pub:latest`
|
||||
|
||||
## Manual CLI Installation
|
||||
|
||||
#### Notes:
|
||||
* Use of a reverse proxy is only required if you wish to serve LNURLs
|
||||
* The service defaults to port `1776`
|
||||
* Requires [Node.js](https://nodejs.org) >=18.x
|
||||
* Commands for your specific OS may differ slightly, Ubuntu/Debian used for example
|
||||
|
||||
#### Steps:
|
||||
1) Run [LND](https://github.com/lightningnetwork/lnd/releases) if you aren't already
|
||||
|
||||
*Example mainnet startup*:
|
||||
|
||||
```
|
||||
./lnd --bitcoin.active --bitcoin.mainnet --bitcoin.node=neutrino --neutrino.addpeer=neutrino.shock.network --feeurl=https://nodes.lightning.computer/fees/v1/btc-fee-estimates.json
|
||||
```
|
||||
|
||||
|
||||
2) Download and Install Lightning.Pub
|
||||
|
||||
|
||||
* `git clone https://github.com/shocknet/Lightning.Pub`
|
||||
|
||||
* `cd Lightning.Pub && npm i`
|
||||
|
||||
|
||||
3) Configure values to env file as desired
|
||||
* `cp env.example .env && nano .env`
|
||||
|
||||
5) `npm start`
|
||||
|
||||
- A default "wallet" application pool will be automatically created, if you wish to create other app pools:
|
||||
|
||||
* `curl -XPOST -H 'Authorization: Bearer defined_in_ADMIN_TOKEN_env' -H "Content-type: application/json" -d '{"name":"ExampleApplicationPoolName"}' 'http://localhost:8080/api/admin/app/add'`
|
||||
|
||||
5) Connect with [wallet2](https://github.com/shocknet/wallet2) using the wallet nprofile that gets logged at startup.
|
||||
> [!NOTE]
|
||||
> Connecting with wallet will create an account on the node, it will not show or have access to the full LND balance
|
||||
|
||||
|
||||

|
||||
|
||||

|
||||
[](http://makeapullrequest.com)
|
||||
[](https://t.me/ShockBTC)
|
||||

|
||||
|
||||
|
||||
### Don't just run a Lightning Node, run a Lightning Pub.
|
||||
|
||||
"Pub" is a [Nostr](https://nostr.info)-native account system designed to make running Lightning infrastructure for your friends/family/customers easier than previously thought possible.
|
||||
|
||||
It may come as a surprise that the biggest hurdle to more Uncle Jim nodes hasn't been with Bitcoin/Lightning node management itself, as we've seen liquidity easily automated even in unreliable environments like mobile nodes.
|
||||
|
||||
It's the legacy baggage of traditional Client-Server web infrastructure, things like IP4, Reverse Proxies, DNS, Firewalls and SSL certificates, all of which require a personal configuration that is a hurdle for most.
|
||||
|
||||
Tor as a workaround has proven too slow and unreliable, and a dead-end for clearnet-web usecases. Bxlt12, being a re-implementation of Tor, appears destined for the same fate.
|
||||
|
||||
Pub solves these challenges with a P2P-like design that is also web-friendly, by implementing a full RPC that is Nostr-native. Being Nostr-native eliminates the complexity of configuring your node like a server by using commodity Nostr relays. These relays, unlike LNURL proxies, are trustless by nature of Nostr's own encryption spec (NIP44).
|
||||
|
||||
Additionally, support for optional services are integrated into Pub for operators seeking backward compatibility with legacy LNURLs and Lightning Addresses.
|
||||
|
||||
By solving the networking and programability hurdles, Pub provides Lightning with a 3rd Layer that enables node-runners and Uncle Jims to more easily bring their personal network into Bitcoin's permissionless economy. In doing so, Pub runners can keep the Lightning Network decentralized, with custodial scaling that is free of fiat rails, large banks, and other forms of high-time-preference shitcoinery.
|
||||
|
||||
#### Features:
|
||||
|
||||
- Wrapper for [`LND`](https://github.com/lightningnetwork/lnd/releases) that can serve accounts over LNURL and NOSTR
|
||||
- A growing number of [methods](https://github.com/shocknet/Lightning.Pub/blob/master/proto/autogenerated/client.md)
|
||||
- Accounting SubLayers for Application Pools and Users
|
||||
- A fee regime allows applications owners to monetize users, or node operators to host distinctly monetized applications.
|
||||
|
||||

|
||||
|
||||
- Connecting via ShockWallet is as easy as pasting an nprofile
|
||||
- Or use a link to share your nprofile with friends and family
|
||||
|
||||
<img src="https://cdn.shockwallet.app/add_src_sm.png" height="20%" alt="Connect Wallet"> <img src="https://cdn.shockwallet.app/src_invite_sm.png" height="20%" alt="Invite Guests">
|
||||
|
||||
|
||||
#### Planned
|
||||
- [ ] A management dashboard is actively being integrated into [ShockWallet](https://github.com/shocknet/wallet2)
|
||||
- [ ] Nostr native "offers" (successor to LNURL-Pay, Lightning Address, Bxlt12)
|
||||
- [ ] Automated Channels
|
||||
- [ ] Bootstrap Peering (Passive "LSP")
|
||||
- [ ] Event Notifications
|
||||
- [ ] Swap integration
|
||||
- [ ] High-Availabilty / Clustering
|
||||
|
||||
Dashboard Wireframe:
|
||||
|
||||
<img src="https://shockwallet.b-cdn.net/pub_home_ss.png" alt="Pub Dashboard" width="240">
|
||||
|
||||
> [!IMPORTANT]
|
||||
> ShockWallet and Lightning.Pub are free software. If you would like to see continued development, please show your [**support**](https://github.com/sponsors/shocknet) 😊<br>
|
||||
|
||||
<img src="https://www.gnu.org/graphics/agplv3-with-text-162x68.png" alt="License">
|
||||
<br>
|
||||
|
||||
> [!WARNING]
|
||||
> While this software has been used in a high-profile production environment for over a year, it should still be considered bleeding edge. Special care has been taken to mitigate the risk of drainage attacks, which is a common risk to all Lightning API's. An integrated Watchdog service will terminate spends if it detects a discrepency between LND and the database, for this reason **IT IS NOT RECOMMENDED TO USE PUB ALONGSIDE OTHER ACCOUNT SYSTEMS**. While we give the utmost care and attention to security, **the internet is an adversarial environment and SECURITY/RELIABILITY ARE NOT GUARANTEED- USE AT YOUR OWN RISK**.
|
||||
|
||||
## Umbrel Installation
|
||||
|
||||
Coming Soon
|
||||
|
||||
## Desktop Installation
|
||||
|
||||
Coming Soon
|
||||
|
||||
## Docker
|
||||
|
||||
`docker pull ghcr.io/shocknet/lightning-pub:latest`
|
||||
|
||||
## Manual CLI Installation
|
||||
|
||||
#### Notes:
|
||||
* Use of a reverse proxy is only required if you wish to serve LNURLs
|
||||
* The service defaults to port `1776`
|
||||
* Requires [Node.js](https://nodejs.org) >=18.x
|
||||
* Commands for your specific OS may differ slightly, Ubuntu/Debian used for example
|
||||
|
||||
#### Steps:
|
||||
1) Run [LND](https://github.com/lightningnetwork/lnd/releases) if you aren't already
|
||||
|
||||
*Example mainnet startup*:
|
||||
|
||||
```
|
||||
./lnd --bitcoin.active --bitcoin.mainnet --bitcoin.node=neutrino --neutrino.addpeer=neutrino.shock.network --feeurl=https://nodes.lightning.computer/fees/v1/btc-fee-estimates.json
|
||||
```
|
||||
|
||||
|
||||
2) Download and Install Lightning.Pub
|
||||
|
||||
|
||||
* `git clone https://github.com/shocknet/Lightning.Pub`
|
||||
|
||||
* `cd Lightning.Pub && npm i`
|
||||
|
||||
|
||||
3) Configure values to env file as desired
|
||||
* `cp env.example .env && nano .env`
|
||||
|
||||
5) `npm start`
|
||||
|
||||
- A default "wallet" application pool will be automatically created, if you wish to create other app pools:
|
||||
|
||||
* `curl -XPOST -H 'Authorization: Bearer defined_in_ADMIN_TOKEN_env' -H "Content-type: application/json" -d '{"name":"ExampleApplicationPoolName"}' 'http://localhost:8080/api/admin/app/add'`
|
||||
|
||||
5) Connect with [wallet2](https://github.com/shocknet/wallet2) using the wallet nprofile that gets logged at startup.
|
||||
> [!NOTE]
|
||||
> Connecting with wallet will create an account on the node, it will not show or have access to the full LND balance
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
version: "3.7"
|
||||
services:
|
||||
app_proxy:
|
||||
environment:
|
||||
APP_HOST: lightning-pub
|
||||
APP_PORT: 1776
|
||||
|
||||
server:
|
||||
image: ghcr.io/shocknet/lightning.pub:umbrel-works
|
||||
volumes:
|
||||
- "${APP_DATA_DIR}/data:/data"
|
||||
- "${APP_LIGHTNING_NODE_DATA_DIR}:/lnd:ro"
|
||||
environment:
|
||||
LN_BACKEND_TYPE: "LND"
|
||||
LND_ADDRESS: $APP_LIGHTNING_NODE_IP:$APP_LIGHTNING_NODE_GRPC_PORT
|
||||
LND_CERT_PATH: "/lnd/tls.cert"
|
||||
LND_MACAROON_PATH: "/lnd/data/chain/bitcoin/${APP_BITCOIN_NETWORK}/admin.macaroon"
|
||||
DATABASE_FILE: "/data/db.sqlite"
|
||||
METRICS_DATABASE_FILE: "/data/metrics.sqlite"
|
||||
PORT: 1776
|
||||
restart: on-failure
|
||||
stop_grace_period: 1m
|
||||
version: "3.7"
|
||||
services:
|
||||
app_proxy:
|
||||
environment:
|
||||
APP_HOST: lightning-pub
|
||||
APP_PORT: 1776
|
||||
|
||||
server:
|
||||
image: ghcr.io/shocknet/lightning.pub:umbrel-works
|
||||
volumes:
|
||||
- "${APP_DATA_DIR}/data:/data"
|
||||
- "${APP_LIGHTNING_NODE_DATA_DIR}:/lnd:ro"
|
||||
environment:
|
||||
LN_BACKEND_TYPE: "LND"
|
||||
LND_ADDRESS: $APP_LIGHTNING_NODE_IP:$APP_LIGHTNING_NODE_GRPC_PORT
|
||||
LND_CERT_PATH: "/lnd/tls.cert"
|
||||
LND_MACAROON_PATH: "/lnd/data/chain/bitcoin/${APP_BITCOIN_NETWORK}/admin.macaroon"
|
||||
DATABASE_FILE: "/data/db.sqlite"
|
||||
METRICS_DATABASE_FILE: "/data/metrics.sqlite"
|
||||
PORT: 1776
|
||||
restart: on-failure
|
||||
stop_grace_period: 1m
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
manifestVersion: 1
|
||||
id: lightning-pub
|
||||
category: finance
|
||||
name: Lightning.Pub
|
||||
version: "1.0.0"
|
||||
tagline: lightning, nostr, accounts, lnurl, web
|
||||
description: >-
|
||||
"Pub" is a Nostr-native account system designed
|
||||
to make running Lightning infrastructure for your friends/family/customers
|
||||
easier than previously thought possible.
|
||||
|
||||
Being Nostr-native eliminates the complexity of configuring your node like a server by using commodity Nostr relays.
|
||||
These relays, unlike LNURL proxies, are trustless by nature of Nostr's own encryption spec (NIP44).
|
||||
|
||||
Support for optional services are integrated into Pub for operators seeking backward compatibility with legacy LNURLs and Lightning Addresses.
|
||||
|
||||
By solving the networking and programability hurdles, Pub provides Lightning with a 3rd Layer that enables node-runners and
|
||||
Uncle Jims to more easily bring their personal network into Bitcoin's permissionless economy. In doing so, Pub runners
|
||||
can keep the Lightning Network decentralized, with custodial scaling that is free of fiat rails, large banks,
|
||||
and other forms of high-time-preference shitcoinery.
|
||||
developer: shocknet
|
||||
website: https://shock.network
|
||||
dependencies:
|
||||
- lightning
|
||||
repo: https://github.com/shocknet/Lightning.Pub
|
||||
support: https://github.com/shocknet/Lightning.Pub/discussions
|
||||
port: 1776
|
||||
gallery:
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
path: ""
|
||||
defaultUsername: ""
|
||||
defaultPassword: ""
|
||||
submitter: shocknet
|
||||
manifestVersion: 1
|
||||
id: lightning-pub
|
||||
category: finance
|
||||
name: Lightning.Pub
|
||||
version: "1.0.0"
|
||||
tagline: lightning, nostr, accounts, lnurl, web
|
||||
description: >-
|
||||
"Pub" is a Nostr-native account system designed
|
||||
to make running Lightning infrastructure for your friends/family/customers
|
||||
easier than previously thought possible.
|
||||
|
||||
Being Nostr-native eliminates the complexity of configuring your node like a server by using commodity Nostr relays.
|
||||
These relays, unlike LNURL proxies, are trustless by nature of Nostr's own encryption spec (NIP44).
|
||||
|
||||
Support for optional services are integrated into Pub for operators seeking backward compatibility with legacy LNURLs and Lightning Addresses.
|
||||
|
||||
By solving the networking and programability hurdles, Pub provides Lightning with a 3rd Layer that enables node-runners and
|
||||
Uncle Jims to more easily bring their personal network into Bitcoin's permissionless economy. In doing so, Pub runners
|
||||
can keep the Lightning Network decentralized, with custodial scaling that is free of fiat rails, large banks,
|
||||
and other forms of high-time-preference shitcoinery.
|
||||
developer: shocknet
|
||||
website: https://shock.network
|
||||
dependencies:
|
||||
- lightning
|
||||
repo: https://github.com/shocknet/Lightning.Pub
|
||||
support: https://github.com/shocknet/Lightning.Pub/discussions
|
||||
port: 1776
|
||||
gallery:
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
path: ""
|
||||
defaultUsername: ""
|
||||
defaultPassword: ""
|
||||
submitter: shocknet
|
||||
submission: https://github.com/getumbrel/umbrel/pull/334
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
import { DataSource } from "typeorm"
|
||||
import { User } from "./build/src/services/storage/entity/User.js"
|
||||
import { UserReceivingInvoice } from "./build/src/services/storage/entity/UserReceivingInvoice.js"
|
||||
import { AddressReceivingTransaction } from "./build/src/services/storage/entity/AddressReceivingTransaction.js"
|
||||
import { Application } from "./build/src/services/storage/entity/Application.js"
|
||||
import { ApplicationUser } from "./build/src/services/storage/entity/ApplicationUser.js"
|
||||
import { BalanceEvent } from "./build/src/services/storage/entity/BalanceEvent.js"
|
||||
import { ChannelBalanceEvent } from "./build/src/services/storage/entity/ChannelsBalanceEvent.js"
|
||||
import { Product } from "./build/src/services/storage/entity/Product.js"
|
||||
import { RoutingEvent } from "./build/src/services/storage/entity/RoutingEvent.js"
|
||||
import { UserBasicAuth } from "./build/src/services/storage/entity/UserBasicAuth.js"
|
||||
import { UserEphemeralKey } from "./build/src/services/storage/entity/UserEphemeralKey.js"
|
||||
import { UserInvoicePayment } from "./build/src/services/storage/entity/UserInvoicePayment.js"
|
||||
import { UserReceivingAddress } from "./build/src/services/storage/entity/UserReceivingAddress.js"
|
||||
import { UserToUserPayment } from "./build/src/services/storage/entity/UserToUserPayment.js"
|
||||
import { UserTransactionPayment } from "./build/src/services/storage/entity/UserTransactionPayment.js"
|
||||
|
||||
export default new DataSource({
|
||||
type: "sqlite",
|
||||
database: "source.sqlite",
|
||||
// logging: true,
|
||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, RoutingEvent, BalanceEvent, ChannelBalanceEvent],
|
||||
// synchronize: true,
|
||||
import { DataSource } from "typeorm"
|
||||
import { User } from "./build/src/services/storage/entity/User.js"
|
||||
import { UserReceivingInvoice } from "./build/src/services/storage/entity/UserReceivingInvoice.js"
|
||||
import { AddressReceivingTransaction } from "./build/src/services/storage/entity/AddressReceivingTransaction.js"
|
||||
import { Application } from "./build/src/services/storage/entity/Application.js"
|
||||
import { ApplicationUser } from "./build/src/services/storage/entity/ApplicationUser.js"
|
||||
import { BalanceEvent } from "./build/src/services/storage/entity/BalanceEvent.js"
|
||||
import { ChannelBalanceEvent } from "./build/src/services/storage/entity/ChannelsBalanceEvent.js"
|
||||
import { Product } from "./build/src/services/storage/entity/Product.js"
|
||||
import { RoutingEvent } from "./build/src/services/storage/entity/RoutingEvent.js"
|
||||
import { UserBasicAuth } from "./build/src/services/storage/entity/UserBasicAuth.js"
|
||||
import { UserEphemeralKey } from "./build/src/services/storage/entity/UserEphemeralKey.js"
|
||||
import { UserInvoicePayment } from "./build/src/services/storage/entity/UserInvoicePayment.js"
|
||||
import { UserReceivingAddress } from "./build/src/services/storage/entity/UserReceivingAddress.js"
|
||||
import { UserToUserPayment } from "./build/src/services/storage/entity/UserToUserPayment.js"
|
||||
import { UserTransactionPayment } from "./build/src/services/storage/entity/UserTransactionPayment.js"
|
||||
|
||||
export default new DataSource({
|
||||
type: "sqlite",
|
||||
database: "source.sqlite",
|
||||
// logging: true,
|
||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, RoutingEvent, BalanceEvent, ChannelBalanceEvent],
|
||||
// synchronize: true,
|
||||
})
|
||||
178
env.example
178
env.example
|
|
@ -1,89 +1,89 @@
|
|||
# Example configuration for Lightning.Pub
|
||||
# Copy this file as .env in the Pub folder and uncomment the desired settings to override defaults
|
||||
# Alternatively, these settings can be passed as environment variables at startup
|
||||
|
||||
#LND_CONNECTION
|
||||
# Defaults typical for straight Linux
|
||||
# Containers, Mac and Windows may need more detailed paths
|
||||
#LND_ADDRESS=127.0.0.1:10009
|
||||
#LND_CERT_PATH=~/.lnd/tls.cert
|
||||
#LND_MACAROON_PATH=~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon
|
||||
|
||||
LIQUIDITY_PROVIDER_PUB=
|
||||
|
||||
#DB
|
||||
#DATABASE_FILE=db.sqlite
|
||||
#METRICS_DATABASE_FILE=metrics.sqlite
|
||||
#LOGS_DIR=logs
|
||||
|
||||
#LOCALHOST
|
||||
#ADMIN_TOKEN=
|
||||
#PORT=1776
|
||||
#JWT_SECRET=
|
||||
|
||||
#LIGHTNING
|
||||
# Maximum amount in network fees passed to LND when it pays an external invoice
|
||||
# BPS are basis points, 100 BPS = 1%
|
||||
#OUTBOUND_MAX_FEE_BPS=60
|
||||
#OUTBOUND_MAX_FEE_EXTRA_SATS=100
|
||||
# If the back-end doesn't have adequate channel capacity, buy one from an LSP
|
||||
# Will execute when it costs less than 1% of balance and uses a trusted peer
|
||||
#BOOTSTRAP=1
|
||||
|
||||
#ROOT_FEES
|
||||
# Applied to either debits or credits and sent to an admin account
|
||||
# BPS are basis points, 100 BPS = 1%
|
||||
#INCOMING_CHAIN_FEE_ROOT_BPS=0
|
||||
#INCOMING_INVOICE_FEE_ROOT_BPS=0
|
||||
# Chain spends are currently unstable and thus disabled, do not use until further notice
|
||||
#OUTGOING_CHAIN_FEE_ROOT_BPS=60
|
||||
# Outgoing Invoice Fee must be >= Lightning Outbound Max Fee so admins don't incur losses on spends
|
||||
#OUTGOING_INVOICE_FEE_ROOT_BPS=60
|
||||
# Internal user fees bugged, do not use until further notice
|
||||
#TX_FEE_INTERNAL_ROOT_BPS=0 #applied to inter-application txns
|
||||
|
||||
#APP_FEES
|
||||
# An extra fee applied at the app level and sent to the application owner
|
||||
#INCOMING_INVOICE_FEE_USER_BPS=0
|
||||
#OUTGOING_INVOICE_FEE_USER_BPS=0
|
||||
#TX_FEE_INTERNAL_USER_BPS=0
|
||||
|
||||
#NOSTR
|
||||
# Default relay may become rate-limited without a paid subscription
|
||||
#NOSTR_RELAYS=wss://strfry.shock.network
|
||||
|
||||
#LNURL
|
||||
# Optional
|
||||
# If undefined, LNURLs (including Lightning Address) will be disabled
|
||||
# To enable, add a reachable https endpoint for requests (or purchase a subscription)
|
||||
# You also need an SSL reverse proxy from the domain to this local host
|
||||
# Read more at https://docs.shock.network
|
||||
#SERVICE_URL=https://yourdomainhere.xyz
|
||||
|
||||
#SUBSCRIPTION_SERVICES
|
||||
# Opt-in to cloud relays for LNURL and Nostr
|
||||
# A small monthly fee supports the developers
|
||||
# Read more at https://docs.shock.network
|
||||
#SUBSCRIBER=1
|
||||
|
||||
#DEV_OPTS
|
||||
#MOCK_LND=false
|
||||
#ALLOW_BALANCE_MIGRATION=false
|
||||
#MIGRATE_DB=false
|
||||
#LOG_LEVEL=DEBUG
|
||||
|
||||
#METRICS
|
||||
#RECORD_PERFORMANCE=true
|
||||
#SKIP_SANITY_CHECK=false
|
||||
# A read-only token that can be used with dashboard to view reports
|
||||
#METRICS_TOKEN=
|
||||
# Disable outbound payments aka honeypot mode
|
||||
#DISABLE_EXTERNAL_PAYMENTS=false
|
||||
|
||||
#WATCHDOG SECURITY
|
||||
# A last line of defense against 0-day drainage attacks
|
||||
# This will monitor LND separately and terminate sends if a balance discrepency is detected
|
||||
# This setting defaults to 0 meaning no discrepency will be tolerated
|
||||
# Increase this values to add a spending buffer for non-Pub services sharing LND
|
||||
# Max difference between users balance and LND balance at Pub startup
|
||||
#WATCHDOG_MAX_DIFF_SATS=0
|
||||
# Example configuration for Lightning.Pub
|
||||
# Copy this file as .env in the Pub folder and uncomment the desired settings to override defaults
|
||||
# Alternatively, these settings can be passed as environment variables at startup
|
||||
|
||||
#LND_CONNECTION
|
||||
# Defaults typical for straight Linux
|
||||
# Containers, Mac and Windows may need more detailed paths
|
||||
#LND_ADDRESS=127.0.0.1:10009
|
||||
#LND_CERT_PATH=~/.lnd/tls.cert
|
||||
#LND_MACAROON_PATH=~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon
|
||||
|
||||
LIQUIDITY_PROVIDER_PUB=
|
||||
|
||||
#DB
|
||||
#DATABASE_FILE=db.sqlite
|
||||
#METRICS_DATABASE_FILE=metrics.sqlite
|
||||
#LOGS_DIR=logs
|
||||
|
||||
#LOCALHOST
|
||||
#ADMIN_TOKEN=
|
||||
#PORT=1776
|
||||
#JWT_SECRET=
|
||||
|
||||
#LIGHTNING
|
||||
# Maximum amount in network fees passed to LND when it pays an external invoice
|
||||
# BPS are basis points, 100 BPS = 1%
|
||||
#OUTBOUND_MAX_FEE_BPS=60
|
||||
#OUTBOUND_MAX_FEE_EXTRA_SATS=100
|
||||
# If the back-end doesn't have adequate channel capacity, buy one from an LSP
|
||||
# Will execute when it costs less than 1% of balance and uses a trusted peer
|
||||
#BOOTSTRAP=1
|
||||
|
||||
#ROOT_FEES
|
||||
# Applied to either debits or credits and sent to an admin account
|
||||
# BPS are basis points, 100 BPS = 1%
|
||||
#INCOMING_CHAIN_FEE_ROOT_BPS=0
|
||||
#INCOMING_INVOICE_FEE_ROOT_BPS=0
|
||||
# Chain spends are currently unstable and thus disabled, do not use until further notice
|
||||
#OUTGOING_CHAIN_FEE_ROOT_BPS=60
|
||||
# Outgoing Invoice Fee must be >= Lightning Outbound Max Fee so admins don't incur losses on spends
|
||||
#OUTGOING_INVOICE_FEE_ROOT_BPS=60
|
||||
# Internal user fees bugged, do not use until further notice
|
||||
#TX_FEE_INTERNAL_ROOT_BPS=0 #applied to inter-application txns
|
||||
|
||||
#APP_FEES
|
||||
# An extra fee applied at the app level and sent to the application owner
|
||||
#INCOMING_INVOICE_FEE_USER_BPS=0
|
||||
#OUTGOING_INVOICE_FEE_USER_BPS=0
|
||||
#TX_FEE_INTERNAL_USER_BPS=0
|
||||
|
||||
#NOSTR
|
||||
# Default relay may become rate-limited without a paid subscription
|
||||
#NOSTR_RELAYS=wss://strfry.shock.network
|
||||
|
||||
#LNURL
|
||||
# Optional
|
||||
# If undefined, LNURLs (including Lightning Address) will be disabled
|
||||
# To enable, add a reachable https endpoint for requests (or purchase a subscription)
|
||||
# You also need an SSL reverse proxy from the domain to this local host
|
||||
# Read more at https://docs.shock.network
|
||||
#SERVICE_URL=https://yourdomainhere.xyz
|
||||
|
||||
#SUBSCRIPTION_SERVICES
|
||||
# Opt-in to cloud relays for LNURL and Nostr
|
||||
# A small monthly fee supports the developers
|
||||
# Read more at https://docs.shock.network
|
||||
#SUBSCRIBER=1
|
||||
|
||||
#DEV_OPTS
|
||||
#MOCK_LND=false
|
||||
#ALLOW_BALANCE_MIGRATION=false
|
||||
#MIGRATE_DB=false
|
||||
#LOG_LEVEL=DEBUG
|
||||
|
||||
#METRICS
|
||||
#RECORD_PERFORMANCE=true
|
||||
#SKIP_SANITY_CHECK=false
|
||||
# A read-only token that can be used with dashboard to view reports
|
||||
#METRICS_TOKEN=
|
||||
# Disable outbound payments aka honeypot mode
|
||||
#DISABLE_EXTERNAL_PAYMENTS=false
|
||||
|
||||
#WATCHDOG SECURITY
|
||||
# A last line of defense against 0-day drainage attacks
|
||||
# This will monitor LND separately and terminate sends if a balance discrepency is detected
|
||||
# This setting defaults to 0 meaning no discrepency will be tolerated
|
||||
# Increase this values to add a spending buffer for non-Pub services sharing LND
|
||||
# Max difference between users balance and LND balance at Pub startup
|
||||
#WATCHDOG_MAX_DIFF_SATS=0
|
||||
|
|
|
|||
12
genkey.js
12
genkey.js
|
|
@ -1,6 +1,6 @@
|
|||
import { generatePrivateKey, getPublicKey } from 'nostr-tools'
|
||||
const p = generatePrivateKey()
|
||||
console.log({
|
||||
privateKey: p,
|
||||
publicKey: getPublicKey(Buffer.from(p, 'hex'))
|
||||
})
|
||||
import { generatePrivateKey, getPublicKey } from 'nostr-tools'
|
||||
const p = generatePrivateKey()
|
||||
console.log({
|
||||
privateKey: p,
|
||||
publicKey: getPublicKey(Buffer.from(p, 'hex'))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { DataSource } from "typeorm"
|
||||
import { ChannelRouting } from "./build/src/services/storage/entity/ChannelRouting.js"
|
||||
|
||||
|
||||
|
||||
export default new DataSource({
|
||||
type: "sqlite",
|
||||
database: "metrics.sqlite",
|
||||
entities: [ChannelRouting],
|
||||
import { DataSource } from "typeorm"
|
||||
import { ChannelRouting } from "./build/src/services/storage/entity/ChannelRouting.js"
|
||||
|
||||
|
||||
|
||||
export default new DataSource({
|
||||
type: "sqlite",
|
||||
database: "metrics.sqlite",
|
||||
entities: [ChannelRouting],
|
||||
});
|
||||
18636
package-lock.json
generated
18636
package-lock.json
generated
File diff suppressed because it is too large
Load diff
162
package.json
162
package.json
|
|
@ -1,81 +1,81 @@
|
|||
{
|
||||
"name": "lightning.pub",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"clean": "rimraf build",
|
||||
"test": "npm run clean && tsc && node build/src/tests/testRunner.js",
|
||||
"start": "npm run clean && tsc && node build/src/index.js",
|
||||
"start:ci": "git reset --hard && git pull && npm run start",
|
||||
"build_autogenerated": "cd proto && rimraf autogenerated && protoc -I ./service --pub_out=. service/*",
|
||||
"build_lnd_client_1": "cd proto && protoc -I ./others --plugin=.\\node_modules\\.bin\\protoc-gen-ts_proto.cmd --ts_proto_out=./lnd --ts_proto_opt=esModuleInterop=true others/* ",
|
||||
"build_lnd_client": "cd proto && rimraf lnd/* && npx protoc --ts_out ./lnd --ts_opt long_type_string --proto_path others others/* ",
|
||||
"typeorm": "typeorm-ts-node-commonjs"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/shocknet/Lightning.Pub.git"
|
||||
},
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/shocknet/Lightning.Pub/issues"
|
||||
},
|
||||
"homepage": "https://github.com/shocknet/Lightning.Pub#readme",
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.6.7",
|
||||
"@protobuf-ts/grpc-transport": "^2.5.0",
|
||||
"@protobuf-ts/plugin": "^2.5.0",
|
||||
"@protobuf-ts/runtime": "^2.5.0",
|
||||
"@stablelib/xchacha20": "^1.0.1",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^17.0.31",
|
||||
"@types/secp256k1": "^4.0.3",
|
||||
"axios": "^0.28.0",
|
||||
"bech32": "^2.0.0",
|
||||
"bitcoin-core": "^4.2.0",
|
||||
"chai": "^4.3.7",
|
||||
"chai-string": "^1.5.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"cors": "^2.8.5",
|
||||
"csv": "^6.3.8",
|
||||
"dotenv": "^16.0.0",
|
||||
"eccrypto": "^1.1.6",
|
||||
"express": "^4.19.2",
|
||||
"globby": "^13.1.2",
|
||||
"grpc-tools": "^1.11.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nostr-tools": "^1.9.0",
|
||||
"pg": "^8.4.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.5.5",
|
||||
"secp256k1": "^4.0.3",
|
||||
"sqlite3": "^5.1.5",
|
||||
"ts-node": "^10.7.0",
|
||||
"ts-proto": "^1.131.2",
|
||||
"typeorm": "0.3.15",
|
||||
"typescript": "^4.6.4",
|
||||
"uuid": "^8.3.2",
|
||||
"websocket": "^1.0.34",
|
||||
"websocket-polyfill": "^0.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.4",
|
||||
"@types/chai-string": "^1.4.5",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/eccrypto": "^1.1.3",
|
||||
"@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",
|
||||
"@types/websocket": "^1.0.6",
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-node": "10.7.0",
|
||||
"typescript": "4.5.2"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "lightning.pub",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"clean": "rimraf build",
|
||||
"test": "npm run clean && tsc && node build/src/tests/testRunner.js",
|
||||
"start": "npm run clean && tsc && node build/src/index.js",
|
||||
"start:ci": "git reset --hard && git pull && npm run start",
|
||||
"build_autogenerated": "cd proto && rimraf autogenerated && protoc -I ./service --pub_out=. service/*",
|
||||
"build_lnd_client_1": "cd proto && protoc -I ./others --plugin=.\\node_modules\\.bin\\protoc-gen-ts_proto.cmd --ts_proto_out=./lnd --ts_proto_opt=esModuleInterop=true others/* ",
|
||||
"build_lnd_client": "cd proto && rimraf lnd/* && npx protoc --ts_out ./lnd --ts_opt long_type_string --proto_path others others/* ",
|
||||
"typeorm": "typeorm-ts-node-commonjs"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/shocknet/Lightning.Pub.git"
|
||||
},
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/shocknet/Lightning.Pub/issues"
|
||||
},
|
||||
"homepage": "https://github.com/shocknet/Lightning.Pub#readme",
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.6.7",
|
||||
"@protobuf-ts/grpc-transport": "^2.5.0",
|
||||
"@protobuf-ts/plugin": "^2.5.0",
|
||||
"@protobuf-ts/runtime": "^2.5.0",
|
||||
"@stablelib/xchacha20": "^1.0.1",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^17.0.31",
|
||||
"@types/secp256k1": "^4.0.3",
|
||||
"axios": "^0.28.0",
|
||||
"bech32": "^2.0.0",
|
||||
"bitcoin-core": "^4.2.0",
|
||||
"chai": "^4.3.7",
|
||||
"chai-string": "^1.5.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"cors": "^2.8.5",
|
||||
"csv": "^6.3.8",
|
||||
"dotenv": "^16.0.0",
|
||||
"eccrypto": "^1.1.6",
|
||||
"express": "^4.19.2",
|
||||
"globby": "^13.1.2",
|
||||
"grpc-tools": "^1.11.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nostr-tools": "^1.9.0",
|
||||
"pg": "^8.4.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.5.5",
|
||||
"secp256k1": "^4.0.3",
|
||||
"sqlite3": "^5.1.5",
|
||||
"ts-node": "^10.7.0",
|
||||
"ts-proto": "^1.131.2",
|
||||
"typeorm": "0.3.15",
|
||||
"typescript": "^4.6.4",
|
||||
"uuid": "^8.3.2",
|
||||
"websocket": "^1.0.34",
|
||||
"websocket-polyfill": "^0.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.4",
|
||||
"@types/chai-string": "^1.4.5",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/eccrypto": "^1.1.3",
|
||||
"@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",
|
||||
"@types/websocket": "^1.0.6",
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-node": "10.7.0",
|
||||
"typescript": "4.5.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
create lnd classes: `npx protoc -I ./others --ts_out=./lnd others/*`
|
||||
create lnd classes: `npx protoc -I ./others --ts_out=./lnd others/*`
|
||||
create server classes: `npx protoc -I ./service --pub_out=. service/*`
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,282 +1,282 @@
|
|||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
import { NostrRequest } from './nostr_transport.js'
|
||||
import * as Types from './types.js'
|
||||
export type ResultError = { status: 'ERROR', reason: string }
|
||||
|
||||
export type NostrClientParams = {
|
||||
pubDestination: string
|
||||
retrieveNostrUserAuth: () => Promise<string | null>
|
||||
checkResult?: true
|
||||
}
|
||||
export default (params: NostrClientParams, send: (to:string, message: NostrRequest) => Promise<any>, subscribe: (to:string, message: NostrRequest, cb:(res:any)=> void) => void) => ({
|
||||
LinkNPubThroughToken: async (request: Types.LinkNPubThroughTokenRequest): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'LinkNPubThroughToken',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
UserHealth: async (): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'UserHealth',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetUserInfo: async (): Promise<ResultError | ({ status: 'OK' }& Types.UserInfo)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetUserInfo',authIdentifier:auth, ...nostrRequest })
|
||||
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.UserInfoValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
AddProduct: async (request: Types.AddProductRequest): Promise<ResultError | ({ status: 'OK' }& Types.Product)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'AddProduct',authIdentifier:auth, ...nostrRequest })
|
||||
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.ProductValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
NewProductInvoice: async (query: Types.NewProductInvoice_Query): Promise<ResultError | ({ status: 'OK' }& Types.NewInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.query = query
|
||||
const data = await send(params.pubDestination, {rpcName:'NewProductInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
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' }
|
||||
},
|
||||
GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise<ResultError | ({ status: 'OK' }& Types.GetUserOperationsResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'GetUserOperations',authIdentifier:auth, ...nostrRequest })
|
||||
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.GetUserOperationsResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
NewAddress: async (request: Types.NewAddressRequest): Promise<ResultError | ({ status: 'OK' }& Types.NewAddressResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'NewAddress',authIdentifier:auth, ...nostrRequest })
|
||||
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.NewAddressResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayAddress: async (request: Types.PayAddressRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayAddressResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'PayAddress',authIdentifier:auth, ...nostrRequest })
|
||||
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.PayAddressResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
NewInvoice: async (request: Types.NewInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.NewInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'NewInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
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' }
|
||||
},
|
||||
DecodeInvoice: async (request: Types.DecodeInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.DecodeInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'DecodeInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
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.DecodeInvoiceResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayInvoice: async (request: Types.PayInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'PayInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
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.PayInvoiceResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
OpenChannel: async (request: Types.OpenChannelRequest): Promise<ResultError | ({ status: 'OK' }& Types.OpenChannelResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'OpenChannel',authIdentifier:auth, ...nostrRequest })
|
||||
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.OpenChannelResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLnurlWithdrawLink: async (): Promise<ResultError | ({ status: 'OK' }& Types.LnurlLinkResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLnurlWithdrawLink',authIdentifier:auth, ...nostrRequest })
|
||||
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.LnurlLinkResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLnurlPayLink: async (): Promise<ResultError | ({ status: 'OK' }& Types.LnurlLinkResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLnurlPayLink',authIdentifier:auth, ...nostrRequest })
|
||||
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.LnurlLinkResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLNURLChannelLink: async (): Promise<ResultError | ({ status: 'OK' }& Types.LnurlLinkResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLNURLChannelLink',authIdentifier:auth, ...nostrRequest })
|
||||
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.LnurlLinkResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLiveUserOperations: async (cb: (res:ResultError | ({ status: 'OK' }& Types.LiveUserOperation)) => void): Promise<void> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
subscribe(params.pubDestination, {rpcName:'GetLiveUserOperations',authIdentifier:auth, ...nostrRequest }, (data) => {
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return cb(data)
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return cb({ status: 'OK', ...result })
|
||||
const error = Types.LiveUserOperationValidate(result)
|
||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
||||
}
|
||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
||||
})
|
||||
},
|
||||
GetMigrationUpdate: async (cb: (res:ResultError | ({ status: 'OK' }& Types.MigrationUpdate)) => void): Promise<void> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
subscribe(params.pubDestination, {rpcName:'GetMigrationUpdate',authIdentifier:auth, ...nostrRequest }, (data) => {
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return cb(data)
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return cb({ status: 'OK', ...result })
|
||||
const error = Types.MigrationUpdateValidate(result)
|
||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
||||
}
|
||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
||||
})
|
||||
},
|
||||
GetHttpCreds: async (cb: (res:ResultError | ({ status: 'OK' }& Types.HttpCreds)) => void): Promise<void> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
subscribe(params.pubDestination, {rpcName:'GetHttpCreds',authIdentifier:auth, ...nostrRequest }, (data) => {
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return cb(data)
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return cb({ status: 'OK', ...result })
|
||||
const error = Types.HttpCredsValidate(result)
|
||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
||||
}
|
||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
||||
})
|
||||
},
|
||||
BatchUser: async (requests:Types.UserMethodInputs[]): Promise<ResultError | ({ status: 'OK', responses:(Types.UserMethodOutputs)[] })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {body:{requests}}
|
||||
const data = await send(params.pubDestination, {rpcName:'BatchUser',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
}
|
||||
})
|
||||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
import { NostrRequest } from './nostr_transport.js'
|
||||
import * as Types from './types.js'
|
||||
export type ResultError = { status: 'ERROR', reason: string }
|
||||
|
||||
export type NostrClientParams = {
|
||||
pubDestination: string
|
||||
retrieveNostrUserAuth: () => Promise<string | null>
|
||||
checkResult?: true
|
||||
}
|
||||
export default (params: NostrClientParams, send: (to:string, message: NostrRequest) => Promise<any>, subscribe: (to:string, message: NostrRequest, cb:(res:any)=> void) => void) => ({
|
||||
LinkNPubThroughToken: async (request: Types.LinkNPubThroughTokenRequest): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'LinkNPubThroughToken',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
UserHealth: async (): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'UserHealth',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetUserInfo: async (): Promise<ResultError | ({ status: 'OK' }& Types.UserInfo)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetUserInfo',authIdentifier:auth, ...nostrRequest })
|
||||
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.UserInfoValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
AddProduct: async (request: Types.AddProductRequest): Promise<ResultError | ({ status: 'OK' }& Types.Product)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'AddProduct',authIdentifier:auth, ...nostrRequest })
|
||||
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.ProductValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
NewProductInvoice: async (query: Types.NewProductInvoice_Query): Promise<ResultError | ({ status: 'OK' }& Types.NewInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.query = query
|
||||
const data = await send(params.pubDestination, {rpcName:'NewProductInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
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' }
|
||||
},
|
||||
GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise<ResultError | ({ status: 'OK' }& Types.GetUserOperationsResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'GetUserOperations',authIdentifier:auth, ...nostrRequest })
|
||||
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.GetUserOperationsResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
NewAddress: async (request: Types.NewAddressRequest): Promise<ResultError | ({ status: 'OK' }& Types.NewAddressResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'NewAddress',authIdentifier:auth, ...nostrRequest })
|
||||
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.NewAddressResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayAddress: async (request: Types.PayAddressRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayAddressResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'PayAddress',authIdentifier:auth, ...nostrRequest })
|
||||
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.PayAddressResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
NewInvoice: async (request: Types.NewInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.NewInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'NewInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
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' }
|
||||
},
|
||||
DecodeInvoice: async (request: Types.DecodeInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.DecodeInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'DecodeInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
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.DecodeInvoiceResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayInvoice: async (request: Types.PayInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'PayInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
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.PayInvoiceResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
OpenChannel: async (request: Types.OpenChannelRequest): Promise<ResultError | ({ status: 'OK' }& Types.OpenChannelResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'OpenChannel',authIdentifier:auth, ...nostrRequest })
|
||||
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.OpenChannelResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLnurlWithdrawLink: async (): Promise<ResultError | ({ status: 'OK' }& Types.LnurlLinkResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLnurlWithdrawLink',authIdentifier:auth, ...nostrRequest })
|
||||
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.LnurlLinkResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLnurlPayLink: async (): Promise<ResultError | ({ status: 'OK' }& Types.LnurlLinkResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLnurlPayLink',authIdentifier:auth, ...nostrRequest })
|
||||
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.LnurlLinkResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLNURLChannelLink: async (): Promise<ResultError | ({ status: 'OK' }& Types.LnurlLinkResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLNURLChannelLink',authIdentifier:auth, ...nostrRequest })
|
||||
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.LnurlLinkResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLiveUserOperations: async (cb: (res:ResultError | ({ status: 'OK' }& Types.LiveUserOperation)) => void): Promise<void> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
subscribe(params.pubDestination, {rpcName:'GetLiveUserOperations',authIdentifier:auth, ...nostrRequest }, (data) => {
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return cb(data)
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return cb({ status: 'OK', ...result })
|
||||
const error = Types.LiveUserOperationValidate(result)
|
||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
||||
}
|
||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
||||
})
|
||||
},
|
||||
GetMigrationUpdate: async (cb: (res:ResultError | ({ status: 'OK' }& Types.MigrationUpdate)) => void): Promise<void> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
subscribe(params.pubDestination, {rpcName:'GetMigrationUpdate',authIdentifier:auth, ...nostrRequest }, (data) => {
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return cb(data)
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return cb({ status: 'OK', ...result })
|
||||
const error = Types.MigrationUpdateValidate(result)
|
||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
||||
}
|
||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
||||
})
|
||||
},
|
||||
GetHttpCreds: async (cb: (res:ResultError | ({ status: 'OK' }& Types.HttpCreds)) => void): Promise<void> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
subscribe(params.pubDestination, {rpcName:'GetHttpCreds',authIdentifier:auth, ...nostrRequest }, (data) => {
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return cb(data)
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return cb({ status: 'OK', ...result })
|
||||
const error = Types.HttpCredsValidate(result)
|
||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
||||
}
|
||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
||||
})
|
||||
},
|
||||
BatchUser: async (requests:Types.UserMethodInputs[]): Promise<ResultError | ({ status: 'OK', responses:(Types.UserMethodOutputs)[] })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {body:{requests}}
|
||||
const data = await send(params.pubDestination, {rpcName:'BatchUser',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,499 +1,499 @@
|
|||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
|
||||
import * as Types from './types.js'
|
||||
export type Logger = { log: (v: any) => void, error: (v: any) => void }
|
||||
type NostrResponse = (message: object) => void
|
||||
export type NostrRequest = {
|
||||
rpcName?: string
|
||||
params?: Record<string, string>
|
||||
query?: Record<string, string>
|
||||
body?: any
|
||||
authIdentifier?: string
|
||||
requestId?: string
|
||||
appId?: string
|
||||
}
|
||||
export type NostrOptions = {
|
||||
logger?: Logger
|
||||
throwErrors?: true
|
||||
metricsCallback: (metrics: Types.RequestMetric[]) => void
|
||||
NostrUserAuthGuard: (appId?: string, identifier?: string) => Promise<Types.UserContext>
|
||||
}
|
||||
const logErrorAndReturnResponse = (error: Error, response: string, res: NostrResponse, logger: Logger, metric: Types.RequestMetric, metricsCallback: (metrics: Types.RequestMetric[]) => void) => {
|
||||
logger.error(error.message || error); metricsCallback([{ ...metric, error: response }]); res({ status: 'ERROR', reason: response })
|
||||
}
|
||||
export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
||||
const logger = opts.logger || { log: console.log, error: console.error }
|
||||
return async (req: NostrRequest, res: NostrResponse, startString: string, startMs: number) => {
|
||||
const startTime = BigInt(startString)
|
||||
const info: Types.RequestInfo = { rpcName: req.rpcName || 'unkown', batch: false, nostr: true, batchSize: 0 }
|
||||
const stats: Types.RequestStats = { startMs, start: startTime, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
|
||||
let authCtx: Types.AuthContext = {}
|
||||
switch (req.rpcName) {
|
||||
case 'LinkNPubThroughToken':
|
||||
try {
|
||||
if (!methods.LinkNPubThroughToken) throw new Error('method: LinkNPubThroughToken is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.LinkNPubThroughTokenRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
await methods.LinkNPubThroughToken({ rpcName: 'LinkNPubThroughToken', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK' })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'UserHealth':
|
||||
try {
|
||||
if (!methods.UserHealth) throw new Error('method: UserHealth is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
await methods.UserHealth({ rpcName: 'UserHealth', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK' })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetUserInfo':
|
||||
try {
|
||||
if (!methods.GetUserInfo) throw new Error('method: GetUserInfo is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetUserInfo({ rpcName: 'GetUserInfo', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'AddProduct':
|
||||
try {
|
||||
if (!methods.AddProduct) throw new Error('method: AddProduct is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.AddProductRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.AddProduct({ rpcName: 'AddProduct', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'NewProductInvoice':
|
||||
try {
|
||||
if (!methods.NewProductInvoice) throw new Error('method: NewProductInvoice is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.NewProductInvoice({ rpcName: 'NewProductInvoice', ctx: authContext, query: req.query || {} })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetUserOperations':
|
||||
try {
|
||||
if (!methods.GetUserOperations) throw new Error('method: GetUserOperations is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.GetUserOperationsRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.GetUserOperations({ rpcName: 'GetUserOperations', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'NewAddress':
|
||||
try {
|
||||
if (!methods.NewAddress) throw new Error('method: NewAddress is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.NewAddressRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.NewAddress({ rpcName: 'NewAddress', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'PayAddress':
|
||||
try {
|
||||
if (!methods.PayAddress) throw new Error('method: PayAddress is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.PayAddressRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.PayAddress({ rpcName: 'PayAddress', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'NewInvoice':
|
||||
try {
|
||||
if (!methods.NewInvoice) throw new Error('method: NewInvoice is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.NewInvoiceRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.NewInvoice({ rpcName: 'NewInvoice', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'DecodeInvoice':
|
||||
try {
|
||||
if (!methods.DecodeInvoice) throw new Error('method: DecodeInvoice is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.DecodeInvoiceRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.DecodeInvoice({ rpcName: 'DecodeInvoice', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'PayInvoice':
|
||||
try {
|
||||
if (!methods.PayInvoice) throw new Error('method: PayInvoice is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.PayInvoiceRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.PayInvoice({ rpcName: 'PayInvoice', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'OpenChannel':
|
||||
try {
|
||||
if (!methods.OpenChannel) throw new Error('method: OpenChannel is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.OpenChannelRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.OpenChannel({ rpcName: 'OpenChannel', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLnurlWithdrawLink':
|
||||
try {
|
||||
if (!methods.GetLnurlWithdrawLink) throw new Error('method: GetLnurlWithdrawLink is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetLnurlWithdrawLink({ rpcName: 'GetLnurlWithdrawLink', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLnurlPayLink':
|
||||
try {
|
||||
if (!methods.GetLnurlPayLink) throw new Error('method: GetLnurlPayLink is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetLnurlPayLink({ rpcName: 'GetLnurlPayLink', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLNURLChannelLink':
|
||||
try {
|
||||
if (!methods.GetLNURLChannelLink) throw new Error('method: GetLNURLChannelLink is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetLNURLChannelLink({ rpcName: 'GetLNURLChannelLink', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLiveUserOperations':
|
||||
try {
|
||||
if (!methods.GetLiveUserOperations) throw new Error('method: GetLiveUserOperations is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
methods.GetLiveUserOperations({
|
||||
rpcName: 'GetLiveUserOperations', ctx: authContext, cb: (response, err) => {
|
||||
stats.handle = process.hrtime.bigint()
|
||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback) } else { res({ status: 'OK', ...response }); opts.metricsCallback([{ ...info, ...stats, ...authContext }]) }
|
||||
}
|
||||
})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetMigrationUpdate':
|
||||
try {
|
||||
if (!methods.GetMigrationUpdate) throw new Error('method: GetMigrationUpdate is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
methods.GetMigrationUpdate({
|
||||
rpcName: 'GetMigrationUpdate', ctx: authContext, cb: (response, err) => {
|
||||
stats.handle = process.hrtime.bigint()
|
||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback) } else { res({ status: 'OK', ...response }); opts.metricsCallback([{ ...info, ...stats, ...authContext }]) }
|
||||
}
|
||||
})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetHttpCreds':
|
||||
try {
|
||||
if (!methods.GetHttpCreds) throw new Error('method: GetHttpCreds is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
methods.GetHttpCreds({
|
||||
rpcName: 'GetHttpCreds', ctx: authContext, cb: (response, err) => {
|
||||
stats.handle = process.hrtime.bigint()
|
||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback) } else { res({ status: 'OK', ...response }); opts.metricsCallback([{ ...info, ...stats, ...authContext }]) }
|
||||
}
|
||||
})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'BatchUser':
|
||||
try {
|
||||
info.batch = true
|
||||
const requests = req.body.requests as Types.UserMethodInputs[]
|
||||
if (!Array.isArray(requests)) throw new Error('invalid body, is not an array')
|
||||
info.batchSize = requests.length
|
||||
if (requests.length > 10) throw new Error('too many requests in the batch')
|
||||
const ctx = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = ctx
|
||||
stats.validate = stats.guard
|
||||
const responses = []
|
||||
const callsMetrics: Types.RequestMetric[] = []
|
||||
for (let i = 0; i < requests.length; i++) {
|
||||
const operation = requests[i]
|
||||
const opInfo: Types.RequestInfo = { rpcName: operation.rpcName, batch: true, nostr: true, batchSize: 0 }
|
||||
const opStats: Types.RequestStats = { startMs, start: startTime, parse: stats.parse, guard: stats.guard, validate: 0n, handle: 0n }
|
||||
try {
|
||||
switch (operation.rpcName) {
|
||||
case 'LinkNPubThroughToken':
|
||||
if (!methods.LinkNPubThroughToken) {
|
||||
throw new Error('method not defined: LinkNPubThroughToken')
|
||||
} else {
|
||||
const error = Types.LinkNPubThroughTokenRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
await methods.LinkNPubThroughToken({ ...operation, ctx }); responses.push({ status: 'OK' })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'UserHealth':
|
||||
if (!methods.UserHealth) {
|
||||
throw new Error('method not defined: UserHealth')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
await methods.UserHealth({ ...operation, ctx }); responses.push({ status: 'OK' })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetUserInfo':
|
||||
if (!methods.GetUserInfo) {
|
||||
throw new Error('method not defined: GetUserInfo')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetUserInfo({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'AddProduct':
|
||||
if (!methods.AddProduct) {
|
||||
throw new Error('method not defined: AddProduct')
|
||||
} else {
|
||||
const error = Types.AddProductRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.AddProduct({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewProductInvoice':
|
||||
if (!methods.NewProductInvoice) {
|
||||
throw new Error('method not defined: NewProductInvoice')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.NewProductInvoice({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetUserOperations':
|
||||
if (!methods.GetUserOperations) {
|
||||
throw new Error('method not defined: GetUserOperations')
|
||||
} else {
|
||||
const error = Types.GetUserOperationsRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.GetUserOperations({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewAddress':
|
||||
if (!methods.NewAddress) {
|
||||
throw new Error('method not defined: NewAddress')
|
||||
} else {
|
||||
const error = Types.NewAddressRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.NewAddress({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'PayAddress':
|
||||
if (!methods.PayAddress) {
|
||||
throw new Error('method not defined: PayAddress')
|
||||
} else {
|
||||
const error = Types.PayAddressRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.PayAddress({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewInvoice':
|
||||
if (!methods.NewInvoice) {
|
||||
throw new Error('method not defined: NewInvoice')
|
||||
} else {
|
||||
const error = Types.NewInvoiceRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.NewInvoice({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'DecodeInvoice':
|
||||
if (!methods.DecodeInvoice) {
|
||||
throw new Error('method not defined: DecodeInvoice')
|
||||
} else {
|
||||
const error = Types.DecodeInvoiceRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.DecodeInvoice({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'PayInvoice':
|
||||
if (!methods.PayInvoice) {
|
||||
throw new Error('method not defined: PayInvoice')
|
||||
} else {
|
||||
const error = Types.PayInvoiceRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.PayInvoice({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'OpenChannel':
|
||||
if (!methods.OpenChannel) {
|
||||
throw new Error('method not defined: OpenChannel')
|
||||
} else {
|
||||
const error = Types.OpenChannelRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.OpenChannel({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetLnurlWithdrawLink':
|
||||
if (!methods.GetLnurlWithdrawLink) {
|
||||
throw new Error('method not defined: GetLnurlWithdrawLink')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetLnurlWithdrawLink({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetLnurlPayLink':
|
||||
if (!methods.GetLnurlPayLink) {
|
||||
throw new Error('method not defined: GetLnurlPayLink')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetLnurlPayLink({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetLNURLChannelLink':
|
||||
if (!methods.GetLNURLChannelLink) {
|
||||
throw new Error('method not defined: GetLNURLChannelLink')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetLNURLChannelLink({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error('unkown rpcName')
|
||||
}
|
||||
} catch (ex) { const e = ex as any; logger.error(e.message || e); callsMetrics.push({ ...opInfo, ...opStats, ...ctx, error: e.message }); responses.push({ status: 'ERROR', reason: e.message || e }) }
|
||||
}
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', responses })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...ctx }, ...callsMetrics])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
default: logger.error('unknown rpc call name from nostr event:' + req.rpcName)
|
||||
}
|
||||
}
|
||||
}
|
||||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
|
||||
import * as Types from './types.js'
|
||||
export type Logger = { log: (v: any) => void, error: (v: any) => void }
|
||||
type NostrResponse = (message: object) => void
|
||||
export type NostrRequest = {
|
||||
rpcName?: string
|
||||
params?: Record<string, string>
|
||||
query?: Record<string, string>
|
||||
body?: any
|
||||
authIdentifier?: string
|
||||
requestId?: string
|
||||
appId?: string
|
||||
}
|
||||
export type NostrOptions = {
|
||||
logger?: Logger
|
||||
throwErrors?: true
|
||||
metricsCallback: (metrics: Types.RequestMetric[]) => void
|
||||
NostrUserAuthGuard: (appId?: string, identifier?: string) => Promise<Types.UserContext>
|
||||
}
|
||||
const logErrorAndReturnResponse = (error: Error, response: string, res: NostrResponse, logger: Logger, metric: Types.RequestMetric, metricsCallback: (metrics: Types.RequestMetric[]) => void) => {
|
||||
logger.error(error.message || error); metricsCallback([{ ...metric, error: response }]); res({ status: 'ERROR', reason: response })
|
||||
}
|
||||
export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
||||
const logger = opts.logger || { log: console.log, error: console.error }
|
||||
return async (req: NostrRequest, res: NostrResponse, startString: string, startMs: number) => {
|
||||
const startTime = BigInt(startString)
|
||||
const info: Types.RequestInfo = { rpcName: req.rpcName || 'unkown', batch: false, nostr: true, batchSize: 0 }
|
||||
const stats: Types.RequestStats = { startMs, start: startTime, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
|
||||
let authCtx: Types.AuthContext = {}
|
||||
switch (req.rpcName) {
|
||||
case 'LinkNPubThroughToken':
|
||||
try {
|
||||
if (!methods.LinkNPubThroughToken) throw new Error('method: LinkNPubThroughToken is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.LinkNPubThroughTokenRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
await methods.LinkNPubThroughToken({ rpcName: 'LinkNPubThroughToken', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK' })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'UserHealth':
|
||||
try {
|
||||
if (!methods.UserHealth) throw new Error('method: UserHealth is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
await methods.UserHealth({ rpcName: 'UserHealth', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK' })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetUserInfo':
|
||||
try {
|
||||
if (!methods.GetUserInfo) throw new Error('method: GetUserInfo is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetUserInfo({ rpcName: 'GetUserInfo', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'AddProduct':
|
||||
try {
|
||||
if (!methods.AddProduct) throw new Error('method: AddProduct is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.AddProductRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.AddProduct({ rpcName: 'AddProduct', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'NewProductInvoice':
|
||||
try {
|
||||
if (!methods.NewProductInvoice) throw new Error('method: NewProductInvoice is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.NewProductInvoice({ rpcName: 'NewProductInvoice', ctx: authContext, query: req.query || {} })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetUserOperations':
|
||||
try {
|
||||
if (!methods.GetUserOperations) throw new Error('method: GetUserOperations is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.GetUserOperationsRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.GetUserOperations({ rpcName: 'GetUserOperations', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'NewAddress':
|
||||
try {
|
||||
if (!methods.NewAddress) throw new Error('method: NewAddress is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.NewAddressRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.NewAddress({ rpcName: 'NewAddress', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'PayAddress':
|
||||
try {
|
||||
if (!methods.PayAddress) throw new Error('method: PayAddress is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.PayAddressRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.PayAddress({ rpcName: 'PayAddress', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'NewInvoice':
|
||||
try {
|
||||
if (!methods.NewInvoice) throw new Error('method: NewInvoice is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.NewInvoiceRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.NewInvoice({ rpcName: 'NewInvoice', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'DecodeInvoice':
|
||||
try {
|
||||
if (!methods.DecodeInvoice) throw new Error('method: DecodeInvoice is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.DecodeInvoiceRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.DecodeInvoice({ rpcName: 'DecodeInvoice', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'PayInvoice':
|
||||
try {
|
||||
if (!methods.PayInvoice) throw new Error('method: PayInvoice is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.PayInvoiceRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.PayInvoice({ rpcName: 'PayInvoice', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'OpenChannel':
|
||||
try {
|
||||
if (!methods.OpenChannel) throw new Error('method: OpenChannel is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.OpenChannelRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.OpenChannel({ rpcName: 'OpenChannel', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLnurlWithdrawLink':
|
||||
try {
|
||||
if (!methods.GetLnurlWithdrawLink) throw new Error('method: GetLnurlWithdrawLink is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetLnurlWithdrawLink({ rpcName: 'GetLnurlWithdrawLink', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLnurlPayLink':
|
||||
try {
|
||||
if (!methods.GetLnurlPayLink) throw new Error('method: GetLnurlPayLink is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetLnurlPayLink({ rpcName: 'GetLnurlPayLink', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLNURLChannelLink':
|
||||
try {
|
||||
if (!methods.GetLNURLChannelLink) throw new Error('method: GetLNURLChannelLink is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetLNURLChannelLink({ rpcName: 'GetLNURLChannelLink', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLiveUserOperations':
|
||||
try {
|
||||
if (!methods.GetLiveUserOperations) throw new Error('method: GetLiveUserOperations is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
methods.GetLiveUserOperations({
|
||||
rpcName: 'GetLiveUserOperations', ctx: authContext, cb: (response, err) => {
|
||||
stats.handle = process.hrtime.bigint()
|
||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback) } else { res({ status: 'OK', ...response }); opts.metricsCallback([{ ...info, ...stats, ...authContext }]) }
|
||||
}
|
||||
})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetMigrationUpdate':
|
||||
try {
|
||||
if (!methods.GetMigrationUpdate) throw new Error('method: GetMigrationUpdate is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
methods.GetMigrationUpdate({
|
||||
rpcName: 'GetMigrationUpdate', ctx: authContext, cb: (response, err) => {
|
||||
stats.handle = process.hrtime.bigint()
|
||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback) } else { res({ status: 'OK', ...response }); opts.metricsCallback([{ ...info, ...stats, ...authContext }]) }
|
||||
}
|
||||
})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetHttpCreds':
|
||||
try {
|
||||
if (!methods.GetHttpCreds) throw new Error('method: GetHttpCreds is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
methods.GetHttpCreds({
|
||||
rpcName: 'GetHttpCreds', ctx: authContext, cb: (response, err) => {
|
||||
stats.handle = process.hrtime.bigint()
|
||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback) } else { res({ status: 'OK', ...response }); opts.metricsCallback([{ ...info, ...stats, ...authContext }]) }
|
||||
}
|
||||
})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'BatchUser':
|
||||
try {
|
||||
info.batch = true
|
||||
const requests = req.body.requests as Types.UserMethodInputs[]
|
||||
if (!Array.isArray(requests)) throw new Error('invalid body, is not an array')
|
||||
info.batchSize = requests.length
|
||||
if (requests.length > 10) throw new Error('too many requests in the batch')
|
||||
const ctx = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = ctx
|
||||
stats.validate = stats.guard
|
||||
const responses = []
|
||||
const callsMetrics: Types.RequestMetric[] = []
|
||||
for (let i = 0; i < requests.length; i++) {
|
||||
const operation = requests[i]
|
||||
const opInfo: Types.RequestInfo = { rpcName: operation.rpcName, batch: true, nostr: true, batchSize: 0 }
|
||||
const opStats: Types.RequestStats = { startMs, start: startTime, parse: stats.parse, guard: stats.guard, validate: 0n, handle: 0n }
|
||||
try {
|
||||
switch (operation.rpcName) {
|
||||
case 'LinkNPubThroughToken':
|
||||
if (!methods.LinkNPubThroughToken) {
|
||||
throw new Error('method not defined: LinkNPubThroughToken')
|
||||
} else {
|
||||
const error = Types.LinkNPubThroughTokenRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
await methods.LinkNPubThroughToken({ ...operation, ctx }); responses.push({ status: 'OK' })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'UserHealth':
|
||||
if (!methods.UserHealth) {
|
||||
throw new Error('method not defined: UserHealth')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
await methods.UserHealth({ ...operation, ctx }); responses.push({ status: 'OK' })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetUserInfo':
|
||||
if (!methods.GetUserInfo) {
|
||||
throw new Error('method not defined: GetUserInfo')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetUserInfo({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'AddProduct':
|
||||
if (!methods.AddProduct) {
|
||||
throw new Error('method not defined: AddProduct')
|
||||
} else {
|
||||
const error = Types.AddProductRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.AddProduct({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewProductInvoice':
|
||||
if (!methods.NewProductInvoice) {
|
||||
throw new Error('method not defined: NewProductInvoice')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.NewProductInvoice({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetUserOperations':
|
||||
if (!methods.GetUserOperations) {
|
||||
throw new Error('method not defined: GetUserOperations')
|
||||
} else {
|
||||
const error = Types.GetUserOperationsRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.GetUserOperations({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewAddress':
|
||||
if (!methods.NewAddress) {
|
||||
throw new Error('method not defined: NewAddress')
|
||||
} else {
|
||||
const error = Types.NewAddressRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.NewAddress({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'PayAddress':
|
||||
if (!methods.PayAddress) {
|
||||
throw new Error('method not defined: PayAddress')
|
||||
} else {
|
||||
const error = Types.PayAddressRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.PayAddress({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewInvoice':
|
||||
if (!methods.NewInvoice) {
|
||||
throw new Error('method not defined: NewInvoice')
|
||||
} else {
|
||||
const error = Types.NewInvoiceRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.NewInvoice({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'DecodeInvoice':
|
||||
if (!methods.DecodeInvoice) {
|
||||
throw new Error('method not defined: DecodeInvoice')
|
||||
} else {
|
||||
const error = Types.DecodeInvoiceRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.DecodeInvoice({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'PayInvoice':
|
||||
if (!methods.PayInvoice) {
|
||||
throw new Error('method not defined: PayInvoice')
|
||||
} else {
|
||||
const error = Types.PayInvoiceRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.PayInvoice({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'OpenChannel':
|
||||
if (!methods.OpenChannel) {
|
||||
throw new Error('method not defined: OpenChannel')
|
||||
} else {
|
||||
const error = Types.OpenChannelRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.OpenChannel({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetLnurlWithdrawLink':
|
||||
if (!methods.GetLnurlWithdrawLink) {
|
||||
throw new Error('method not defined: GetLnurlWithdrawLink')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetLnurlWithdrawLink({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetLnurlPayLink':
|
||||
if (!methods.GetLnurlPayLink) {
|
||||
throw new Error('method not defined: GetLnurlPayLink')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetLnurlPayLink({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetLNURLChannelLink':
|
||||
if (!methods.GetLNURLChannelLink) {
|
||||
throw new Error('method not defined: GetLNURLChannelLink')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetLNURLChannelLink({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error('unkown rpcName')
|
||||
}
|
||||
} catch (ex) { const e = ex as any; logger.error(e.message || e); callsMetrics.push({ ...opInfo, ...opStats, ...ctx, error: e.message }); responses.push({ status: 'ERROR', reason: e.message || e }) }
|
||||
}
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', responses })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...ctx }, ...callsMetrics])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
default: logger.error('unknown rpc call name from nostr event:' + req.rpcName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,128 +1,128 @@
|
|||
// @generated by protobuf-ts 2.8.1
|
||||
// @generated from protobuf file "chainnotifier.proto" (package "chainrpc", syntax proto3)
|
||||
// tslint:disable
|
||||
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
|
||||
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
|
||||
import { ChainNotifier } from "./chainnotifier.js";
|
||||
import type { BlockEpoch } from "./chainnotifier.js";
|
||||
import type { SpendEvent } from "./chainnotifier.js";
|
||||
import type { SpendRequest } from "./chainnotifier.js";
|
||||
import { stackIntercept } from "@protobuf-ts/runtime-rpc";
|
||||
import type { ConfEvent } from "./chainnotifier.js";
|
||||
import type { ConfRequest } from "./chainnotifier.js";
|
||||
import type { ServerStreamingCall } from "@protobuf-ts/runtime-rpc";
|
||||
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
|
||||
/**
|
||||
* ChainNotifier is a service that can be used to get information about the
|
||||
* chain backend by registering notifiers for chain events.
|
||||
*
|
||||
* @generated from protobuf service chainrpc.ChainNotifier
|
||||
*/
|
||||
export interface IChainNotifierClient {
|
||||
/**
|
||||
*
|
||||
* RegisterConfirmationsNtfn is a synchronous response-streaming RPC that
|
||||
* registers an intent for a client to be notified once a confirmation request
|
||||
* has reached its required number of confirmations on-chain.
|
||||
*
|
||||
* A confirmation request must have a valid output script. It is also possible
|
||||
* to give a transaction ID. If the transaction ID is not set, a notification
|
||||
* is sent once the output script confirms. If the transaction ID is also set,
|
||||
* a notification is sent once the output script confirms in the given
|
||||
* transaction.
|
||||
*
|
||||
* @generated from protobuf rpc: RegisterConfirmationsNtfn(chainrpc.ConfRequest) returns (stream chainrpc.ConfEvent);
|
||||
*/
|
||||
registerConfirmationsNtfn(input: ConfRequest, options?: RpcOptions): ServerStreamingCall<ConfRequest, ConfEvent>;
|
||||
/**
|
||||
*
|
||||
* RegisterSpendNtfn is a synchronous response-streaming RPC that registers an
|
||||
* intent for a client to be notification once a spend request has been spent
|
||||
* by a transaction that has confirmed on-chain.
|
||||
*
|
||||
* A client can specify whether the spend request should be for a particular
|
||||
* outpoint or for an output script by specifying a zero outpoint.
|
||||
*
|
||||
* @generated from protobuf rpc: RegisterSpendNtfn(chainrpc.SpendRequest) returns (stream chainrpc.SpendEvent);
|
||||
*/
|
||||
registerSpendNtfn(input: SpendRequest, options?: RpcOptions): ServerStreamingCall<SpendRequest, SpendEvent>;
|
||||
/**
|
||||
*
|
||||
* RegisterBlockEpochNtfn is a synchronous response-streaming RPC that
|
||||
* registers an intent for a client to be notified of blocks in the chain. The
|
||||
* stream will return a hash and height tuple of a block for each new/stale
|
||||
* block in the chain. It is the client's responsibility to determine whether
|
||||
* the tuple returned is for a new or stale block in the chain.
|
||||
*
|
||||
* A client can also request a historical backlog of blocks from a particular
|
||||
* point. This allows clients to be idempotent by ensuring that they do not
|
||||
* missing processing a single block within the chain.
|
||||
*
|
||||
* @generated from protobuf rpc: RegisterBlockEpochNtfn(chainrpc.BlockEpoch) returns (stream chainrpc.BlockEpoch);
|
||||
*/
|
||||
registerBlockEpochNtfn(input: BlockEpoch, options?: RpcOptions): ServerStreamingCall<BlockEpoch, BlockEpoch>;
|
||||
}
|
||||
/**
|
||||
* ChainNotifier is a service that can be used to get information about the
|
||||
* chain backend by registering notifiers for chain events.
|
||||
*
|
||||
* @generated from protobuf service chainrpc.ChainNotifier
|
||||
*/
|
||||
export class ChainNotifierClient implements IChainNotifierClient, ServiceInfo {
|
||||
typeName = ChainNotifier.typeName;
|
||||
methods = ChainNotifier.methods;
|
||||
options = ChainNotifier.options;
|
||||
constructor(private readonly _transport: RpcTransport) {
|
||||
}
|
||||
/**
|
||||
*
|
||||
* RegisterConfirmationsNtfn is a synchronous response-streaming RPC that
|
||||
* registers an intent for a client to be notified once a confirmation request
|
||||
* has reached its required number of confirmations on-chain.
|
||||
*
|
||||
* A confirmation request must have a valid output script. It is also possible
|
||||
* to give a transaction ID. If the transaction ID is not set, a notification
|
||||
* is sent once the output script confirms. If the transaction ID is also set,
|
||||
* a notification is sent once the output script confirms in the given
|
||||
* transaction.
|
||||
*
|
||||
* @generated from protobuf rpc: RegisterConfirmationsNtfn(chainrpc.ConfRequest) returns (stream chainrpc.ConfEvent);
|
||||
*/
|
||||
registerConfirmationsNtfn(input: ConfRequest, options?: RpcOptions): ServerStreamingCall<ConfRequest, ConfEvent> {
|
||||
const method = this.methods[0], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<ConfRequest, ConfEvent>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* RegisterSpendNtfn is a synchronous response-streaming RPC that registers an
|
||||
* intent for a client to be notification once a spend request has been spent
|
||||
* by a transaction that has confirmed on-chain.
|
||||
*
|
||||
* A client can specify whether the spend request should be for a particular
|
||||
* outpoint or for an output script by specifying a zero outpoint.
|
||||
*
|
||||
* @generated from protobuf rpc: RegisterSpendNtfn(chainrpc.SpendRequest) returns (stream chainrpc.SpendEvent);
|
||||
*/
|
||||
registerSpendNtfn(input: SpendRequest, options?: RpcOptions): ServerStreamingCall<SpendRequest, SpendEvent> {
|
||||
const method = this.methods[1], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SpendRequest, SpendEvent>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* RegisterBlockEpochNtfn is a synchronous response-streaming RPC that
|
||||
* registers an intent for a client to be notified of blocks in the chain. The
|
||||
* stream will return a hash and height tuple of a block for each new/stale
|
||||
* block in the chain. It is the client's responsibility to determine whether
|
||||
* the tuple returned is for a new or stale block in the chain.
|
||||
*
|
||||
* A client can also request a historical backlog of blocks from a particular
|
||||
* point. This allows clients to be idempotent by ensuring that they do not
|
||||
* missing processing a single block within the chain.
|
||||
*
|
||||
* @generated from protobuf rpc: RegisterBlockEpochNtfn(chainrpc.BlockEpoch) returns (stream chainrpc.BlockEpoch);
|
||||
*/
|
||||
registerBlockEpochNtfn(input: BlockEpoch, options?: RpcOptions): ServerStreamingCall<BlockEpoch, BlockEpoch> {
|
||||
const method = this.methods[2], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<BlockEpoch, BlockEpoch>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
}
|
||||
// @generated by protobuf-ts 2.8.1
|
||||
// @generated from protobuf file "chainnotifier.proto" (package "chainrpc", syntax proto3)
|
||||
// tslint:disable
|
||||
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
|
||||
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
|
||||
import { ChainNotifier } from "./chainnotifier.js";
|
||||
import type { BlockEpoch } from "./chainnotifier.js";
|
||||
import type { SpendEvent } from "./chainnotifier.js";
|
||||
import type { SpendRequest } from "./chainnotifier.js";
|
||||
import { stackIntercept } from "@protobuf-ts/runtime-rpc";
|
||||
import type { ConfEvent } from "./chainnotifier.js";
|
||||
import type { ConfRequest } from "./chainnotifier.js";
|
||||
import type { ServerStreamingCall } from "@protobuf-ts/runtime-rpc";
|
||||
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
|
||||
/**
|
||||
* ChainNotifier is a service that can be used to get information about the
|
||||
* chain backend by registering notifiers for chain events.
|
||||
*
|
||||
* @generated from protobuf service chainrpc.ChainNotifier
|
||||
*/
|
||||
export interface IChainNotifierClient {
|
||||
/**
|
||||
*
|
||||
* RegisterConfirmationsNtfn is a synchronous response-streaming RPC that
|
||||
* registers an intent for a client to be notified once a confirmation request
|
||||
* has reached its required number of confirmations on-chain.
|
||||
*
|
||||
* A confirmation request must have a valid output script. It is also possible
|
||||
* to give a transaction ID. If the transaction ID is not set, a notification
|
||||
* is sent once the output script confirms. If the transaction ID is also set,
|
||||
* a notification is sent once the output script confirms in the given
|
||||
* transaction.
|
||||
*
|
||||
* @generated from protobuf rpc: RegisterConfirmationsNtfn(chainrpc.ConfRequest) returns (stream chainrpc.ConfEvent);
|
||||
*/
|
||||
registerConfirmationsNtfn(input: ConfRequest, options?: RpcOptions): ServerStreamingCall<ConfRequest, ConfEvent>;
|
||||
/**
|
||||
*
|
||||
* RegisterSpendNtfn is a synchronous response-streaming RPC that registers an
|
||||
* intent for a client to be notification once a spend request has been spent
|
||||
* by a transaction that has confirmed on-chain.
|
||||
*
|
||||
* A client can specify whether the spend request should be for a particular
|
||||
* outpoint or for an output script by specifying a zero outpoint.
|
||||
*
|
||||
* @generated from protobuf rpc: RegisterSpendNtfn(chainrpc.SpendRequest) returns (stream chainrpc.SpendEvent);
|
||||
*/
|
||||
registerSpendNtfn(input: SpendRequest, options?: RpcOptions): ServerStreamingCall<SpendRequest, SpendEvent>;
|
||||
/**
|
||||
*
|
||||
* RegisterBlockEpochNtfn is a synchronous response-streaming RPC that
|
||||
* registers an intent for a client to be notified of blocks in the chain. The
|
||||
* stream will return a hash and height tuple of a block for each new/stale
|
||||
* block in the chain. It is the client's responsibility to determine whether
|
||||
* the tuple returned is for a new or stale block in the chain.
|
||||
*
|
||||
* A client can also request a historical backlog of blocks from a particular
|
||||
* point. This allows clients to be idempotent by ensuring that they do not
|
||||
* missing processing a single block within the chain.
|
||||
*
|
||||
* @generated from protobuf rpc: RegisterBlockEpochNtfn(chainrpc.BlockEpoch) returns (stream chainrpc.BlockEpoch);
|
||||
*/
|
||||
registerBlockEpochNtfn(input: BlockEpoch, options?: RpcOptions): ServerStreamingCall<BlockEpoch, BlockEpoch>;
|
||||
}
|
||||
/**
|
||||
* ChainNotifier is a service that can be used to get information about the
|
||||
* chain backend by registering notifiers for chain events.
|
||||
*
|
||||
* @generated from protobuf service chainrpc.ChainNotifier
|
||||
*/
|
||||
export class ChainNotifierClient implements IChainNotifierClient, ServiceInfo {
|
||||
typeName = ChainNotifier.typeName;
|
||||
methods = ChainNotifier.methods;
|
||||
options = ChainNotifier.options;
|
||||
constructor(private readonly _transport: RpcTransport) {
|
||||
}
|
||||
/**
|
||||
*
|
||||
* RegisterConfirmationsNtfn is a synchronous response-streaming RPC that
|
||||
* registers an intent for a client to be notified once a confirmation request
|
||||
* has reached its required number of confirmations on-chain.
|
||||
*
|
||||
* A confirmation request must have a valid output script. It is also possible
|
||||
* to give a transaction ID. If the transaction ID is not set, a notification
|
||||
* is sent once the output script confirms. If the transaction ID is also set,
|
||||
* a notification is sent once the output script confirms in the given
|
||||
* transaction.
|
||||
*
|
||||
* @generated from protobuf rpc: RegisterConfirmationsNtfn(chainrpc.ConfRequest) returns (stream chainrpc.ConfEvent);
|
||||
*/
|
||||
registerConfirmationsNtfn(input: ConfRequest, options?: RpcOptions): ServerStreamingCall<ConfRequest, ConfEvent> {
|
||||
const method = this.methods[0], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<ConfRequest, ConfEvent>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* RegisterSpendNtfn is a synchronous response-streaming RPC that registers an
|
||||
* intent for a client to be notification once a spend request has been spent
|
||||
* by a transaction that has confirmed on-chain.
|
||||
*
|
||||
* A client can specify whether the spend request should be for a particular
|
||||
* outpoint or for an output script by specifying a zero outpoint.
|
||||
*
|
||||
* @generated from protobuf rpc: RegisterSpendNtfn(chainrpc.SpendRequest) returns (stream chainrpc.SpendEvent);
|
||||
*/
|
||||
registerSpendNtfn(input: SpendRequest, options?: RpcOptions): ServerStreamingCall<SpendRequest, SpendEvent> {
|
||||
const method = this.methods[1], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SpendRequest, SpendEvent>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* RegisterBlockEpochNtfn is a synchronous response-streaming RPC that
|
||||
* registers an intent for a client to be notified of blocks in the chain. The
|
||||
* stream will return a hash and height tuple of a block for each new/stale
|
||||
* block in the chain. It is the client's responsibility to determine whether
|
||||
* the tuple returned is for a new or stale block in the chain.
|
||||
*
|
||||
* A client can also request a historical backlog of blocks from a particular
|
||||
* point. This allows clients to be idempotent by ensuring that they do not
|
||||
* missing processing a single block within the chain.
|
||||
*
|
||||
* @generated from protobuf rpc: RegisterBlockEpochNtfn(chainrpc.BlockEpoch) returns (stream chainrpc.BlockEpoch);
|
||||
*/
|
||||
registerBlockEpochNtfn(input: BlockEpoch, options?: RpcOptions): ServerStreamingCall<BlockEpoch, BlockEpoch> {
|
||||
const method = this.methods[2], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<BlockEpoch, BlockEpoch>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,139 +1,139 @@
|
|||
// @generated by protobuf-ts 2.8.1
|
||||
// @generated from protobuf file "invoices.proto" (package "invoicesrpc", syntax proto3)
|
||||
// tslint:disable
|
||||
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
|
||||
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
|
||||
import { Invoices } from "./invoices.js";
|
||||
import type { LookupInvoiceMsg } from "./invoices.js";
|
||||
import type { SettleInvoiceResp } from "./invoices.js";
|
||||
import type { SettleInvoiceMsg } from "./invoices.js";
|
||||
import type { AddHoldInvoiceResp } from "./invoices.js";
|
||||
import type { AddHoldInvoiceRequest } from "./invoices.js";
|
||||
import type { CancelInvoiceResp } from "./invoices.js";
|
||||
import type { CancelInvoiceMsg } from "./invoices.js";
|
||||
import type { UnaryCall } from "@protobuf-ts/runtime-rpc";
|
||||
import { stackIntercept } from "@protobuf-ts/runtime-rpc";
|
||||
import type { Invoice } from "./lightning.js";
|
||||
import type { SubscribeSingleInvoiceRequest } from "./invoices.js";
|
||||
import type { ServerStreamingCall } from "@protobuf-ts/runtime-rpc";
|
||||
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
|
||||
/**
|
||||
* Invoices is a service that can be used to create, accept, settle and cancel
|
||||
* invoices.
|
||||
*
|
||||
* @generated from protobuf service invoicesrpc.Invoices
|
||||
*/
|
||||
export interface IInvoicesClient {
|
||||
/**
|
||||
*
|
||||
* SubscribeSingleInvoice returns a uni-directional stream (server -> client)
|
||||
* to notify the client of state transitions of the specified invoice.
|
||||
* Initially the current invoice state is always sent out.
|
||||
*
|
||||
* @generated from protobuf rpc: SubscribeSingleInvoice(invoicesrpc.SubscribeSingleInvoiceRequest) returns (stream lnrpc.Invoice);
|
||||
*/
|
||||
subscribeSingleInvoice(input: SubscribeSingleInvoiceRequest, options?: RpcOptions): ServerStreamingCall<SubscribeSingleInvoiceRequest, Invoice>;
|
||||
/**
|
||||
*
|
||||
* CancelInvoice cancels a currently open invoice. If the invoice is already
|
||||
* canceled, this call will succeed. If the invoice is already settled, it will
|
||||
* fail.
|
||||
*
|
||||
* @generated from protobuf rpc: CancelInvoice(invoicesrpc.CancelInvoiceMsg) returns (invoicesrpc.CancelInvoiceResp);
|
||||
*/
|
||||
cancelInvoice(input: CancelInvoiceMsg, options?: RpcOptions): UnaryCall<CancelInvoiceMsg, CancelInvoiceResp>;
|
||||
/**
|
||||
*
|
||||
* AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
|
||||
* supplied in the request.
|
||||
*
|
||||
* @generated from protobuf rpc: AddHoldInvoice(invoicesrpc.AddHoldInvoiceRequest) returns (invoicesrpc.AddHoldInvoiceResp);
|
||||
*/
|
||||
addHoldInvoice(input: AddHoldInvoiceRequest, options?: RpcOptions): UnaryCall<AddHoldInvoiceRequest, AddHoldInvoiceResp>;
|
||||
/**
|
||||
*
|
||||
* SettleInvoice settles an accepted invoice. If the invoice is already
|
||||
* settled, this call will succeed.
|
||||
*
|
||||
* @generated from protobuf rpc: SettleInvoice(invoicesrpc.SettleInvoiceMsg) returns (invoicesrpc.SettleInvoiceResp);
|
||||
*/
|
||||
settleInvoice(input: SettleInvoiceMsg, options?: RpcOptions): UnaryCall<SettleInvoiceMsg, SettleInvoiceResp>;
|
||||
/**
|
||||
*
|
||||
* LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced
|
||||
* using either its payment hash, payment address, or set ID.
|
||||
*
|
||||
* @generated from protobuf rpc: LookupInvoiceV2(invoicesrpc.LookupInvoiceMsg) returns (lnrpc.Invoice);
|
||||
*/
|
||||
lookupInvoiceV2(input: LookupInvoiceMsg, options?: RpcOptions): UnaryCall<LookupInvoiceMsg, Invoice>;
|
||||
}
|
||||
/**
|
||||
* Invoices is a service that can be used to create, accept, settle and cancel
|
||||
* invoices.
|
||||
*
|
||||
* @generated from protobuf service invoicesrpc.Invoices
|
||||
*/
|
||||
export class InvoicesClient implements IInvoicesClient, ServiceInfo {
|
||||
typeName = Invoices.typeName;
|
||||
methods = Invoices.methods;
|
||||
options = Invoices.options;
|
||||
constructor(private readonly _transport: RpcTransport) {
|
||||
}
|
||||
/**
|
||||
*
|
||||
* SubscribeSingleInvoice returns a uni-directional stream (server -> client)
|
||||
* to notify the client of state transitions of the specified invoice.
|
||||
* Initially the current invoice state is always sent out.
|
||||
*
|
||||
* @generated from protobuf rpc: SubscribeSingleInvoice(invoicesrpc.SubscribeSingleInvoiceRequest) returns (stream lnrpc.Invoice);
|
||||
*/
|
||||
subscribeSingleInvoice(input: SubscribeSingleInvoiceRequest, options?: RpcOptions): ServerStreamingCall<SubscribeSingleInvoiceRequest, Invoice> {
|
||||
const method = this.methods[0], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SubscribeSingleInvoiceRequest, Invoice>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* CancelInvoice cancels a currently open invoice. If the invoice is already
|
||||
* canceled, this call will succeed. If the invoice is already settled, it will
|
||||
* fail.
|
||||
*
|
||||
* @generated from protobuf rpc: CancelInvoice(invoicesrpc.CancelInvoiceMsg) returns (invoicesrpc.CancelInvoiceResp);
|
||||
*/
|
||||
cancelInvoice(input: CancelInvoiceMsg, options?: RpcOptions): UnaryCall<CancelInvoiceMsg, CancelInvoiceResp> {
|
||||
const method = this.methods[1], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<CancelInvoiceMsg, CancelInvoiceResp>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
|
||||
* supplied in the request.
|
||||
*
|
||||
* @generated from protobuf rpc: AddHoldInvoice(invoicesrpc.AddHoldInvoiceRequest) returns (invoicesrpc.AddHoldInvoiceResp);
|
||||
*/
|
||||
addHoldInvoice(input: AddHoldInvoiceRequest, options?: RpcOptions): UnaryCall<AddHoldInvoiceRequest, AddHoldInvoiceResp> {
|
||||
const method = this.methods[2], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<AddHoldInvoiceRequest, AddHoldInvoiceResp>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* SettleInvoice settles an accepted invoice. If the invoice is already
|
||||
* settled, this call will succeed.
|
||||
*
|
||||
* @generated from protobuf rpc: SettleInvoice(invoicesrpc.SettleInvoiceMsg) returns (invoicesrpc.SettleInvoiceResp);
|
||||
*/
|
||||
settleInvoice(input: SettleInvoiceMsg, options?: RpcOptions): UnaryCall<SettleInvoiceMsg, SettleInvoiceResp> {
|
||||
const method = this.methods[3], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SettleInvoiceMsg, SettleInvoiceResp>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced
|
||||
* using either its payment hash, payment address, or set ID.
|
||||
*
|
||||
* @generated from protobuf rpc: LookupInvoiceV2(invoicesrpc.LookupInvoiceMsg) returns (lnrpc.Invoice);
|
||||
*/
|
||||
lookupInvoiceV2(input: LookupInvoiceMsg, options?: RpcOptions): UnaryCall<LookupInvoiceMsg, Invoice> {
|
||||
const method = this.methods[4], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<LookupInvoiceMsg, Invoice>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
}
|
||||
// @generated by protobuf-ts 2.8.1
|
||||
// @generated from protobuf file "invoices.proto" (package "invoicesrpc", syntax proto3)
|
||||
// tslint:disable
|
||||
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
|
||||
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
|
||||
import { Invoices } from "./invoices.js";
|
||||
import type { LookupInvoiceMsg } from "./invoices.js";
|
||||
import type { SettleInvoiceResp } from "./invoices.js";
|
||||
import type { SettleInvoiceMsg } from "./invoices.js";
|
||||
import type { AddHoldInvoiceResp } from "./invoices.js";
|
||||
import type { AddHoldInvoiceRequest } from "./invoices.js";
|
||||
import type { CancelInvoiceResp } from "./invoices.js";
|
||||
import type { CancelInvoiceMsg } from "./invoices.js";
|
||||
import type { UnaryCall } from "@protobuf-ts/runtime-rpc";
|
||||
import { stackIntercept } from "@protobuf-ts/runtime-rpc";
|
||||
import type { Invoice } from "./lightning.js";
|
||||
import type { SubscribeSingleInvoiceRequest } from "./invoices.js";
|
||||
import type { ServerStreamingCall } from "@protobuf-ts/runtime-rpc";
|
||||
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
|
||||
/**
|
||||
* Invoices is a service that can be used to create, accept, settle and cancel
|
||||
* invoices.
|
||||
*
|
||||
* @generated from protobuf service invoicesrpc.Invoices
|
||||
*/
|
||||
export interface IInvoicesClient {
|
||||
/**
|
||||
*
|
||||
* SubscribeSingleInvoice returns a uni-directional stream (server -> client)
|
||||
* to notify the client of state transitions of the specified invoice.
|
||||
* Initially the current invoice state is always sent out.
|
||||
*
|
||||
* @generated from protobuf rpc: SubscribeSingleInvoice(invoicesrpc.SubscribeSingleInvoiceRequest) returns (stream lnrpc.Invoice);
|
||||
*/
|
||||
subscribeSingleInvoice(input: SubscribeSingleInvoiceRequest, options?: RpcOptions): ServerStreamingCall<SubscribeSingleInvoiceRequest, Invoice>;
|
||||
/**
|
||||
*
|
||||
* CancelInvoice cancels a currently open invoice. If the invoice is already
|
||||
* canceled, this call will succeed. If the invoice is already settled, it will
|
||||
* fail.
|
||||
*
|
||||
* @generated from protobuf rpc: CancelInvoice(invoicesrpc.CancelInvoiceMsg) returns (invoicesrpc.CancelInvoiceResp);
|
||||
*/
|
||||
cancelInvoice(input: CancelInvoiceMsg, options?: RpcOptions): UnaryCall<CancelInvoiceMsg, CancelInvoiceResp>;
|
||||
/**
|
||||
*
|
||||
* AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
|
||||
* supplied in the request.
|
||||
*
|
||||
* @generated from protobuf rpc: AddHoldInvoice(invoicesrpc.AddHoldInvoiceRequest) returns (invoicesrpc.AddHoldInvoiceResp);
|
||||
*/
|
||||
addHoldInvoice(input: AddHoldInvoiceRequest, options?: RpcOptions): UnaryCall<AddHoldInvoiceRequest, AddHoldInvoiceResp>;
|
||||
/**
|
||||
*
|
||||
* SettleInvoice settles an accepted invoice. If the invoice is already
|
||||
* settled, this call will succeed.
|
||||
*
|
||||
* @generated from protobuf rpc: SettleInvoice(invoicesrpc.SettleInvoiceMsg) returns (invoicesrpc.SettleInvoiceResp);
|
||||
*/
|
||||
settleInvoice(input: SettleInvoiceMsg, options?: RpcOptions): UnaryCall<SettleInvoiceMsg, SettleInvoiceResp>;
|
||||
/**
|
||||
*
|
||||
* LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced
|
||||
* using either its payment hash, payment address, or set ID.
|
||||
*
|
||||
* @generated from protobuf rpc: LookupInvoiceV2(invoicesrpc.LookupInvoiceMsg) returns (lnrpc.Invoice);
|
||||
*/
|
||||
lookupInvoiceV2(input: LookupInvoiceMsg, options?: RpcOptions): UnaryCall<LookupInvoiceMsg, Invoice>;
|
||||
}
|
||||
/**
|
||||
* Invoices is a service that can be used to create, accept, settle and cancel
|
||||
* invoices.
|
||||
*
|
||||
* @generated from protobuf service invoicesrpc.Invoices
|
||||
*/
|
||||
export class InvoicesClient implements IInvoicesClient, ServiceInfo {
|
||||
typeName = Invoices.typeName;
|
||||
methods = Invoices.methods;
|
||||
options = Invoices.options;
|
||||
constructor(private readonly _transport: RpcTransport) {
|
||||
}
|
||||
/**
|
||||
*
|
||||
* SubscribeSingleInvoice returns a uni-directional stream (server -> client)
|
||||
* to notify the client of state transitions of the specified invoice.
|
||||
* Initially the current invoice state is always sent out.
|
||||
*
|
||||
* @generated from protobuf rpc: SubscribeSingleInvoice(invoicesrpc.SubscribeSingleInvoiceRequest) returns (stream lnrpc.Invoice);
|
||||
*/
|
||||
subscribeSingleInvoice(input: SubscribeSingleInvoiceRequest, options?: RpcOptions): ServerStreamingCall<SubscribeSingleInvoiceRequest, Invoice> {
|
||||
const method = this.methods[0], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SubscribeSingleInvoiceRequest, Invoice>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* CancelInvoice cancels a currently open invoice. If the invoice is already
|
||||
* canceled, this call will succeed. If the invoice is already settled, it will
|
||||
* fail.
|
||||
*
|
||||
* @generated from protobuf rpc: CancelInvoice(invoicesrpc.CancelInvoiceMsg) returns (invoicesrpc.CancelInvoiceResp);
|
||||
*/
|
||||
cancelInvoice(input: CancelInvoiceMsg, options?: RpcOptions): UnaryCall<CancelInvoiceMsg, CancelInvoiceResp> {
|
||||
const method = this.methods[1], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<CancelInvoiceMsg, CancelInvoiceResp>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
|
||||
* supplied in the request.
|
||||
*
|
||||
* @generated from protobuf rpc: AddHoldInvoice(invoicesrpc.AddHoldInvoiceRequest) returns (invoicesrpc.AddHoldInvoiceResp);
|
||||
*/
|
||||
addHoldInvoice(input: AddHoldInvoiceRequest, options?: RpcOptions): UnaryCall<AddHoldInvoiceRequest, AddHoldInvoiceResp> {
|
||||
const method = this.methods[2], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<AddHoldInvoiceRequest, AddHoldInvoiceResp>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* SettleInvoice settles an accepted invoice. If the invoice is already
|
||||
* settled, this call will succeed.
|
||||
*
|
||||
* @generated from protobuf rpc: SettleInvoice(invoicesrpc.SettleInvoiceMsg) returns (invoicesrpc.SettleInvoiceResp);
|
||||
*/
|
||||
settleInvoice(input: SettleInvoiceMsg, options?: RpcOptions): UnaryCall<SettleInvoiceMsg, SettleInvoiceResp> {
|
||||
const method = this.methods[3], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SettleInvoiceMsg, SettleInvoiceResp>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced
|
||||
* using either its payment hash, payment address, or set ID.
|
||||
*
|
||||
* @generated from protobuf rpc: LookupInvoiceV2(invoicesrpc.LookupInvoiceMsg) returns (lnrpc.Invoice);
|
||||
*/
|
||||
lookupInvoiceV2(input: LookupInvoiceMsg, options?: RpcOptions): UnaryCall<LookupInvoiceMsg, Invoice> {
|
||||
const method = this.methods[4], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<LookupInvoiceMsg, Invoice>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
41990
proto/lnd/lightning.ts
41990
proto/lnd/lightning.ts
File diff suppressed because it is too large
Load diff
|
|
@ -1,446 +1,446 @@
|
|||
// @generated by protobuf-ts 2.8.1
|
||||
// @generated from protobuf file "router.proto" (package "routerrpc", syntax proto3)
|
||||
// tslint:disable
|
||||
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
|
||||
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
|
||||
import { Router } from "./router.js";
|
||||
import type { UpdateChanStatusResponse } from "./router.js";
|
||||
import type { UpdateChanStatusRequest } from "./router.js";
|
||||
import type { ForwardHtlcInterceptRequest } from "./router.js";
|
||||
import type { ForwardHtlcInterceptResponse } from "./router.js";
|
||||
import type { DuplexStreamingCall } from "@protobuf-ts/runtime-rpc";
|
||||
import type { PaymentStatus } from "./router.js";
|
||||
import type { HtlcEvent } from "./router.js";
|
||||
import type { SubscribeHtlcEventsRequest } from "./router.js";
|
||||
import type { BuildRouteResponse } from "./router.js";
|
||||
import type { BuildRouteRequest } from "./router.js";
|
||||
import type { QueryProbabilityResponse } from "./router.js";
|
||||
import type { QueryProbabilityRequest } from "./router.js";
|
||||
import type { SetMissionControlConfigResponse } from "./router.js";
|
||||
import type { SetMissionControlConfigRequest } from "./router.js";
|
||||
import type { GetMissionControlConfigResponse } from "./router.js";
|
||||
import type { GetMissionControlConfigRequest } from "./router.js";
|
||||
import type { XImportMissionControlResponse } from "./router.js";
|
||||
import type { XImportMissionControlRequest } from "./router.js";
|
||||
import type { QueryMissionControlResponse } from "./router.js";
|
||||
import type { QueryMissionControlRequest } from "./router.js";
|
||||
import type { ResetMissionControlResponse } from "./router.js";
|
||||
import type { ResetMissionControlRequest } from "./router.js";
|
||||
import type { HTLCAttempt } from "./lightning.js";
|
||||
import type { SendToRouteResponse } from "./router.js";
|
||||
import type { SendToRouteRequest } from "./router.js";
|
||||
import type { RouteFeeResponse } from "./router.js";
|
||||
import type { RouteFeeRequest } from "./router.js";
|
||||
import type { UnaryCall } from "@protobuf-ts/runtime-rpc";
|
||||
import type { TrackPaymentsRequest } from "./router.js";
|
||||
import type { TrackPaymentRequest } from "./router.js";
|
||||
import { stackIntercept } from "@protobuf-ts/runtime-rpc";
|
||||
import type { Payment } from "./lightning.js";
|
||||
import type { SendPaymentRequest } from "./router.js";
|
||||
import type { ServerStreamingCall } from "@protobuf-ts/runtime-rpc";
|
||||
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
|
||||
/**
|
||||
* Router is a service that offers advanced interaction with the router
|
||||
* subsystem of the daemon.
|
||||
*
|
||||
* @generated from protobuf service routerrpc.Router
|
||||
*/
|
||||
export interface IRouterClient {
|
||||
/**
|
||||
*
|
||||
* SendPaymentV2 attempts to route a payment described by the passed
|
||||
* PaymentRequest to the final destination. The call returns a stream of
|
||||
* payment updates.
|
||||
*
|
||||
* @generated from protobuf rpc: SendPaymentV2(routerrpc.SendPaymentRequest) returns (stream lnrpc.Payment);
|
||||
*/
|
||||
sendPaymentV2(input: SendPaymentRequest, options?: RpcOptions): ServerStreamingCall<SendPaymentRequest, Payment>;
|
||||
/**
|
||||
*
|
||||
* TrackPaymentV2 returns an update stream for the payment identified by the
|
||||
* payment hash.
|
||||
*
|
||||
* @generated from protobuf rpc: TrackPaymentV2(routerrpc.TrackPaymentRequest) returns (stream lnrpc.Payment);
|
||||
*/
|
||||
trackPaymentV2(input: TrackPaymentRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentRequest, Payment>;
|
||||
/**
|
||||
*
|
||||
* TrackPayments returns an update stream for every payment that is not in a
|
||||
* terminal state. Note that if payments are in-flight while starting a new
|
||||
* subscription, the start of the payment stream could produce out-of-order
|
||||
* and/or duplicate events. In order to get updates for every in-flight
|
||||
* payment attempt make sure to subscribe to this method before initiating any
|
||||
* payments.
|
||||
*
|
||||
* @generated from protobuf rpc: TrackPayments(routerrpc.TrackPaymentsRequest) returns (stream lnrpc.Payment);
|
||||
*/
|
||||
trackPayments(input: TrackPaymentsRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentsRequest, Payment>;
|
||||
/**
|
||||
*
|
||||
* EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
|
||||
* may cost to send an HTLC to the target end destination.
|
||||
*
|
||||
* @generated from protobuf rpc: EstimateRouteFee(routerrpc.RouteFeeRequest) returns (routerrpc.RouteFeeResponse);
|
||||
*/
|
||||
estimateRouteFee(input: RouteFeeRequest, options?: RpcOptions): UnaryCall<RouteFeeRequest, RouteFeeResponse>;
|
||||
/**
|
||||
*
|
||||
* Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via
|
||||
* the specified route. This method differs from SendPayment in that it
|
||||
* allows users to specify a full route manually. This can be used for
|
||||
* things like rebalancing, and atomic swaps. It differs from the newer
|
||||
* SendToRouteV2 in that it doesn't return the full HTLC information.
|
||||
*
|
||||
* @deprecated
|
||||
* @generated from protobuf rpc: SendToRoute(routerrpc.SendToRouteRequest) returns (routerrpc.SendToRouteResponse);
|
||||
*/
|
||||
sendToRoute(input: SendToRouteRequest, options?: RpcOptions): UnaryCall<SendToRouteRequest, SendToRouteResponse>;
|
||||
/**
|
||||
*
|
||||
* SendToRouteV2 attempts to make a payment via the specified route. This
|
||||
* method differs from SendPayment in that it allows users to specify a full
|
||||
* route manually. This can be used for things like rebalancing, and atomic
|
||||
* swaps.
|
||||
*
|
||||
* @generated from protobuf rpc: SendToRouteV2(routerrpc.SendToRouteRequest) returns (lnrpc.HTLCAttempt);
|
||||
*/
|
||||
sendToRouteV2(input: SendToRouteRequest, options?: RpcOptions): UnaryCall<SendToRouteRequest, HTLCAttempt>;
|
||||
/**
|
||||
*
|
||||
* ResetMissionControl clears all mission control state and starts with a clean
|
||||
* slate.
|
||||
*
|
||||
* @generated from protobuf rpc: ResetMissionControl(routerrpc.ResetMissionControlRequest) returns (routerrpc.ResetMissionControlResponse);
|
||||
*/
|
||||
resetMissionControl(input: ResetMissionControlRequest, options?: RpcOptions): UnaryCall<ResetMissionControlRequest, ResetMissionControlResponse>;
|
||||
/**
|
||||
*
|
||||
* QueryMissionControl exposes the internal mission control state to callers.
|
||||
* It is a development feature.
|
||||
*
|
||||
* @generated from protobuf rpc: QueryMissionControl(routerrpc.QueryMissionControlRequest) returns (routerrpc.QueryMissionControlResponse);
|
||||
*/
|
||||
queryMissionControl(input: QueryMissionControlRequest, options?: RpcOptions): UnaryCall<QueryMissionControlRequest, QueryMissionControlResponse>;
|
||||
/**
|
||||
*
|
||||
* XImportMissionControl is an experimental API that imports the state provided
|
||||
* to the internal mission control's state, using all results which are more
|
||||
* recent than our existing values. These values will only be imported
|
||||
* in-memory, and will not be persisted across restarts.
|
||||
*
|
||||
* @generated from protobuf rpc: XImportMissionControl(routerrpc.XImportMissionControlRequest) returns (routerrpc.XImportMissionControlResponse);
|
||||
*/
|
||||
xImportMissionControl(input: XImportMissionControlRequest, options?: RpcOptions): UnaryCall<XImportMissionControlRequest, XImportMissionControlResponse>;
|
||||
/**
|
||||
*
|
||||
* GetMissionControlConfig returns mission control's current config.
|
||||
*
|
||||
* @generated from protobuf rpc: GetMissionControlConfig(routerrpc.GetMissionControlConfigRequest) returns (routerrpc.GetMissionControlConfigResponse);
|
||||
*/
|
||||
getMissionControlConfig(input: GetMissionControlConfigRequest, options?: RpcOptions): UnaryCall<GetMissionControlConfigRequest, GetMissionControlConfigResponse>;
|
||||
/**
|
||||
*
|
||||
* SetMissionControlConfig will set mission control's config, if the config
|
||||
* provided is valid.
|
||||
*
|
||||
* @generated from protobuf rpc: SetMissionControlConfig(routerrpc.SetMissionControlConfigRequest) returns (routerrpc.SetMissionControlConfigResponse);
|
||||
*/
|
||||
setMissionControlConfig(input: SetMissionControlConfigRequest, options?: RpcOptions): UnaryCall<SetMissionControlConfigRequest, SetMissionControlConfigResponse>;
|
||||
/**
|
||||
*
|
||||
* QueryProbability returns the current success probability estimate for a
|
||||
* given node pair and amount.
|
||||
*
|
||||
* @generated from protobuf rpc: QueryProbability(routerrpc.QueryProbabilityRequest) returns (routerrpc.QueryProbabilityResponse);
|
||||
*/
|
||||
queryProbability(input: QueryProbabilityRequest, options?: RpcOptions): UnaryCall<QueryProbabilityRequest, QueryProbabilityResponse>;
|
||||
/**
|
||||
*
|
||||
* BuildRoute builds a fully specified route based on a list of hop public
|
||||
* keys. It retrieves the relevant channel policies from the graph in order to
|
||||
* calculate the correct fees and time locks.
|
||||
*
|
||||
* @generated from protobuf rpc: BuildRoute(routerrpc.BuildRouteRequest) returns (routerrpc.BuildRouteResponse);
|
||||
*/
|
||||
buildRoute(input: BuildRouteRequest, options?: RpcOptions): UnaryCall<BuildRouteRequest, BuildRouteResponse>;
|
||||
/**
|
||||
*
|
||||
* SubscribeHtlcEvents creates a uni-directional stream from the server to
|
||||
* the client which delivers a stream of htlc events.
|
||||
*
|
||||
* @generated from protobuf rpc: SubscribeHtlcEvents(routerrpc.SubscribeHtlcEventsRequest) returns (stream routerrpc.HtlcEvent);
|
||||
*/
|
||||
subscribeHtlcEvents(input: SubscribeHtlcEventsRequest, options?: RpcOptions): ServerStreamingCall<SubscribeHtlcEventsRequest, HtlcEvent>;
|
||||
/**
|
||||
*
|
||||
* Deprecated, use SendPaymentV2. SendPayment attempts to route a payment
|
||||
* described by the passed PaymentRequest to the final destination. The call
|
||||
* returns a stream of payment status updates.
|
||||
*
|
||||
* @deprecated
|
||||
* @generated from protobuf rpc: SendPayment(routerrpc.SendPaymentRequest) returns (stream routerrpc.PaymentStatus);
|
||||
*/
|
||||
sendPayment(input: SendPaymentRequest, options?: RpcOptions): ServerStreamingCall<SendPaymentRequest, PaymentStatus>;
|
||||
/**
|
||||
*
|
||||
* Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for
|
||||
* the payment identified by the payment hash.
|
||||
*
|
||||
* @deprecated
|
||||
* @generated from protobuf rpc: TrackPayment(routerrpc.TrackPaymentRequest) returns (stream routerrpc.PaymentStatus);
|
||||
*/
|
||||
trackPayment(input: TrackPaymentRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentRequest, PaymentStatus>;
|
||||
/**
|
||||
* *
|
||||
* HtlcInterceptor dispatches a bi-directional streaming RPC in which
|
||||
* Forwarded HTLC requests are sent to the client and the client responds with
|
||||
* a boolean that tells LND if this htlc should be intercepted.
|
||||
* In case of interception, the htlc can be either settled, cancelled or
|
||||
* resumed later by using the ResolveHoldForward endpoint.
|
||||
*
|
||||
* @generated from protobuf rpc: HtlcInterceptor(stream routerrpc.ForwardHtlcInterceptResponse) returns (stream routerrpc.ForwardHtlcInterceptRequest);
|
||||
*/
|
||||
htlcInterceptor(options?: RpcOptions): DuplexStreamingCall<ForwardHtlcInterceptResponse, ForwardHtlcInterceptRequest>;
|
||||
/**
|
||||
*
|
||||
* UpdateChanStatus attempts to manually set the state of a channel
|
||||
* (enabled, disabled, or auto). A manual "disable" request will cause the
|
||||
* channel to stay disabled until a subsequent manual request of either
|
||||
* "enable" or "auto".
|
||||
*
|
||||
* @generated from protobuf rpc: UpdateChanStatus(routerrpc.UpdateChanStatusRequest) returns (routerrpc.UpdateChanStatusResponse);
|
||||
*/
|
||||
updateChanStatus(input: UpdateChanStatusRequest, options?: RpcOptions): UnaryCall<UpdateChanStatusRequest, UpdateChanStatusResponse>;
|
||||
}
|
||||
/**
|
||||
* Router is a service that offers advanced interaction with the router
|
||||
* subsystem of the daemon.
|
||||
*
|
||||
* @generated from protobuf service routerrpc.Router
|
||||
*/
|
||||
export class RouterClient implements IRouterClient, ServiceInfo {
|
||||
typeName = Router.typeName;
|
||||
methods = Router.methods;
|
||||
options = Router.options;
|
||||
constructor(private readonly _transport: RpcTransport) {
|
||||
}
|
||||
/**
|
||||
*
|
||||
* SendPaymentV2 attempts to route a payment described by the passed
|
||||
* PaymentRequest to the final destination. The call returns a stream of
|
||||
* payment updates.
|
||||
*
|
||||
* @generated from protobuf rpc: SendPaymentV2(routerrpc.SendPaymentRequest) returns (stream lnrpc.Payment);
|
||||
*/
|
||||
sendPaymentV2(input: SendPaymentRequest, options?: RpcOptions): ServerStreamingCall<SendPaymentRequest, Payment> {
|
||||
const method = this.methods[0], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SendPaymentRequest, Payment>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* TrackPaymentV2 returns an update stream for the payment identified by the
|
||||
* payment hash.
|
||||
*
|
||||
* @generated from protobuf rpc: TrackPaymentV2(routerrpc.TrackPaymentRequest) returns (stream lnrpc.Payment);
|
||||
*/
|
||||
trackPaymentV2(input: TrackPaymentRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentRequest, Payment> {
|
||||
const method = this.methods[1], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<TrackPaymentRequest, Payment>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* TrackPayments returns an update stream for every payment that is not in a
|
||||
* terminal state. Note that if payments are in-flight while starting a new
|
||||
* subscription, the start of the payment stream could produce out-of-order
|
||||
* and/or duplicate events. In order to get updates for every in-flight
|
||||
* payment attempt make sure to subscribe to this method before initiating any
|
||||
* payments.
|
||||
*
|
||||
* @generated from protobuf rpc: TrackPayments(routerrpc.TrackPaymentsRequest) returns (stream lnrpc.Payment);
|
||||
*/
|
||||
trackPayments(input: TrackPaymentsRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentsRequest, Payment> {
|
||||
const method = this.methods[2], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<TrackPaymentsRequest, Payment>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
|
||||
* may cost to send an HTLC to the target end destination.
|
||||
*
|
||||
* @generated from protobuf rpc: EstimateRouteFee(routerrpc.RouteFeeRequest) returns (routerrpc.RouteFeeResponse);
|
||||
*/
|
||||
estimateRouteFee(input: RouteFeeRequest, options?: RpcOptions): UnaryCall<RouteFeeRequest, RouteFeeResponse> {
|
||||
const method = this.methods[3], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<RouteFeeRequest, RouteFeeResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via
|
||||
* the specified route. This method differs from SendPayment in that it
|
||||
* allows users to specify a full route manually. This can be used for
|
||||
* things like rebalancing, and atomic swaps. It differs from the newer
|
||||
* SendToRouteV2 in that it doesn't return the full HTLC information.
|
||||
*
|
||||
* @deprecated
|
||||
* @generated from protobuf rpc: SendToRoute(routerrpc.SendToRouteRequest) returns (routerrpc.SendToRouteResponse);
|
||||
*/
|
||||
sendToRoute(input: SendToRouteRequest, options?: RpcOptions): UnaryCall<SendToRouteRequest, SendToRouteResponse> {
|
||||
const method = this.methods[4], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SendToRouteRequest, SendToRouteResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* SendToRouteV2 attempts to make a payment via the specified route. This
|
||||
* method differs from SendPayment in that it allows users to specify a full
|
||||
* route manually. This can be used for things like rebalancing, and atomic
|
||||
* swaps.
|
||||
*
|
||||
* @generated from protobuf rpc: SendToRouteV2(routerrpc.SendToRouteRequest) returns (lnrpc.HTLCAttempt);
|
||||
*/
|
||||
sendToRouteV2(input: SendToRouteRequest, options?: RpcOptions): UnaryCall<SendToRouteRequest, HTLCAttempt> {
|
||||
const method = this.methods[5], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SendToRouteRequest, HTLCAttempt>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* ResetMissionControl clears all mission control state and starts with a clean
|
||||
* slate.
|
||||
*
|
||||
* @generated from protobuf rpc: ResetMissionControl(routerrpc.ResetMissionControlRequest) returns (routerrpc.ResetMissionControlResponse);
|
||||
*/
|
||||
resetMissionControl(input: ResetMissionControlRequest, options?: RpcOptions): UnaryCall<ResetMissionControlRequest, ResetMissionControlResponse> {
|
||||
const method = this.methods[6], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<ResetMissionControlRequest, ResetMissionControlResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* QueryMissionControl exposes the internal mission control state to callers.
|
||||
* It is a development feature.
|
||||
*
|
||||
* @generated from protobuf rpc: QueryMissionControl(routerrpc.QueryMissionControlRequest) returns (routerrpc.QueryMissionControlResponse);
|
||||
*/
|
||||
queryMissionControl(input: QueryMissionControlRequest, options?: RpcOptions): UnaryCall<QueryMissionControlRequest, QueryMissionControlResponse> {
|
||||
const method = this.methods[7], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<QueryMissionControlRequest, QueryMissionControlResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* XImportMissionControl is an experimental API that imports the state provided
|
||||
* to the internal mission control's state, using all results which are more
|
||||
* recent than our existing values. These values will only be imported
|
||||
* in-memory, and will not be persisted across restarts.
|
||||
*
|
||||
* @generated from protobuf rpc: XImportMissionControl(routerrpc.XImportMissionControlRequest) returns (routerrpc.XImportMissionControlResponse);
|
||||
*/
|
||||
xImportMissionControl(input: XImportMissionControlRequest, options?: RpcOptions): UnaryCall<XImportMissionControlRequest, XImportMissionControlResponse> {
|
||||
const method = this.methods[8], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<XImportMissionControlRequest, XImportMissionControlResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* GetMissionControlConfig returns mission control's current config.
|
||||
*
|
||||
* @generated from protobuf rpc: GetMissionControlConfig(routerrpc.GetMissionControlConfigRequest) returns (routerrpc.GetMissionControlConfigResponse);
|
||||
*/
|
||||
getMissionControlConfig(input: GetMissionControlConfigRequest, options?: RpcOptions): UnaryCall<GetMissionControlConfigRequest, GetMissionControlConfigResponse> {
|
||||
const method = this.methods[9], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<GetMissionControlConfigRequest, GetMissionControlConfigResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* SetMissionControlConfig will set mission control's config, if the config
|
||||
* provided is valid.
|
||||
*
|
||||
* @generated from protobuf rpc: SetMissionControlConfig(routerrpc.SetMissionControlConfigRequest) returns (routerrpc.SetMissionControlConfigResponse);
|
||||
*/
|
||||
setMissionControlConfig(input: SetMissionControlConfigRequest, options?: RpcOptions): UnaryCall<SetMissionControlConfigRequest, SetMissionControlConfigResponse> {
|
||||
const method = this.methods[10], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SetMissionControlConfigRequest, SetMissionControlConfigResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* QueryProbability returns the current success probability estimate for a
|
||||
* given node pair and amount.
|
||||
*
|
||||
* @generated from protobuf rpc: QueryProbability(routerrpc.QueryProbabilityRequest) returns (routerrpc.QueryProbabilityResponse);
|
||||
*/
|
||||
queryProbability(input: QueryProbabilityRequest, options?: RpcOptions): UnaryCall<QueryProbabilityRequest, QueryProbabilityResponse> {
|
||||
const method = this.methods[11], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<QueryProbabilityRequest, QueryProbabilityResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* BuildRoute builds a fully specified route based on a list of hop public
|
||||
* keys. It retrieves the relevant channel policies from the graph in order to
|
||||
* calculate the correct fees and time locks.
|
||||
*
|
||||
* @generated from protobuf rpc: BuildRoute(routerrpc.BuildRouteRequest) returns (routerrpc.BuildRouteResponse);
|
||||
*/
|
||||
buildRoute(input: BuildRouteRequest, options?: RpcOptions): UnaryCall<BuildRouteRequest, BuildRouteResponse> {
|
||||
const method = this.methods[12], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<BuildRouteRequest, BuildRouteResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* SubscribeHtlcEvents creates a uni-directional stream from the server to
|
||||
* the client which delivers a stream of htlc events.
|
||||
*
|
||||
* @generated from protobuf rpc: SubscribeHtlcEvents(routerrpc.SubscribeHtlcEventsRequest) returns (stream routerrpc.HtlcEvent);
|
||||
*/
|
||||
subscribeHtlcEvents(input: SubscribeHtlcEventsRequest, options?: RpcOptions): ServerStreamingCall<SubscribeHtlcEventsRequest, HtlcEvent> {
|
||||
const method = this.methods[13], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SubscribeHtlcEventsRequest, HtlcEvent>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Deprecated, use SendPaymentV2. SendPayment attempts to route a payment
|
||||
* described by the passed PaymentRequest to the final destination. The call
|
||||
* returns a stream of payment status updates.
|
||||
*
|
||||
* @deprecated
|
||||
* @generated from protobuf rpc: SendPayment(routerrpc.SendPaymentRequest) returns (stream routerrpc.PaymentStatus);
|
||||
*/
|
||||
sendPayment(input: SendPaymentRequest, options?: RpcOptions): ServerStreamingCall<SendPaymentRequest, PaymentStatus> {
|
||||
const method = this.methods[14], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SendPaymentRequest, PaymentStatus>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for
|
||||
* the payment identified by the payment hash.
|
||||
*
|
||||
* @deprecated
|
||||
* @generated from protobuf rpc: TrackPayment(routerrpc.TrackPaymentRequest) returns (stream routerrpc.PaymentStatus);
|
||||
*/
|
||||
trackPayment(input: TrackPaymentRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentRequest, PaymentStatus> {
|
||||
const method = this.methods[15], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<TrackPaymentRequest, PaymentStatus>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* *
|
||||
* HtlcInterceptor dispatches a bi-directional streaming RPC in which
|
||||
* Forwarded HTLC requests are sent to the client and the client responds with
|
||||
* a boolean that tells LND if this htlc should be intercepted.
|
||||
* In case of interception, the htlc can be either settled, cancelled or
|
||||
* resumed later by using the ResolveHoldForward endpoint.
|
||||
*
|
||||
* @generated from protobuf rpc: HtlcInterceptor(stream routerrpc.ForwardHtlcInterceptResponse) returns (stream routerrpc.ForwardHtlcInterceptRequest);
|
||||
*/
|
||||
htlcInterceptor(options?: RpcOptions): DuplexStreamingCall<ForwardHtlcInterceptResponse, ForwardHtlcInterceptRequest> {
|
||||
const method = this.methods[16], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<ForwardHtlcInterceptResponse, ForwardHtlcInterceptRequest>("duplex", this._transport, method, opt);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* UpdateChanStatus attempts to manually set the state of a channel
|
||||
* (enabled, disabled, or auto). A manual "disable" request will cause the
|
||||
* channel to stay disabled until a subsequent manual request of either
|
||||
* "enable" or "auto".
|
||||
*
|
||||
* @generated from protobuf rpc: UpdateChanStatus(routerrpc.UpdateChanStatusRequest) returns (routerrpc.UpdateChanStatusResponse);
|
||||
*/
|
||||
updateChanStatus(input: UpdateChanStatusRequest, options?: RpcOptions): UnaryCall<UpdateChanStatusRequest, UpdateChanStatusResponse> {
|
||||
const method = this.methods[17], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<UpdateChanStatusRequest, UpdateChanStatusResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
}
|
||||
// @generated by protobuf-ts 2.8.1
|
||||
// @generated from protobuf file "router.proto" (package "routerrpc", syntax proto3)
|
||||
// tslint:disable
|
||||
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
|
||||
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
|
||||
import { Router } from "./router.js";
|
||||
import type { UpdateChanStatusResponse } from "./router.js";
|
||||
import type { UpdateChanStatusRequest } from "./router.js";
|
||||
import type { ForwardHtlcInterceptRequest } from "./router.js";
|
||||
import type { ForwardHtlcInterceptResponse } from "./router.js";
|
||||
import type { DuplexStreamingCall } from "@protobuf-ts/runtime-rpc";
|
||||
import type { PaymentStatus } from "./router.js";
|
||||
import type { HtlcEvent } from "./router.js";
|
||||
import type { SubscribeHtlcEventsRequest } from "./router.js";
|
||||
import type { BuildRouteResponse } from "./router.js";
|
||||
import type { BuildRouteRequest } from "./router.js";
|
||||
import type { QueryProbabilityResponse } from "./router.js";
|
||||
import type { QueryProbabilityRequest } from "./router.js";
|
||||
import type { SetMissionControlConfigResponse } from "./router.js";
|
||||
import type { SetMissionControlConfigRequest } from "./router.js";
|
||||
import type { GetMissionControlConfigResponse } from "./router.js";
|
||||
import type { GetMissionControlConfigRequest } from "./router.js";
|
||||
import type { XImportMissionControlResponse } from "./router.js";
|
||||
import type { XImportMissionControlRequest } from "./router.js";
|
||||
import type { QueryMissionControlResponse } from "./router.js";
|
||||
import type { QueryMissionControlRequest } from "./router.js";
|
||||
import type { ResetMissionControlResponse } from "./router.js";
|
||||
import type { ResetMissionControlRequest } from "./router.js";
|
||||
import type { HTLCAttempt } from "./lightning.js";
|
||||
import type { SendToRouteResponse } from "./router.js";
|
||||
import type { SendToRouteRequest } from "./router.js";
|
||||
import type { RouteFeeResponse } from "./router.js";
|
||||
import type { RouteFeeRequest } from "./router.js";
|
||||
import type { UnaryCall } from "@protobuf-ts/runtime-rpc";
|
||||
import type { TrackPaymentsRequest } from "./router.js";
|
||||
import type { TrackPaymentRequest } from "./router.js";
|
||||
import { stackIntercept } from "@protobuf-ts/runtime-rpc";
|
||||
import type { Payment } from "./lightning.js";
|
||||
import type { SendPaymentRequest } from "./router.js";
|
||||
import type { ServerStreamingCall } from "@protobuf-ts/runtime-rpc";
|
||||
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
|
||||
/**
|
||||
* Router is a service that offers advanced interaction with the router
|
||||
* subsystem of the daemon.
|
||||
*
|
||||
* @generated from protobuf service routerrpc.Router
|
||||
*/
|
||||
export interface IRouterClient {
|
||||
/**
|
||||
*
|
||||
* SendPaymentV2 attempts to route a payment described by the passed
|
||||
* PaymentRequest to the final destination. The call returns a stream of
|
||||
* payment updates.
|
||||
*
|
||||
* @generated from protobuf rpc: SendPaymentV2(routerrpc.SendPaymentRequest) returns (stream lnrpc.Payment);
|
||||
*/
|
||||
sendPaymentV2(input: SendPaymentRequest, options?: RpcOptions): ServerStreamingCall<SendPaymentRequest, Payment>;
|
||||
/**
|
||||
*
|
||||
* TrackPaymentV2 returns an update stream for the payment identified by the
|
||||
* payment hash.
|
||||
*
|
||||
* @generated from protobuf rpc: TrackPaymentV2(routerrpc.TrackPaymentRequest) returns (stream lnrpc.Payment);
|
||||
*/
|
||||
trackPaymentV2(input: TrackPaymentRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentRequest, Payment>;
|
||||
/**
|
||||
*
|
||||
* TrackPayments returns an update stream for every payment that is not in a
|
||||
* terminal state. Note that if payments are in-flight while starting a new
|
||||
* subscription, the start of the payment stream could produce out-of-order
|
||||
* and/or duplicate events. In order to get updates for every in-flight
|
||||
* payment attempt make sure to subscribe to this method before initiating any
|
||||
* payments.
|
||||
*
|
||||
* @generated from protobuf rpc: TrackPayments(routerrpc.TrackPaymentsRequest) returns (stream lnrpc.Payment);
|
||||
*/
|
||||
trackPayments(input: TrackPaymentsRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentsRequest, Payment>;
|
||||
/**
|
||||
*
|
||||
* EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
|
||||
* may cost to send an HTLC to the target end destination.
|
||||
*
|
||||
* @generated from protobuf rpc: EstimateRouteFee(routerrpc.RouteFeeRequest) returns (routerrpc.RouteFeeResponse);
|
||||
*/
|
||||
estimateRouteFee(input: RouteFeeRequest, options?: RpcOptions): UnaryCall<RouteFeeRequest, RouteFeeResponse>;
|
||||
/**
|
||||
*
|
||||
* Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via
|
||||
* the specified route. This method differs from SendPayment in that it
|
||||
* allows users to specify a full route manually. This can be used for
|
||||
* things like rebalancing, and atomic swaps. It differs from the newer
|
||||
* SendToRouteV2 in that it doesn't return the full HTLC information.
|
||||
*
|
||||
* @deprecated
|
||||
* @generated from protobuf rpc: SendToRoute(routerrpc.SendToRouteRequest) returns (routerrpc.SendToRouteResponse);
|
||||
*/
|
||||
sendToRoute(input: SendToRouteRequest, options?: RpcOptions): UnaryCall<SendToRouteRequest, SendToRouteResponse>;
|
||||
/**
|
||||
*
|
||||
* SendToRouteV2 attempts to make a payment via the specified route. This
|
||||
* method differs from SendPayment in that it allows users to specify a full
|
||||
* route manually. This can be used for things like rebalancing, and atomic
|
||||
* swaps.
|
||||
*
|
||||
* @generated from protobuf rpc: SendToRouteV2(routerrpc.SendToRouteRequest) returns (lnrpc.HTLCAttempt);
|
||||
*/
|
||||
sendToRouteV2(input: SendToRouteRequest, options?: RpcOptions): UnaryCall<SendToRouteRequest, HTLCAttempt>;
|
||||
/**
|
||||
*
|
||||
* ResetMissionControl clears all mission control state and starts with a clean
|
||||
* slate.
|
||||
*
|
||||
* @generated from protobuf rpc: ResetMissionControl(routerrpc.ResetMissionControlRequest) returns (routerrpc.ResetMissionControlResponse);
|
||||
*/
|
||||
resetMissionControl(input: ResetMissionControlRequest, options?: RpcOptions): UnaryCall<ResetMissionControlRequest, ResetMissionControlResponse>;
|
||||
/**
|
||||
*
|
||||
* QueryMissionControl exposes the internal mission control state to callers.
|
||||
* It is a development feature.
|
||||
*
|
||||
* @generated from protobuf rpc: QueryMissionControl(routerrpc.QueryMissionControlRequest) returns (routerrpc.QueryMissionControlResponse);
|
||||
*/
|
||||
queryMissionControl(input: QueryMissionControlRequest, options?: RpcOptions): UnaryCall<QueryMissionControlRequest, QueryMissionControlResponse>;
|
||||
/**
|
||||
*
|
||||
* XImportMissionControl is an experimental API that imports the state provided
|
||||
* to the internal mission control's state, using all results which are more
|
||||
* recent than our existing values. These values will only be imported
|
||||
* in-memory, and will not be persisted across restarts.
|
||||
*
|
||||
* @generated from protobuf rpc: XImportMissionControl(routerrpc.XImportMissionControlRequest) returns (routerrpc.XImportMissionControlResponse);
|
||||
*/
|
||||
xImportMissionControl(input: XImportMissionControlRequest, options?: RpcOptions): UnaryCall<XImportMissionControlRequest, XImportMissionControlResponse>;
|
||||
/**
|
||||
*
|
||||
* GetMissionControlConfig returns mission control's current config.
|
||||
*
|
||||
* @generated from protobuf rpc: GetMissionControlConfig(routerrpc.GetMissionControlConfigRequest) returns (routerrpc.GetMissionControlConfigResponse);
|
||||
*/
|
||||
getMissionControlConfig(input: GetMissionControlConfigRequest, options?: RpcOptions): UnaryCall<GetMissionControlConfigRequest, GetMissionControlConfigResponse>;
|
||||
/**
|
||||
*
|
||||
* SetMissionControlConfig will set mission control's config, if the config
|
||||
* provided is valid.
|
||||
*
|
||||
* @generated from protobuf rpc: SetMissionControlConfig(routerrpc.SetMissionControlConfigRequest) returns (routerrpc.SetMissionControlConfigResponse);
|
||||
*/
|
||||
setMissionControlConfig(input: SetMissionControlConfigRequest, options?: RpcOptions): UnaryCall<SetMissionControlConfigRequest, SetMissionControlConfigResponse>;
|
||||
/**
|
||||
*
|
||||
* QueryProbability returns the current success probability estimate for a
|
||||
* given node pair and amount.
|
||||
*
|
||||
* @generated from protobuf rpc: QueryProbability(routerrpc.QueryProbabilityRequest) returns (routerrpc.QueryProbabilityResponse);
|
||||
*/
|
||||
queryProbability(input: QueryProbabilityRequest, options?: RpcOptions): UnaryCall<QueryProbabilityRequest, QueryProbabilityResponse>;
|
||||
/**
|
||||
*
|
||||
* BuildRoute builds a fully specified route based on a list of hop public
|
||||
* keys. It retrieves the relevant channel policies from the graph in order to
|
||||
* calculate the correct fees and time locks.
|
||||
*
|
||||
* @generated from protobuf rpc: BuildRoute(routerrpc.BuildRouteRequest) returns (routerrpc.BuildRouteResponse);
|
||||
*/
|
||||
buildRoute(input: BuildRouteRequest, options?: RpcOptions): UnaryCall<BuildRouteRequest, BuildRouteResponse>;
|
||||
/**
|
||||
*
|
||||
* SubscribeHtlcEvents creates a uni-directional stream from the server to
|
||||
* the client which delivers a stream of htlc events.
|
||||
*
|
||||
* @generated from protobuf rpc: SubscribeHtlcEvents(routerrpc.SubscribeHtlcEventsRequest) returns (stream routerrpc.HtlcEvent);
|
||||
*/
|
||||
subscribeHtlcEvents(input: SubscribeHtlcEventsRequest, options?: RpcOptions): ServerStreamingCall<SubscribeHtlcEventsRequest, HtlcEvent>;
|
||||
/**
|
||||
*
|
||||
* Deprecated, use SendPaymentV2. SendPayment attempts to route a payment
|
||||
* described by the passed PaymentRequest to the final destination. The call
|
||||
* returns a stream of payment status updates.
|
||||
*
|
||||
* @deprecated
|
||||
* @generated from protobuf rpc: SendPayment(routerrpc.SendPaymentRequest) returns (stream routerrpc.PaymentStatus);
|
||||
*/
|
||||
sendPayment(input: SendPaymentRequest, options?: RpcOptions): ServerStreamingCall<SendPaymentRequest, PaymentStatus>;
|
||||
/**
|
||||
*
|
||||
* Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for
|
||||
* the payment identified by the payment hash.
|
||||
*
|
||||
* @deprecated
|
||||
* @generated from protobuf rpc: TrackPayment(routerrpc.TrackPaymentRequest) returns (stream routerrpc.PaymentStatus);
|
||||
*/
|
||||
trackPayment(input: TrackPaymentRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentRequest, PaymentStatus>;
|
||||
/**
|
||||
* *
|
||||
* HtlcInterceptor dispatches a bi-directional streaming RPC in which
|
||||
* Forwarded HTLC requests are sent to the client and the client responds with
|
||||
* a boolean that tells LND if this htlc should be intercepted.
|
||||
* In case of interception, the htlc can be either settled, cancelled or
|
||||
* resumed later by using the ResolveHoldForward endpoint.
|
||||
*
|
||||
* @generated from protobuf rpc: HtlcInterceptor(stream routerrpc.ForwardHtlcInterceptResponse) returns (stream routerrpc.ForwardHtlcInterceptRequest);
|
||||
*/
|
||||
htlcInterceptor(options?: RpcOptions): DuplexStreamingCall<ForwardHtlcInterceptResponse, ForwardHtlcInterceptRequest>;
|
||||
/**
|
||||
*
|
||||
* UpdateChanStatus attempts to manually set the state of a channel
|
||||
* (enabled, disabled, or auto). A manual "disable" request will cause the
|
||||
* channel to stay disabled until a subsequent manual request of either
|
||||
* "enable" or "auto".
|
||||
*
|
||||
* @generated from protobuf rpc: UpdateChanStatus(routerrpc.UpdateChanStatusRequest) returns (routerrpc.UpdateChanStatusResponse);
|
||||
*/
|
||||
updateChanStatus(input: UpdateChanStatusRequest, options?: RpcOptions): UnaryCall<UpdateChanStatusRequest, UpdateChanStatusResponse>;
|
||||
}
|
||||
/**
|
||||
* Router is a service that offers advanced interaction with the router
|
||||
* subsystem of the daemon.
|
||||
*
|
||||
* @generated from protobuf service routerrpc.Router
|
||||
*/
|
||||
export class RouterClient implements IRouterClient, ServiceInfo {
|
||||
typeName = Router.typeName;
|
||||
methods = Router.methods;
|
||||
options = Router.options;
|
||||
constructor(private readonly _transport: RpcTransport) {
|
||||
}
|
||||
/**
|
||||
*
|
||||
* SendPaymentV2 attempts to route a payment described by the passed
|
||||
* PaymentRequest to the final destination. The call returns a stream of
|
||||
* payment updates.
|
||||
*
|
||||
* @generated from protobuf rpc: SendPaymentV2(routerrpc.SendPaymentRequest) returns (stream lnrpc.Payment);
|
||||
*/
|
||||
sendPaymentV2(input: SendPaymentRequest, options?: RpcOptions): ServerStreamingCall<SendPaymentRequest, Payment> {
|
||||
const method = this.methods[0], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SendPaymentRequest, Payment>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* TrackPaymentV2 returns an update stream for the payment identified by the
|
||||
* payment hash.
|
||||
*
|
||||
* @generated from protobuf rpc: TrackPaymentV2(routerrpc.TrackPaymentRequest) returns (stream lnrpc.Payment);
|
||||
*/
|
||||
trackPaymentV2(input: TrackPaymentRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentRequest, Payment> {
|
||||
const method = this.methods[1], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<TrackPaymentRequest, Payment>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* TrackPayments returns an update stream for every payment that is not in a
|
||||
* terminal state. Note that if payments are in-flight while starting a new
|
||||
* subscription, the start of the payment stream could produce out-of-order
|
||||
* and/or duplicate events. In order to get updates for every in-flight
|
||||
* payment attempt make sure to subscribe to this method before initiating any
|
||||
* payments.
|
||||
*
|
||||
* @generated from protobuf rpc: TrackPayments(routerrpc.TrackPaymentsRequest) returns (stream lnrpc.Payment);
|
||||
*/
|
||||
trackPayments(input: TrackPaymentsRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentsRequest, Payment> {
|
||||
const method = this.methods[2], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<TrackPaymentsRequest, Payment>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
|
||||
* may cost to send an HTLC to the target end destination.
|
||||
*
|
||||
* @generated from protobuf rpc: EstimateRouteFee(routerrpc.RouteFeeRequest) returns (routerrpc.RouteFeeResponse);
|
||||
*/
|
||||
estimateRouteFee(input: RouteFeeRequest, options?: RpcOptions): UnaryCall<RouteFeeRequest, RouteFeeResponse> {
|
||||
const method = this.methods[3], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<RouteFeeRequest, RouteFeeResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via
|
||||
* the specified route. This method differs from SendPayment in that it
|
||||
* allows users to specify a full route manually. This can be used for
|
||||
* things like rebalancing, and atomic swaps. It differs from the newer
|
||||
* SendToRouteV2 in that it doesn't return the full HTLC information.
|
||||
*
|
||||
* @deprecated
|
||||
* @generated from protobuf rpc: SendToRoute(routerrpc.SendToRouteRequest) returns (routerrpc.SendToRouteResponse);
|
||||
*/
|
||||
sendToRoute(input: SendToRouteRequest, options?: RpcOptions): UnaryCall<SendToRouteRequest, SendToRouteResponse> {
|
||||
const method = this.methods[4], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SendToRouteRequest, SendToRouteResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* SendToRouteV2 attempts to make a payment via the specified route. This
|
||||
* method differs from SendPayment in that it allows users to specify a full
|
||||
* route manually. This can be used for things like rebalancing, and atomic
|
||||
* swaps.
|
||||
*
|
||||
* @generated from protobuf rpc: SendToRouteV2(routerrpc.SendToRouteRequest) returns (lnrpc.HTLCAttempt);
|
||||
*/
|
||||
sendToRouteV2(input: SendToRouteRequest, options?: RpcOptions): UnaryCall<SendToRouteRequest, HTLCAttempt> {
|
||||
const method = this.methods[5], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SendToRouteRequest, HTLCAttempt>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* ResetMissionControl clears all mission control state and starts with a clean
|
||||
* slate.
|
||||
*
|
||||
* @generated from protobuf rpc: ResetMissionControl(routerrpc.ResetMissionControlRequest) returns (routerrpc.ResetMissionControlResponse);
|
||||
*/
|
||||
resetMissionControl(input: ResetMissionControlRequest, options?: RpcOptions): UnaryCall<ResetMissionControlRequest, ResetMissionControlResponse> {
|
||||
const method = this.methods[6], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<ResetMissionControlRequest, ResetMissionControlResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* QueryMissionControl exposes the internal mission control state to callers.
|
||||
* It is a development feature.
|
||||
*
|
||||
* @generated from protobuf rpc: QueryMissionControl(routerrpc.QueryMissionControlRequest) returns (routerrpc.QueryMissionControlResponse);
|
||||
*/
|
||||
queryMissionControl(input: QueryMissionControlRequest, options?: RpcOptions): UnaryCall<QueryMissionControlRequest, QueryMissionControlResponse> {
|
||||
const method = this.methods[7], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<QueryMissionControlRequest, QueryMissionControlResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* XImportMissionControl is an experimental API that imports the state provided
|
||||
* to the internal mission control's state, using all results which are more
|
||||
* recent than our existing values. These values will only be imported
|
||||
* in-memory, and will not be persisted across restarts.
|
||||
*
|
||||
* @generated from protobuf rpc: XImportMissionControl(routerrpc.XImportMissionControlRequest) returns (routerrpc.XImportMissionControlResponse);
|
||||
*/
|
||||
xImportMissionControl(input: XImportMissionControlRequest, options?: RpcOptions): UnaryCall<XImportMissionControlRequest, XImportMissionControlResponse> {
|
||||
const method = this.methods[8], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<XImportMissionControlRequest, XImportMissionControlResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* GetMissionControlConfig returns mission control's current config.
|
||||
*
|
||||
* @generated from protobuf rpc: GetMissionControlConfig(routerrpc.GetMissionControlConfigRequest) returns (routerrpc.GetMissionControlConfigResponse);
|
||||
*/
|
||||
getMissionControlConfig(input: GetMissionControlConfigRequest, options?: RpcOptions): UnaryCall<GetMissionControlConfigRequest, GetMissionControlConfigResponse> {
|
||||
const method = this.methods[9], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<GetMissionControlConfigRequest, GetMissionControlConfigResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* SetMissionControlConfig will set mission control's config, if the config
|
||||
* provided is valid.
|
||||
*
|
||||
* @generated from protobuf rpc: SetMissionControlConfig(routerrpc.SetMissionControlConfigRequest) returns (routerrpc.SetMissionControlConfigResponse);
|
||||
*/
|
||||
setMissionControlConfig(input: SetMissionControlConfigRequest, options?: RpcOptions): UnaryCall<SetMissionControlConfigRequest, SetMissionControlConfigResponse> {
|
||||
const method = this.methods[10], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SetMissionControlConfigRequest, SetMissionControlConfigResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* QueryProbability returns the current success probability estimate for a
|
||||
* given node pair and amount.
|
||||
*
|
||||
* @generated from protobuf rpc: QueryProbability(routerrpc.QueryProbabilityRequest) returns (routerrpc.QueryProbabilityResponse);
|
||||
*/
|
||||
queryProbability(input: QueryProbabilityRequest, options?: RpcOptions): UnaryCall<QueryProbabilityRequest, QueryProbabilityResponse> {
|
||||
const method = this.methods[11], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<QueryProbabilityRequest, QueryProbabilityResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* BuildRoute builds a fully specified route based on a list of hop public
|
||||
* keys. It retrieves the relevant channel policies from the graph in order to
|
||||
* calculate the correct fees and time locks.
|
||||
*
|
||||
* @generated from protobuf rpc: BuildRoute(routerrpc.BuildRouteRequest) returns (routerrpc.BuildRouteResponse);
|
||||
*/
|
||||
buildRoute(input: BuildRouteRequest, options?: RpcOptions): UnaryCall<BuildRouteRequest, BuildRouteResponse> {
|
||||
const method = this.methods[12], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<BuildRouteRequest, BuildRouteResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* SubscribeHtlcEvents creates a uni-directional stream from the server to
|
||||
* the client which delivers a stream of htlc events.
|
||||
*
|
||||
* @generated from protobuf rpc: SubscribeHtlcEvents(routerrpc.SubscribeHtlcEventsRequest) returns (stream routerrpc.HtlcEvent);
|
||||
*/
|
||||
subscribeHtlcEvents(input: SubscribeHtlcEventsRequest, options?: RpcOptions): ServerStreamingCall<SubscribeHtlcEventsRequest, HtlcEvent> {
|
||||
const method = this.methods[13], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SubscribeHtlcEventsRequest, HtlcEvent>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Deprecated, use SendPaymentV2. SendPayment attempts to route a payment
|
||||
* described by the passed PaymentRequest to the final destination. The call
|
||||
* returns a stream of payment status updates.
|
||||
*
|
||||
* @deprecated
|
||||
* @generated from protobuf rpc: SendPayment(routerrpc.SendPaymentRequest) returns (stream routerrpc.PaymentStatus);
|
||||
*/
|
||||
sendPayment(input: SendPaymentRequest, options?: RpcOptions): ServerStreamingCall<SendPaymentRequest, PaymentStatus> {
|
||||
const method = this.methods[14], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SendPaymentRequest, PaymentStatus>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for
|
||||
* the payment identified by the payment hash.
|
||||
*
|
||||
* @deprecated
|
||||
* @generated from protobuf rpc: TrackPayment(routerrpc.TrackPaymentRequest) returns (stream routerrpc.PaymentStatus);
|
||||
*/
|
||||
trackPayment(input: TrackPaymentRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentRequest, PaymentStatus> {
|
||||
const method = this.methods[15], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<TrackPaymentRequest, PaymentStatus>("serverStreaming", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* *
|
||||
* HtlcInterceptor dispatches a bi-directional streaming RPC in which
|
||||
* Forwarded HTLC requests are sent to the client and the client responds with
|
||||
* a boolean that tells LND if this htlc should be intercepted.
|
||||
* In case of interception, the htlc can be either settled, cancelled or
|
||||
* resumed later by using the ResolveHoldForward endpoint.
|
||||
*
|
||||
* @generated from protobuf rpc: HtlcInterceptor(stream routerrpc.ForwardHtlcInterceptResponse) returns (stream routerrpc.ForwardHtlcInterceptRequest);
|
||||
*/
|
||||
htlcInterceptor(options?: RpcOptions): DuplexStreamingCall<ForwardHtlcInterceptResponse, ForwardHtlcInterceptRequest> {
|
||||
const method = this.methods[16], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<ForwardHtlcInterceptResponse, ForwardHtlcInterceptRequest>("duplex", this._transport, method, opt);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* UpdateChanStatus attempts to manually set the state of a channel
|
||||
* (enabled, disabled, or auto). A manual "disable" request will cause the
|
||||
* channel to stay disabled until a subsequent manual request of either
|
||||
* "enable" or "auto".
|
||||
*
|
||||
* @generated from protobuf rpc: UpdateChanStatus(routerrpc.UpdateChanStatusRequest) returns (routerrpc.UpdateChanStatusResponse);
|
||||
*/
|
||||
updateChanStatus(input: UpdateChanStatusRequest, options?: RpcOptions): UnaryCall<UpdateChanStatusRequest, UpdateChanStatusResponse> {
|
||||
const method = this.methods[17], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<UpdateChanStatusRequest, UpdateChanStatusResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
7088
proto/lnd/router.ts
7088
proto/lnd/router.ts
File diff suppressed because it is too large
Load diff
|
|
@ -1,200 +1,200 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package chainrpc;
|
||||
|
||||
option go_package = "github.com/lightningnetwork/lnd/lnrpc/chainrpc";
|
||||
|
||||
// ChainNotifier is a service that can be used to get information about the
|
||||
// chain backend by registering notifiers for chain events.
|
||||
service ChainNotifier {
|
||||
/*
|
||||
RegisterConfirmationsNtfn is a synchronous response-streaming RPC that
|
||||
registers an intent for a client to be notified once a confirmation request
|
||||
has reached its required number of confirmations on-chain.
|
||||
|
||||
A confirmation request must have a valid output script. It is also possible
|
||||
to give a transaction ID. If the transaction ID is not set, a notification
|
||||
is sent once the output script confirms. If the transaction ID is also set,
|
||||
a notification is sent once the output script confirms in the given
|
||||
transaction.
|
||||
*/
|
||||
rpc RegisterConfirmationsNtfn (ConfRequest) returns (stream ConfEvent);
|
||||
|
||||
/*
|
||||
RegisterSpendNtfn is a synchronous response-streaming RPC that registers an
|
||||
intent for a client to be notification once a spend request has been spent
|
||||
by a transaction that has confirmed on-chain.
|
||||
|
||||
A client can specify whether the spend request should be for a particular
|
||||
outpoint or for an output script by specifying a zero outpoint.
|
||||
*/
|
||||
rpc RegisterSpendNtfn (SpendRequest) returns (stream SpendEvent);
|
||||
|
||||
/*
|
||||
RegisterBlockEpochNtfn is a synchronous response-streaming RPC that
|
||||
registers an intent for a client to be notified of blocks in the chain. The
|
||||
stream will return a hash and height tuple of a block for each new/stale
|
||||
block in the chain. It is the client's responsibility to determine whether
|
||||
the tuple returned is for a new or stale block in the chain.
|
||||
|
||||
A client can also request a historical backlog of blocks from a particular
|
||||
point. This allows clients to be idempotent by ensuring that they do not
|
||||
missing processing a single block within the chain.
|
||||
*/
|
||||
rpc RegisterBlockEpochNtfn (BlockEpoch) returns (stream BlockEpoch);
|
||||
}
|
||||
|
||||
message ConfRequest {
|
||||
/*
|
||||
The transaction hash for which we should request a confirmation notification
|
||||
for. If set to a hash of all zeros, then the confirmation notification will
|
||||
be requested for the script instead.
|
||||
*/
|
||||
bytes txid = 1;
|
||||
|
||||
/*
|
||||
An output script within a transaction with the hash above which will be used
|
||||
by light clients to match block filters. If the transaction hash is set to a
|
||||
hash of all zeros, then a confirmation notification will be requested for
|
||||
this script instead.
|
||||
*/
|
||||
bytes script = 2;
|
||||
|
||||
/*
|
||||
The number of desired confirmations the transaction/output script should
|
||||
reach before dispatching a confirmation notification.
|
||||
*/
|
||||
uint32 num_confs = 3;
|
||||
|
||||
/*
|
||||
The earliest height in the chain for which the transaction/output script
|
||||
could have been included in a block. This should in most cases be set to the
|
||||
broadcast height of the transaction/output script.
|
||||
*/
|
||||
uint32 height_hint = 4;
|
||||
|
||||
/*
|
||||
If true, then the block that mines the specified txid/script will be
|
||||
included in eventual the notification event.
|
||||
*/
|
||||
bool include_block = 5;
|
||||
}
|
||||
|
||||
message ConfDetails {
|
||||
// The raw bytes of the confirmed transaction.
|
||||
bytes raw_tx = 1;
|
||||
|
||||
// The hash of the block in which the confirmed transaction was included in.
|
||||
bytes block_hash = 2;
|
||||
|
||||
// The height of the block in which the confirmed transaction was included
|
||||
// in.
|
||||
uint32 block_height = 3;
|
||||
|
||||
// The index of the confirmed transaction within the block.
|
||||
uint32 tx_index = 4;
|
||||
|
||||
/*
|
||||
The raw bytes of the block that mined the transaction. Only included if
|
||||
include_block was set in the request.
|
||||
*/
|
||||
bytes raw_block = 5;
|
||||
}
|
||||
|
||||
message Reorg {
|
||||
// TODO(wilmer): need to know how the client will use this first.
|
||||
}
|
||||
|
||||
message ConfEvent {
|
||||
oneof event {
|
||||
/*
|
||||
An event that includes the confirmation details of the request
|
||||
(txid/ouput script).
|
||||
*/
|
||||
ConfDetails conf = 1;
|
||||
|
||||
/*
|
||||
An event send when the transaction of the request is reorged out of the
|
||||
chain.
|
||||
*/
|
||||
Reorg reorg = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message Outpoint {
|
||||
// The hash of the transaction.
|
||||
bytes hash = 1;
|
||||
|
||||
// The index of the output within the transaction.
|
||||
uint32 index = 2;
|
||||
}
|
||||
|
||||
message SpendRequest {
|
||||
/*
|
||||
The outpoint for which we should request a spend notification for. If set to
|
||||
a zero outpoint, then the spend notification will be requested for the
|
||||
script instead. A zero or nil outpoint is not supported for Taproot spends
|
||||
because the output script cannot reliably be computed from the witness alone
|
||||
and the spent output script is not always available in the rescan context.
|
||||
So an outpoint must _always_ be specified when registering a spend
|
||||
notification for a Taproot output.
|
||||
*/
|
||||
Outpoint outpoint = 1;
|
||||
|
||||
/*
|
||||
The output script for the outpoint above. This will be used by light clients
|
||||
to match block filters. If the outpoint is set to a zero outpoint, then a
|
||||
spend notification will be requested for this script instead.
|
||||
*/
|
||||
bytes script = 2;
|
||||
|
||||
/*
|
||||
The earliest height in the chain for which the outpoint/output script could
|
||||
have been spent. This should in most cases be set to the broadcast height of
|
||||
the outpoint/output script.
|
||||
*/
|
||||
uint32 height_hint = 3;
|
||||
|
||||
// TODO(wilmer): extend to support num confs on spending tx.
|
||||
}
|
||||
|
||||
message SpendDetails {
|
||||
// The outpoint was that spent.
|
||||
Outpoint spending_outpoint = 1;
|
||||
|
||||
// The raw bytes of the spending transaction.
|
||||
bytes raw_spending_tx = 2;
|
||||
|
||||
// The hash of the spending transaction.
|
||||
bytes spending_tx_hash = 3;
|
||||
|
||||
// The input of the spending transaction that fulfilled the spend request.
|
||||
uint32 spending_input_index = 4;
|
||||
|
||||
// The height at which the spending transaction was included in a block.
|
||||
uint32 spending_height = 5;
|
||||
}
|
||||
|
||||
message SpendEvent {
|
||||
oneof event {
|
||||
/*
|
||||
An event that includes the details of the spending transaction of the
|
||||
request (outpoint/output script).
|
||||
*/
|
||||
SpendDetails spend = 1;
|
||||
|
||||
/*
|
||||
An event sent when the spending transaction of the request was
|
||||
reorged out of the chain.
|
||||
*/
|
||||
Reorg reorg = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message BlockEpoch {
|
||||
// The hash of the block.
|
||||
bytes hash = 1;
|
||||
|
||||
// The height of the block.
|
||||
uint32 height = 2;
|
||||
syntax = "proto3";
|
||||
|
||||
package chainrpc;
|
||||
|
||||
option go_package = "github.com/lightningnetwork/lnd/lnrpc/chainrpc";
|
||||
|
||||
// ChainNotifier is a service that can be used to get information about the
|
||||
// chain backend by registering notifiers for chain events.
|
||||
service ChainNotifier {
|
||||
/*
|
||||
RegisterConfirmationsNtfn is a synchronous response-streaming RPC that
|
||||
registers an intent for a client to be notified once a confirmation request
|
||||
has reached its required number of confirmations on-chain.
|
||||
|
||||
A confirmation request must have a valid output script. It is also possible
|
||||
to give a transaction ID. If the transaction ID is not set, a notification
|
||||
is sent once the output script confirms. If the transaction ID is also set,
|
||||
a notification is sent once the output script confirms in the given
|
||||
transaction.
|
||||
*/
|
||||
rpc RegisterConfirmationsNtfn (ConfRequest) returns (stream ConfEvent);
|
||||
|
||||
/*
|
||||
RegisterSpendNtfn is a synchronous response-streaming RPC that registers an
|
||||
intent for a client to be notification once a spend request has been spent
|
||||
by a transaction that has confirmed on-chain.
|
||||
|
||||
A client can specify whether the spend request should be for a particular
|
||||
outpoint or for an output script by specifying a zero outpoint.
|
||||
*/
|
||||
rpc RegisterSpendNtfn (SpendRequest) returns (stream SpendEvent);
|
||||
|
||||
/*
|
||||
RegisterBlockEpochNtfn is a synchronous response-streaming RPC that
|
||||
registers an intent for a client to be notified of blocks in the chain. The
|
||||
stream will return a hash and height tuple of a block for each new/stale
|
||||
block in the chain. It is the client's responsibility to determine whether
|
||||
the tuple returned is for a new or stale block in the chain.
|
||||
|
||||
A client can also request a historical backlog of blocks from a particular
|
||||
point. This allows clients to be idempotent by ensuring that they do not
|
||||
missing processing a single block within the chain.
|
||||
*/
|
||||
rpc RegisterBlockEpochNtfn (BlockEpoch) returns (stream BlockEpoch);
|
||||
}
|
||||
|
||||
message ConfRequest {
|
||||
/*
|
||||
The transaction hash for which we should request a confirmation notification
|
||||
for. If set to a hash of all zeros, then the confirmation notification will
|
||||
be requested for the script instead.
|
||||
*/
|
||||
bytes txid = 1;
|
||||
|
||||
/*
|
||||
An output script within a transaction with the hash above which will be used
|
||||
by light clients to match block filters. If the transaction hash is set to a
|
||||
hash of all zeros, then a confirmation notification will be requested for
|
||||
this script instead.
|
||||
*/
|
||||
bytes script = 2;
|
||||
|
||||
/*
|
||||
The number of desired confirmations the transaction/output script should
|
||||
reach before dispatching a confirmation notification.
|
||||
*/
|
||||
uint32 num_confs = 3;
|
||||
|
||||
/*
|
||||
The earliest height in the chain for which the transaction/output script
|
||||
could have been included in a block. This should in most cases be set to the
|
||||
broadcast height of the transaction/output script.
|
||||
*/
|
||||
uint32 height_hint = 4;
|
||||
|
||||
/*
|
||||
If true, then the block that mines the specified txid/script will be
|
||||
included in eventual the notification event.
|
||||
*/
|
||||
bool include_block = 5;
|
||||
}
|
||||
|
||||
message ConfDetails {
|
||||
// The raw bytes of the confirmed transaction.
|
||||
bytes raw_tx = 1;
|
||||
|
||||
// The hash of the block in which the confirmed transaction was included in.
|
||||
bytes block_hash = 2;
|
||||
|
||||
// The height of the block in which the confirmed transaction was included
|
||||
// in.
|
||||
uint32 block_height = 3;
|
||||
|
||||
// The index of the confirmed transaction within the block.
|
||||
uint32 tx_index = 4;
|
||||
|
||||
/*
|
||||
The raw bytes of the block that mined the transaction. Only included if
|
||||
include_block was set in the request.
|
||||
*/
|
||||
bytes raw_block = 5;
|
||||
}
|
||||
|
||||
message Reorg {
|
||||
// TODO(wilmer): need to know how the client will use this first.
|
||||
}
|
||||
|
||||
message ConfEvent {
|
||||
oneof event {
|
||||
/*
|
||||
An event that includes the confirmation details of the request
|
||||
(txid/ouput script).
|
||||
*/
|
||||
ConfDetails conf = 1;
|
||||
|
||||
/*
|
||||
An event send when the transaction of the request is reorged out of the
|
||||
chain.
|
||||
*/
|
||||
Reorg reorg = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message Outpoint {
|
||||
// The hash of the transaction.
|
||||
bytes hash = 1;
|
||||
|
||||
// The index of the output within the transaction.
|
||||
uint32 index = 2;
|
||||
}
|
||||
|
||||
message SpendRequest {
|
||||
/*
|
||||
The outpoint for which we should request a spend notification for. If set to
|
||||
a zero outpoint, then the spend notification will be requested for the
|
||||
script instead. A zero or nil outpoint is not supported for Taproot spends
|
||||
because the output script cannot reliably be computed from the witness alone
|
||||
and the spent output script is not always available in the rescan context.
|
||||
So an outpoint must _always_ be specified when registering a spend
|
||||
notification for a Taproot output.
|
||||
*/
|
||||
Outpoint outpoint = 1;
|
||||
|
||||
/*
|
||||
The output script for the outpoint above. This will be used by light clients
|
||||
to match block filters. If the outpoint is set to a zero outpoint, then a
|
||||
spend notification will be requested for this script instead.
|
||||
*/
|
||||
bytes script = 2;
|
||||
|
||||
/*
|
||||
The earliest height in the chain for which the outpoint/output script could
|
||||
have been spent. This should in most cases be set to the broadcast height of
|
||||
the outpoint/output script.
|
||||
*/
|
||||
uint32 height_hint = 3;
|
||||
|
||||
// TODO(wilmer): extend to support num confs on spending tx.
|
||||
}
|
||||
|
||||
message SpendDetails {
|
||||
// The outpoint was that spent.
|
||||
Outpoint spending_outpoint = 1;
|
||||
|
||||
// The raw bytes of the spending transaction.
|
||||
bytes raw_spending_tx = 2;
|
||||
|
||||
// The hash of the spending transaction.
|
||||
bytes spending_tx_hash = 3;
|
||||
|
||||
// The input of the spending transaction that fulfilled the spend request.
|
||||
uint32 spending_input_index = 4;
|
||||
|
||||
// The height at which the spending transaction was included in a block.
|
||||
uint32 spending_height = 5;
|
||||
}
|
||||
|
||||
message SpendEvent {
|
||||
oneof event {
|
||||
/*
|
||||
An event that includes the details of the spending transaction of the
|
||||
request (outpoint/output script).
|
||||
*/
|
||||
SpendDetails spend = 1;
|
||||
|
||||
/*
|
||||
An event sent when the spending transaction of the request was
|
||||
reorged out of the chain.
|
||||
*/
|
||||
Reorg reorg = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message BlockEpoch {
|
||||
// The hash of the block.
|
||||
bytes hash = 1;
|
||||
|
||||
// The height of the block.
|
||||
uint32 height = 2;
|
||||
}
|
||||
|
|
@ -1,175 +1,175 @@
|
|||
syntax = "proto3";
|
||||
|
||||
import "lightning.proto";
|
||||
|
||||
package invoicesrpc;
|
||||
|
||||
option go_package = "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc";
|
||||
|
||||
// Invoices is a service that can be used to create, accept, settle and cancel
|
||||
// invoices.
|
||||
service Invoices {
|
||||
/*
|
||||
SubscribeSingleInvoice returns a uni-directional stream (server -> client)
|
||||
to notify the client of state transitions of the specified invoice.
|
||||
Initially the current invoice state is always sent out.
|
||||
*/
|
||||
rpc SubscribeSingleInvoice (SubscribeSingleInvoiceRequest)
|
||||
returns (stream lnrpc.Invoice);
|
||||
|
||||
/*
|
||||
CancelInvoice cancels a currently open invoice. If the invoice is already
|
||||
canceled, this call will succeed. If the invoice is already settled, it will
|
||||
fail.
|
||||
*/
|
||||
rpc CancelInvoice (CancelInvoiceMsg) returns (CancelInvoiceResp);
|
||||
|
||||
/*
|
||||
AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
|
||||
supplied in the request.
|
||||
*/
|
||||
rpc AddHoldInvoice (AddHoldInvoiceRequest) returns (AddHoldInvoiceResp);
|
||||
|
||||
/*
|
||||
SettleInvoice settles an accepted invoice. If the invoice is already
|
||||
settled, this call will succeed.
|
||||
*/
|
||||
rpc SettleInvoice (SettleInvoiceMsg) returns (SettleInvoiceResp);
|
||||
|
||||
/*
|
||||
LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced
|
||||
using either its payment hash, payment address, or set ID.
|
||||
*/
|
||||
rpc LookupInvoiceV2 (LookupInvoiceMsg) returns (lnrpc.Invoice);
|
||||
}
|
||||
|
||||
message CancelInvoiceMsg {
|
||||
// Hash corresponding to the (hold) invoice to cancel. When using
|
||||
// REST, this field must be encoded as base64.
|
||||
bytes payment_hash = 1;
|
||||
}
|
||||
message CancelInvoiceResp {
|
||||
}
|
||||
|
||||
message AddHoldInvoiceRequest {
|
||||
/*
|
||||
An optional memo to attach along with the invoice. Used for record keeping
|
||||
purposes for the invoice's creator, and will also be set in the description
|
||||
field of the encoded payment request if the description_hash field is not
|
||||
being used.
|
||||
*/
|
||||
string memo = 1;
|
||||
|
||||
// The hash of the preimage
|
||||
bytes hash = 2;
|
||||
|
||||
/*
|
||||
The value of this invoice in satoshis
|
||||
|
||||
The fields value and value_msat are mutually exclusive.
|
||||
*/
|
||||
int64 value = 3;
|
||||
|
||||
/*
|
||||
The value of this invoice in millisatoshis
|
||||
|
||||
The fields value and value_msat are mutually exclusive.
|
||||
*/
|
||||
int64 value_msat = 10;
|
||||
|
||||
/*
|
||||
Hash (SHA-256) of a description of the payment. Used if the description of
|
||||
payment (memo) is too long to naturally fit within the description field
|
||||
of an encoded payment request.
|
||||
*/
|
||||
bytes description_hash = 4;
|
||||
|
||||
// Payment request expiry time in seconds. Default is 3600 (1 hour).
|
||||
int64 expiry = 5;
|
||||
|
||||
// Fallback on-chain address.
|
||||
string fallback_addr = 6;
|
||||
|
||||
// Delta to use for the time-lock of the CLTV extended to the final hop.
|
||||
uint64 cltv_expiry = 7;
|
||||
|
||||
/*
|
||||
Route hints that can each be individually used to assist in reaching the
|
||||
invoice's destination.
|
||||
*/
|
||||
repeated lnrpc.RouteHint route_hints = 8;
|
||||
|
||||
// Whether this invoice should include routing hints for private channels.
|
||||
bool private = 9;
|
||||
}
|
||||
|
||||
message AddHoldInvoiceResp {
|
||||
/*
|
||||
A bare-bones invoice for a payment within the Lightning Network. With the
|
||||
details of the invoice, the sender has all the data necessary to send a
|
||||
payment to the recipient.
|
||||
*/
|
||||
string payment_request = 1;
|
||||
|
||||
/*
|
||||
The "add" index of this invoice. Each newly created invoice will increment
|
||||
this index making it monotonically increasing. Callers to the
|
||||
SubscribeInvoices call can use this to instantly get notified of all added
|
||||
invoices with an add_index greater than this one.
|
||||
*/
|
||||
uint64 add_index = 2;
|
||||
|
||||
/*
|
||||
The payment address of the generated invoice. This value should be used
|
||||
in all payments for this invoice as we require it for end to end
|
||||
security.
|
||||
*/
|
||||
bytes payment_addr = 3;
|
||||
}
|
||||
|
||||
message SettleInvoiceMsg {
|
||||
// Externally discovered pre-image that should be used to settle the hold
|
||||
// invoice.
|
||||
bytes preimage = 1;
|
||||
}
|
||||
|
||||
message SettleInvoiceResp {
|
||||
}
|
||||
|
||||
message SubscribeSingleInvoiceRequest {
|
||||
reserved 1;
|
||||
|
||||
// Hash corresponding to the (hold) invoice to subscribe to. When using
|
||||
// REST, this field must be encoded as base64url.
|
||||
bytes r_hash = 2;
|
||||
}
|
||||
|
||||
enum LookupModifier {
|
||||
// The default look up modifier, no look up behavior is changed.
|
||||
DEFAULT = 0;
|
||||
|
||||
/*
|
||||
Indicates that when a look up is done based on a set_id, then only that set
|
||||
of HTLCs related to that set ID should be returned.
|
||||
*/
|
||||
HTLC_SET_ONLY = 1;
|
||||
|
||||
/*
|
||||
Indicates that when a look up is done using a payment_addr, then no HTLCs
|
||||
related to the payment_addr should be returned. This is useful when one
|
||||
wants to be able to obtain the set of associated setIDs with a given
|
||||
invoice, then look up the sub-invoices "projected" by that set ID.
|
||||
*/
|
||||
HTLC_SET_BLANK = 2;
|
||||
}
|
||||
|
||||
message LookupInvoiceMsg {
|
||||
oneof invoice_ref {
|
||||
// When using REST, this field must be encoded as base64.
|
||||
bytes payment_hash = 1;
|
||||
bytes payment_addr = 2;
|
||||
bytes set_id = 3;
|
||||
}
|
||||
|
||||
LookupModifier lookup_modifier = 4;
|
||||
syntax = "proto3";
|
||||
|
||||
import "lightning.proto";
|
||||
|
||||
package invoicesrpc;
|
||||
|
||||
option go_package = "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc";
|
||||
|
||||
// Invoices is a service that can be used to create, accept, settle and cancel
|
||||
// invoices.
|
||||
service Invoices {
|
||||
/*
|
||||
SubscribeSingleInvoice returns a uni-directional stream (server -> client)
|
||||
to notify the client of state transitions of the specified invoice.
|
||||
Initially the current invoice state is always sent out.
|
||||
*/
|
||||
rpc SubscribeSingleInvoice (SubscribeSingleInvoiceRequest)
|
||||
returns (stream lnrpc.Invoice);
|
||||
|
||||
/*
|
||||
CancelInvoice cancels a currently open invoice. If the invoice is already
|
||||
canceled, this call will succeed. If the invoice is already settled, it will
|
||||
fail.
|
||||
*/
|
||||
rpc CancelInvoice (CancelInvoiceMsg) returns (CancelInvoiceResp);
|
||||
|
||||
/*
|
||||
AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
|
||||
supplied in the request.
|
||||
*/
|
||||
rpc AddHoldInvoice (AddHoldInvoiceRequest) returns (AddHoldInvoiceResp);
|
||||
|
||||
/*
|
||||
SettleInvoice settles an accepted invoice. If the invoice is already
|
||||
settled, this call will succeed.
|
||||
*/
|
||||
rpc SettleInvoice (SettleInvoiceMsg) returns (SettleInvoiceResp);
|
||||
|
||||
/*
|
||||
LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced
|
||||
using either its payment hash, payment address, or set ID.
|
||||
*/
|
||||
rpc LookupInvoiceV2 (LookupInvoiceMsg) returns (lnrpc.Invoice);
|
||||
}
|
||||
|
||||
message CancelInvoiceMsg {
|
||||
// Hash corresponding to the (hold) invoice to cancel. When using
|
||||
// REST, this field must be encoded as base64.
|
||||
bytes payment_hash = 1;
|
||||
}
|
||||
message CancelInvoiceResp {
|
||||
}
|
||||
|
||||
message AddHoldInvoiceRequest {
|
||||
/*
|
||||
An optional memo to attach along with the invoice. Used for record keeping
|
||||
purposes for the invoice's creator, and will also be set in the description
|
||||
field of the encoded payment request if the description_hash field is not
|
||||
being used.
|
||||
*/
|
||||
string memo = 1;
|
||||
|
||||
// The hash of the preimage
|
||||
bytes hash = 2;
|
||||
|
||||
/*
|
||||
The value of this invoice in satoshis
|
||||
|
||||
The fields value and value_msat are mutually exclusive.
|
||||
*/
|
||||
int64 value = 3;
|
||||
|
||||
/*
|
||||
The value of this invoice in millisatoshis
|
||||
|
||||
The fields value and value_msat are mutually exclusive.
|
||||
*/
|
||||
int64 value_msat = 10;
|
||||
|
||||
/*
|
||||
Hash (SHA-256) of a description of the payment. Used if the description of
|
||||
payment (memo) is too long to naturally fit within the description field
|
||||
of an encoded payment request.
|
||||
*/
|
||||
bytes description_hash = 4;
|
||||
|
||||
// Payment request expiry time in seconds. Default is 3600 (1 hour).
|
||||
int64 expiry = 5;
|
||||
|
||||
// Fallback on-chain address.
|
||||
string fallback_addr = 6;
|
||||
|
||||
// Delta to use for the time-lock of the CLTV extended to the final hop.
|
||||
uint64 cltv_expiry = 7;
|
||||
|
||||
/*
|
||||
Route hints that can each be individually used to assist in reaching the
|
||||
invoice's destination.
|
||||
*/
|
||||
repeated lnrpc.RouteHint route_hints = 8;
|
||||
|
||||
// Whether this invoice should include routing hints for private channels.
|
||||
bool private = 9;
|
||||
}
|
||||
|
||||
message AddHoldInvoiceResp {
|
||||
/*
|
||||
A bare-bones invoice for a payment within the Lightning Network. With the
|
||||
details of the invoice, the sender has all the data necessary to send a
|
||||
payment to the recipient.
|
||||
*/
|
||||
string payment_request = 1;
|
||||
|
||||
/*
|
||||
The "add" index of this invoice. Each newly created invoice will increment
|
||||
this index making it monotonically increasing. Callers to the
|
||||
SubscribeInvoices call can use this to instantly get notified of all added
|
||||
invoices with an add_index greater than this one.
|
||||
*/
|
||||
uint64 add_index = 2;
|
||||
|
||||
/*
|
||||
The payment address of the generated invoice. This value should be used
|
||||
in all payments for this invoice as we require it for end to end
|
||||
security.
|
||||
*/
|
||||
bytes payment_addr = 3;
|
||||
}
|
||||
|
||||
message SettleInvoiceMsg {
|
||||
// Externally discovered pre-image that should be used to settle the hold
|
||||
// invoice.
|
||||
bytes preimage = 1;
|
||||
}
|
||||
|
||||
message SettleInvoiceResp {
|
||||
}
|
||||
|
||||
message SubscribeSingleInvoiceRequest {
|
||||
reserved 1;
|
||||
|
||||
// Hash corresponding to the (hold) invoice to subscribe to. When using
|
||||
// REST, this field must be encoded as base64url.
|
||||
bytes r_hash = 2;
|
||||
}
|
||||
|
||||
enum LookupModifier {
|
||||
// The default look up modifier, no look up behavior is changed.
|
||||
DEFAULT = 0;
|
||||
|
||||
/*
|
||||
Indicates that when a look up is done based on a set_id, then only that set
|
||||
of HTLCs related to that set ID should be returned.
|
||||
*/
|
||||
HTLC_SET_ONLY = 1;
|
||||
|
||||
/*
|
||||
Indicates that when a look up is done using a payment_addr, then no HTLCs
|
||||
related to the payment_addr should be returned. This is useful when one
|
||||
wants to be able to obtain the set of associated setIDs with a given
|
||||
invoice, then look up the sub-invoices "projected" by that set ID.
|
||||
*/
|
||||
HTLC_SET_BLANK = 2;
|
||||
}
|
||||
|
||||
message LookupInvoiceMsg {
|
||||
oneof invoice_ref {
|
||||
// When using REST, this field must be encoded as base64.
|
||||
bytes payment_hash = 1;
|
||||
bytes payment_addr = 2;
|
||||
bytes set_id = 3;
|
||||
}
|
||||
|
||||
LookupModifier lookup_modifier = 4;
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,386 +1,386 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package methods;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
import "structs.proto";
|
||||
|
||||
option go_package = "github.com/shocknet/lightning.pub";
|
||||
option (file_options) = {
|
||||
supported_http_methods:["post", "get"];
|
||||
supported_auths:[
|
||||
{
|
||||
id: "guest"
|
||||
name: "Guest"
|
||||
context:[]
|
||||
},
|
||||
{
|
||||
id: "user"
|
||||
name: "User",
|
||||
context:[{
|
||||
key:"user_id",
|
||||
value:"string"
|
||||
},{
|
||||
key:"app_id",
|
||||
value:"string"
|
||||
},{
|
||||
key:"app_user_id",
|
||||
value:"string"
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: "admin",
|
||||
name: "Admin",
|
||||
//encrypted:true,
|
||||
context:{
|
||||
key:"admin_id",
|
||||
value:"string"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "metrics",
|
||||
name: "Metrics",
|
||||
//encrypted:true,
|
||||
context:{
|
||||
key:"operator_id",
|
||||
value:"string"
|
||||
}
|
||||
},
|
||||
{
|
||||
id:"app",
|
||||
name:"App",
|
||||
context:{
|
||||
key:"app_id",
|
||||
value: "string"
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
message MethodQueryOptions {
|
||||
repeated string items = 1;
|
||||
}
|
||||
|
||||
extend google.protobuf.MethodOptions { // TODO: move this stuff to dep repo?
|
||||
string auth_type = 50003;
|
||||
string http_method = 50004;
|
||||
string http_route = 50005;
|
||||
MethodQueryOptions query = 50006;
|
||||
bool nostr = 50007;
|
||||
bool batch = 50008;
|
||||
|
||||
}
|
||||
|
||||
message ProtoFileOptions {
|
||||
message SupportedAuth {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
bool encrypted = 3;
|
||||
map<string,string> context = 4;
|
||||
}
|
||||
repeated SupportedAuth supported_auths = 1;
|
||||
repeated string supported_http_methods = 2;
|
||||
}
|
||||
|
||||
extend google.protobuf.FileOptions {
|
||||
ProtoFileOptions file_options = 50004;
|
||||
}
|
||||
|
||||
service LightningPub {
|
||||
// <Admin>
|
||||
rpc LndGetInfo(structs.LndGetInfoRequest) returns (structs.LndGetInfoResponse){
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/lnd/getinfo";
|
||||
};
|
||||
|
||||
rpc AddApp(structs.AddAppRequest) returns (structs.AuthApp) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/app/add";
|
||||
};
|
||||
|
||||
rpc AuthApp(structs.AuthAppRequest) returns (structs.AuthApp) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/app/auth";
|
||||
}
|
||||
|
||||
rpc BanUser(structs.BanUserRequest) returns (structs.BanUserResponse) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/user/ban";
|
||||
}
|
||||
|
||||
rpc GetUsageMetrics(structs.Empty) returns (structs.UsageMetrics) {
|
||||
option (auth_type) = "Metrics";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/reports/usage";
|
||||
}
|
||||
|
||||
rpc GetAppsMetrics(structs.AppsMetricsRequest) returns (structs.AppsMetrics) {
|
||||
option (auth_type) = "Metrics";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/reports/apps";
|
||||
}
|
||||
|
||||
rpc GetLndMetrics(structs.LndMetricsRequest) returns (structs.LndMetrics) {
|
||||
option (auth_type) = "Metrics";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/reports/lnd";
|
||||
}
|
||||
|
||||
|
||||
// </Admin>
|
||||
|
||||
// <Guest>
|
||||
rpc Health(structs.Empty) returns (structs.Empty){
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/health";
|
||||
};
|
||||
rpc EncryptionExchange(structs.EncryptionExchangeRequest) returns (structs.Empty){
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/encryption/exchange";
|
||||
};
|
||||
|
||||
rpc SetMockInvoiceAsPaid(structs.SetMockInvoiceAsPaidRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/lnd/mock/invoice/paid";
|
||||
}
|
||||
rpc GetLnurlWithdrawInfo(structs.Empty) returns (structs.LnurlWithdrawInfoResponse){
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/guest/lnurl_withdraw/info";
|
||||
option (query) = {items: ["k1"]};
|
||||
}
|
||||
rpc HandleLnurlWithdraw(structs.Empty) returns (structs.Empty){
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/guest/lnurl_withdraw/handle";
|
||||
option (query) = {items: ["k1", "pr"]};
|
||||
}
|
||||
rpc GetLnurlPayInfo(structs.Empty)returns (structs.LnurlPayInfoResponse) {
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/guest/lnurl_pay/info";
|
||||
option (query) = {items: ["k1"]};
|
||||
}
|
||||
rpc HandleLnurlPay(structs.Empty)returns (structs.HandleLnurlPayResponse) {
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/guest/lnurl_pay/handle";
|
||||
option (query) = {items: ["k1", "amount", "nostr", "lnurl"]};
|
||||
}
|
||||
rpc HandleLnurlAddress(structs.Empty)returns (structs.LnurlPayInfoResponse) {
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/.well-known/lnurlp/:address_name";
|
||||
}
|
||||
|
||||
rpc LinkNPubThroughToken(structs.LinkNPubThroughTokenRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "User";
|
||||
option(http_method) = "post";
|
||||
option (http_route) = "/api/guest/npub/link";
|
||||
option (nostr) = true;
|
||||
}
|
||||
//</Guest>
|
||||
|
||||
// <App>
|
||||
rpc GetApp(structs.Empty) returns (structs.Application) {
|
||||
option (auth_type) = "App";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/app/get";
|
||||
}
|
||||
|
||||
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.PayInvoiceResponse) {
|
||||
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";
|
||||
}
|
||||
|
||||
rpc GetAppUserLNURLInfo(structs.GetAppUserLNURLInfoRequest) returns (structs.LnurlPayInfoResponse) {
|
||||
option (auth_type) = "App";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/app/user/lnurl/pay/info";
|
||||
}
|
||||
rpc SetMockAppUserBalance(structs.SetMockAppUserBalanceRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "App";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/app/mock/user/blance/set";
|
||||
}
|
||||
rpc SetMockAppBalance(structs.SetMockAppBalanceRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "App";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/app/mock/blance/set";
|
||||
}
|
||||
rpc RequestNPubLinkingToken(structs.RequestNPubLinkingTokenRequest) returns (structs.RequestNPubLinkingTokenResponse) {
|
||||
option (auth_type) = "App";
|
||||
option(http_method) = "post";
|
||||
option (http_route) = "/api/app/user/npub/token";
|
||||
}
|
||||
// </App>
|
||||
|
||||
// <User>
|
||||
rpc UserHealth(structs.Empty)returns(structs.Empty){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/health";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc GetUserInfo(structs.Empty)returns(structs.UserInfo){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/info";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc AddProduct(structs.AddProductRequest) returns (structs.Product){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/product/add";
|
||||
option (nostr) = true;
|
||||
};
|
||||
|
||||
rpc NewProductInvoice(structs.Empty) returns (structs.NewInvoiceResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/user/product/get/invoice";
|
||||
option (query) = {items: ["id"]};
|
||||
option (nostr) = true;
|
||||
};
|
||||
|
||||
rpc GetUserOperations(structs.GetUserOperationsRequest) returns (structs.GetUserOperationsResponse) {
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/operations";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc NewAddress(structs.NewAddressRequest) returns (structs.NewAddressResponse) {
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/chain/new";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc PayAddress(structs.PayAddressRequest) returns (structs.PayAddressResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/chain/pay";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc NewInvoice(structs.NewInvoiceRequest) returns (structs.NewInvoiceResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/invoice/new";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc DecodeInvoice(structs.DecodeInvoiceRequest) returns (structs.DecodeInvoiceResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/invoice/decode";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc PayInvoice(structs.PayInvoiceRequest) returns (structs.PayInvoiceResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/invoice/pay";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc OpenChannel(structs.OpenChannelRequest) returns (structs.OpenChannelResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/open/channel";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetLnurlWithdrawLink(structs.Empty) returns (structs.LnurlLinkResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/user/lnurl_withdraw/link";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetLnurlPayLink(structs.Empty) returns (structs.LnurlLinkResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/user/lnurl_pay/link";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetLNURLChannelLink(structs.Empty) returns (structs.LnurlLinkResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/lnurl_channel/url";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc GetLiveUserOperations(structs.Empty) returns (stream structs.LiveUserOperation){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/operations/sub";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc GetMigrationUpdate(structs.Empty) returns (stream structs.MigrationUpdate){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/migrations/sub";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc GetHttpCreds(structs.Empty) returns (stream structs.HttpCreds){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/http_creds";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc BatchUser(structs.Empty) returns (structs.Empty){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/batch";
|
||||
option (nostr) = true;
|
||||
option (batch) = true;
|
||||
}
|
||||
// </User>
|
||||
syntax = "proto3";
|
||||
|
||||
package methods;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
import "structs.proto";
|
||||
|
||||
option go_package = "github.com/shocknet/lightning.pub";
|
||||
option (file_options) = {
|
||||
supported_http_methods:["post", "get"];
|
||||
supported_auths:[
|
||||
{
|
||||
id: "guest"
|
||||
name: "Guest"
|
||||
context:[]
|
||||
},
|
||||
{
|
||||
id: "user"
|
||||
name: "User",
|
||||
context:[{
|
||||
key:"user_id",
|
||||
value:"string"
|
||||
},{
|
||||
key:"app_id",
|
||||
value:"string"
|
||||
},{
|
||||
key:"app_user_id",
|
||||
value:"string"
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: "admin",
|
||||
name: "Admin",
|
||||
//encrypted:true,
|
||||
context:{
|
||||
key:"admin_id",
|
||||
value:"string"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "metrics",
|
||||
name: "Metrics",
|
||||
//encrypted:true,
|
||||
context:{
|
||||
key:"operator_id",
|
||||
value:"string"
|
||||
}
|
||||
},
|
||||
{
|
||||
id:"app",
|
||||
name:"App",
|
||||
context:{
|
||||
key:"app_id",
|
||||
value: "string"
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
message MethodQueryOptions {
|
||||
repeated string items = 1;
|
||||
}
|
||||
|
||||
extend google.protobuf.MethodOptions { // TODO: move this stuff to dep repo?
|
||||
string auth_type = 50003;
|
||||
string http_method = 50004;
|
||||
string http_route = 50005;
|
||||
MethodQueryOptions query = 50006;
|
||||
bool nostr = 50007;
|
||||
bool batch = 50008;
|
||||
|
||||
}
|
||||
|
||||
message ProtoFileOptions {
|
||||
message SupportedAuth {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
bool encrypted = 3;
|
||||
map<string,string> context = 4;
|
||||
}
|
||||
repeated SupportedAuth supported_auths = 1;
|
||||
repeated string supported_http_methods = 2;
|
||||
}
|
||||
|
||||
extend google.protobuf.FileOptions {
|
||||
ProtoFileOptions file_options = 50004;
|
||||
}
|
||||
|
||||
service LightningPub {
|
||||
// <Admin>
|
||||
rpc LndGetInfo(structs.LndGetInfoRequest) returns (structs.LndGetInfoResponse){
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/lnd/getinfo";
|
||||
};
|
||||
|
||||
rpc AddApp(structs.AddAppRequest) returns (structs.AuthApp) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/app/add";
|
||||
};
|
||||
|
||||
rpc AuthApp(structs.AuthAppRequest) returns (structs.AuthApp) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/app/auth";
|
||||
}
|
||||
|
||||
rpc BanUser(structs.BanUserRequest) returns (structs.BanUserResponse) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/user/ban";
|
||||
}
|
||||
|
||||
rpc GetUsageMetrics(structs.Empty) returns (structs.UsageMetrics) {
|
||||
option (auth_type) = "Metrics";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/reports/usage";
|
||||
}
|
||||
|
||||
rpc GetAppsMetrics(structs.AppsMetricsRequest) returns (structs.AppsMetrics) {
|
||||
option (auth_type) = "Metrics";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/reports/apps";
|
||||
}
|
||||
|
||||
rpc GetLndMetrics(structs.LndMetricsRequest) returns (structs.LndMetrics) {
|
||||
option (auth_type) = "Metrics";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/reports/lnd";
|
||||
}
|
||||
|
||||
|
||||
// </Admin>
|
||||
|
||||
// <Guest>
|
||||
rpc Health(structs.Empty) returns (structs.Empty){
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/health";
|
||||
};
|
||||
rpc EncryptionExchange(structs.EncryptionExchangeRequest) returns (structs.Empty){
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/encryption/exchange";
|
||||
};
|
||||
|
||||
rpc SetMockInvoiceAsPaid(structs.SetMockInvoiceAsPaidRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/lnd/mock/invoice/paid";
|
||||
}
|
||||
rpc GetLnurlWithdrawInfo(structs.Empty) returns (structs.LnurlWithdrawInfoResponse){
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/guest/lnurl_withdraw/info";
|
||||
option (query) = {items: ["k1"]};
|
||||
}
|
||||
rpc HandleLnurlWithdraw(structs.Empty) returns (structs.Empty){
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/guest/lnurl_withdraw/handle";
|
||||
option (query) = {items: ["k1", "pr"]};
|
||||
}
|
||||
rpc GetLnurlPayInfo(structs.Empty)returns (structs.LnurlPayInfoResponse) {
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/guest/lnurl_pay/info";
|
||||
option (query) = {items: ["k1"]};
|
||||
}
|
||||
rpc HandleLnurlPay(structs.Empty)returns (structs.HandleLnurlPayResponse) {
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/guest/lnurl_pay/handle";
|
||||
option (query) = {items: ["k1", "amount", "nostr", "lnurl"]};
|
||||
}
|
||||
rpc HandleLnurlAddress(structs.Empty)returns (structs.LnurlPayInfoResponse) {
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/.well-known/lnurlp/:address_name";
|
||||
}
|
||||
|
||||
rpc LinkNPubThroughToken(structs.LinkNPubThroughTokenRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "User";
|
||||
option(http_method) = "post";
|
||||
option (http_route) = "/api/guest/npub/link";
|
||||
option (nostr) = true;
|
||||
}
|
||||
//</Guest>
|
||||
|
||||
// <App>
|
||||
rpc GetApp(structs.Empty) returns (structs.Application) {
|
||||
option (auth_type) = "App";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/app/get";
|
||||
}
|
||||
|
||||
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.PayInvoiceResponse) {
|
||||
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";
|
||||
}
|
||||
|
||||
rpc GetAppUserLNURLInfo(structs.GetAppUserLNURLInfoRequest) returns (structs.LnurlPayInfoResponse) {
|
||||
option (auth_type) = "App";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/app/user/lnurl/pay/info";
|
||||
}
|
||||
rpc SetMockAppUserBalance(structs.SetMockAppUserBalanceRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "App";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/app/mock/user/blance/set";
|
||||
}
|
||||
rpc SetMockAppBalance(structs.SetMockAppBalanceRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "App";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/app/mock/blance/set";
|
||||
}
|
||||
rpc RequestNPubLinkingToken(structs.RequestNPubLinkingTokenRequest) returns (structs.RequestNPubLinkingTokenResponse) {
|
||||
option (auth_type) = "App";
|
||||
option(http_method) = "post";
|
||||
option (http_route) = "/api/app/user/npub/token";
|
||||
}
|
||||
// </App>
|
||||
|
||||
// <User>
|
||||
rpc UserHealth(structs.Empty)returns(structs.Empty){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/health";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc GetUserInfo(structs.Empty)returns(structs.UserInfo){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/info";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc AddProduct(structs.AddProductRequest) returns (structs.Product){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/product/add";
|
||||
option (nostr) = true;
|
||||
};
|
||||
|
||||
rpc NewProductInvoice(structs.Empty) returns (structs.NewInvoiceResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/user/product/get/invoice";
|
||||
option (query) = {items: ["id"]};
|
||||
option (nostr) = true;
|
||||
};
|
||||
|
||||
rpc GetUserOperations(structs.GetUserOperationsRequest) returns (structs.GetUserOperationsResponse) {
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/operations";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc NewAddress(structs.NewAddressRequest) returns (structs.NewAddressResponse) {
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/chain/new";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc PayAddress(structs.PayAddressRequest) returns (structs.PayAddressResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/chain/pay";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc NewInvoice(structs.NewInvoiceRequest) returns (structs.NewInvoiceResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/invoice/new";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc DecodeInvoice(structs.DecodeInvoiceRequest) returns (structs.DecodeInvoiceResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/invoice/decode";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc PayInvoice(structs.PayInvoiceRequest) returns (structs.PayInvoiceResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/invoice/pay";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc OpenChannel(structs.OpenChannelRequest) returns (structs.OpenChannelResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/open/channel";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetLnurlWithdrawLink(structs.Empty) returns (structs.LnurlLinkResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/user/lnurl_withdraw/link";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetLnurlPayLink(structs.Empty) returns (structs.LnurlLinkResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/user/lnurl_pay/link";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetLNURLChannelLink(structs.Empty) returns (structs.LnurlLinkResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/lnurl_channel/url";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc GetLiveUserOperations(structs.Empty) returns (stream structs.LiveUserOperation){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/operations/sub";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc GetMigrationUpdate(structs.Empty) returns (stream structs.MigrationUpdate){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/migrations/sub";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc GetHttpCreds(structs.Empty) returns (stream structs.HttpCreds){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/http_creds";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc BatchUser(structs.Empty) returns (structs.Empty){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/batch";
|
||||
option (nostr) = true;
|
||||
option (batch) = true;
|
||||
}
|
||||
// </User>
|
||||
}
|
||||
|
|
@ -1,449 +1,449 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package structs;
|
||||
|
||||
option go_package = "github.com/shocknet/lightning.pub";
|
||||
|
||||
message Empty {}
|
||||
|
||||
|
||||
message EncryptionExchangeRequest {
|
||||
string publicKey = 1;
|
||||
string deviceId = 2;
|
||||
}
|
||||
|
||||
message UsageMetric {
|
||||
int64 processed_at_ms = 1;
|
||||
int64 parsed_in_nano = 2;
|
||||
int64 auth_in_nano = 3;
|
||||
int64 validate_in_nano = 4;
|
||||
int64 handle_in_nano = 5;
|
||||
string rpc_name = 6;
|
||||
bool batch = 7;
|
||||
bool nostr = 8;
|
||||
int64 batch_size = 9;
|
||||
}
|
||||
|
||||
message UsageMetrics {
|
||||
repeated UsageMetric metrics = 1;
|
||||
}
|
||||
|
||||
message AppsMetricsRequest {
|
||||
optional int64 from_unix = 1;
|
||||
optional int64 to_unix = 2;
|
||||
optional bool include_operations = 3;
|
||||
}
|
||||
|
||||
message UsersInfo {
|
||||
int64 total = 1;
|
||||
int64 no_balance = 2;
|
||||
int64 negative_balance = 3;
|
||||
int64 always_been_inactive = 4;
|
||||
|
||||
int64 balance_avg = 5;
|
||||
int64 balance_median = 6;
|
||||
|
||||
}
|
||||
|
||||
message AppMetrics {
|
||||
Application app = 1;
|
||||
|
||||
UsersInfo users = 2;
|
||||
|
||||
int64 received = 5;
|
||||
int64 spent = 6;
|
||||
int64 available = 7;
|
||||
int64 fees = 8;
|
||||
int64 invoices = 9;
|
||||
|
||||
int64 total_fees = 10;
|
||||
|
||||
repeated UserOperation operations = 100;
|
||||
}
|
||||
|
||||
message AppsMetrics {
|
||||
repeated AppMetrics apps = 1;
|
||||
}
|
||||
|
||||
message LndMetricsRequest {
|
||||
optional int64 from_unix = 1;
|
||||
optional int64 to_unix = 2;
|
||||
}
|
||||
|
||||
message RoutingEvent {
|
||||
int64 incoming_channel_id = 1;
|
||||
int64 incoming_htlc_id=2;
|
||||
int64 outgoing_channel_id = 3;
|
||||
int64 outgoing_htlc_id =4;
|
||||
int64 timestamp_ns = 5;
|
||||
string event_type = 6;
|
||||
int64 incoming_amt_msat = 7;
|
||||
int64 outgoing_amt_msat = 8;
|
||||
string failure_string = 9;
|
||||
bool settled = 10;
|
||||
bool offchain = 11;
|
||||
bool forward_fail_event = 12;
|
||||
}
|
||||
message ChannelBalanceEvent {
|
||||
int64 block_height = 1;
|
||||
string channel_id = 2;
|
||||
int64 local_balance_sats = 3;
|
||||
int64 remote_balance_sats = 4;
|
||||
}
|
||||
|
||||
message ChainBalanceEvent {
|
||||
int64 block_height = 1;
|
||||
int64 confirmed_balance = 2;
|
||||
int64 unconfirmed_balance = 3;
|
||||
int64 total_balance = 4;
|
||||
}
|
||||
|
||||
message OpenChannel {
|
||||
string channel_id = 1;
|
||||
int64 capacity = 2;
|
||||
bool active = 3;
|
||||
int64 lifetime =4 ;
|
||||
int64 local_balance=5;
|
||||
int64 remote_balance = 6;
|
||||
}
|
||||
message ClosedChannel {
|
||||
string channel_id = 1;
|
||||
int64 capacity = 2;
|
||||
int64 closed_height =4;
|
||||
}
|
||||
|
||||
message ChannelRouting {
|
||||
string channel_id = 1;
|
||||
int64 send_errors = 2;
|
||||
int64 receive_errors = 3;
|
||||
int64 forward_errors_as_input = 4;
|
||||
int64 forward_errors_as_output = 5;
|
||||
int64 missed_forward_fee_as_input = 6;
|
||||
int64 missed_forward_fee_as_output = 7;
|
||||
int64 forward_fee_as_input = 8;
|
||||
int64 forward_fee_as_output = 9;
|
||||
int64 events_number = 10;
|
||||
}
|
||||
|
||||
message LndNodeMetrics {
|
||||
repeated ChannelBalanceEvent channels_balance_events = 1;
|
||||
repeated ChainBalanceEvent chain_balance_events = 2;
|
||||
int64 offline_channels = 4;
|
||||
int64 online_channels = 5;
|
||||
int64 pending_channels = 6;
|
||||
int64 closing_channels = 7;
|
||||
repeated OpenChannel open_channels = 8;
|
||||
repeated ClosedChannel closed_channels = 9;
|
||||
repeated ChannelRouting channel_routing = 10;
|
||||
}
|
||||
|
||||
message LndMetrics {
|
||||
repeated LndNodeMetrics nodes = 1;
|
||||
|
||||
}
|
||||
|
||||
message LndGetInfoRequest {
|
||||
int64 nodeId = 1;
|
||||
}
|
||||
|
||||
message SetMockInvoiceAsPaidRequest {
|
||||
string invoice = 1;
|
||||
int64 amount =2;
|
||||
}
|
||||
|
||||
message LndGetInfoResponse {
|
||||
string alias = 1;
|
||||
}
|
||||
|
||||
message BanUserRequest {
|
||||
string user_id = 1;
|
||||
}
|
||||
message BannedAppUser {
|
||||
string app_name = 1;
|
||||
string app_id = 2;
|
||||
string user_identifier = 3;
|
||||
string nostr_pub = 4;
|
||||
|
||||
}
|
||||
message BanUserResponse {
|
||||
int64 balance_sats = 1;
|
||||
repeated BannedAppUser banned_app_users = 2;
|
||||
}
|
||||
|
||||
message AddAppRequest {
|
||||
string name = 1;
|
||||
bool allow_user_creation = 2;
|
||||
}
|
||||
|
||||
message AuthAppRequest {
|
||||
string name = 1;
|
||||
optional bool allow_user_creation = 2;
|
||||
}
|
||||
|
||||
message Application {
|
||||
string name = 1;
|
||||
string id = 2;
|
||||
int64 balance = 3;
|
||||
string npub = 4;
|
||||
}
|
||||
|
||||
message AuthApp {
|
||||
Application app = 1;
|
||||
string auth_token = 2;
|
||||
}
|
||||
|
||||
|
||||
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 SendAppUserToAppUserPaymentRequest {
|
||||
string from_user_identifier = 1;
|
||||
string to_user_identifier = 2;
|
||||
int64 amount = 3;
|
||||
}
|
||||
|
||||
message SendAppUserToAppPaymentRequest {
|
||||
string from_user_identifier = 1;
|
||||
int64 amount = 2;
|
||||
}
|
||||
|
||||
message GetAppUserLNURLInfoRequest {
|
||||
string user_identifier = 1;
|
||||
string base_url_override = 2;
|
||||
}
|
||||
|
||||
message SetMockAppUserBalanceRequest {
|
||||
string user_identifier = 1;
|
||||
int64 amount = 2;
|
||||
}
|
||||
|
||||
message SetMockAppBalanceRequest {
|
||||
int64 amount = 1;
|
||||
}
|
||||
|
||||
enum AddressType {
|
||||
WITNESS_PUBKEY_HASH = 0;
|
||||
NESTED_PUBKEY_HASH = 1;
|
||||
TAPROOT_PUBKEY = 2;
|
||||
}
|
||||
message NewAddressRequest {
|
||||
AddressType addressType = 1;
|
||||
}
|
||||
message NewAddressResponse{
|
||||
string address = 1;
|
||||
}
|
||||
message PayAddressRequest{
|
||||
string address = 1;
|
||||
int64 amoutSats = 2;
|
||||
int64 satsPerVByte = 3;
|
||||
}
|
||||
|
||||
message PayAddressResponse{
|
||||
string txId = 1;
|
||||
string operation_id = 2;
|
||||
int64 service_fee = 3;
|
||||
int64 network_fee = 4;
|
||||
}
|
||||
|
||||
message NewInvoiceRequest{
|
||||
int64 amountSats = 1;
|
||||
string memo = 2;
|
||||
}
|
||||
|
||||
message NewInvoiceResponse{
|
||||
string invoice = 1;
|
||||
}
|
||||
message DecodeInvoiceRequest{
|
||||
string invoice = 1;
|
||||
}
|
||||
message DecodeInvoiceResponse{
|
||||
int64 amount=1;
|
||||
}
|
||||
message PayInvoiceRequest{
|
||||
string invoice = 1;
|
||||
int64 amount = 2;
|
||||
}
|
||||
|
||||
message PayInvoiceResponse{
|
||||
string preimage = 1;
|
||||
int64 amount_paid = 2;
|
||||
string operation_id = 3;
|
||||
int64 service_fee = 4;
|
||||
int64 network_fee = 5;
|
||||
}
|
||||
|
||||
message OpenChannelRequest{
|
||||
string destination = 1;
|
||||
int64 fundingAmount = 2;
|
||||
int64 pushAmount = 3;
|
||||
string closeAddress = 4;
|
||||
}
|
||||
|
||||
message OpenChannelResponse{
|
||||
string channelId = 1;
|
||||
}
|
||||
|
||||
message LnurlLinkResponse{
|
||||
string lnurl = 1;
|
||||
string k1 = 2;
|
||||
}
|
||||
|
||||
message LnurlWithdrawInfoResponse {
|
||||
string tag = 1;
|
||||
string callback = 2;
|
||||
string k1 = 3;
|
||||
string defaultDescription = 4;
|
||||
int64 minWithdrawable = 5; // millisatoshi - unsafe overflow possible, but very unlikely
|
||||
int64 maxWithdrawable = 6; // millisatoshi - unsafe overflow possible, but very unlikely
|
||||
string balanceCheck = 7;
|
||||
string payLink = 8;
|
||||
}
|
||||
message LnurlPayInfoResponse {
|
||||
string tag = 1;
|
||||
string callback = 2;
|
||||
int64 maxSendable = 3; // millisatoshi - unsafe overflow possible, but very unlikely
|
||||
int64 minSendable = 4; // millisatoshi - unsafe overflow possible, but very unlikely
|
||||
string metadata = 5;
|
||||
bool allowsNostr = 6;
|
||||
string nostrPubkey = 7;
|
||||
}
|
||||
message HandleLnurlPayResponse {
|
||||
string pr = 1;
|
||||
repeated Empty routes = 2;
|
||||
}
|
||||
|
||||
message UserInfo{
|
||||
string userId = 1;
|
||||
int64 balance = 2;
|
||||
int64 max_withdrawable = 3;
|
||||
string user_identifier = 4;
|
||||
}
|
||||
|
||||
message GetUserOperationsRequest{
|
||||
int64 latestIncomingInvoice = 1;
|
||||
int64 latestOutgoingInvoice = 2;
|
||||
int64 latestIncomingTx = 3;
|
||||
int64 latestOutgoingTx = 4;
|
||||
int64 latestIncomingUserToUserPayment = 5;
|
||||
int64 latestOutgoingUserToUserPayment = 6;
|
||||
int64 max_size = 7;
|
||||
}
|
||||
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;
|
||||
bool inbound =3;
|
||||
int64 amount = 4;
|
||||
string identifier = 5;
|
||||
string operationId = 6;
|
||||
int64 service_fee = 7;
|
||||
int64 network_fee = 8;
|
||||
bool confirmed = 9;
|
||||
string tx_hash = 10;
|
||||
bool internal = 11;
|
||||
}
|
||||
message UserOperations {
|
||||
int64 fromIndex=1;
|
||||
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 {
|
||||
string name = 1;
|
||||
int64 price_sats = 2;
|
||||
}
|
||||
|
||||
message Product {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
int64 price_sats = 3;
|
||||
}
|
||||
|
||||
message GetProductBuyLinkResponse {
|
||||
string link = 1;
|
||||
}
|
||||
|
||||
message LiveUserOperation {
|
||||
UserOperation operation = 1;
|
||||
}
|
||||
message MigrationUpdate {
|
||||
optional ClosureMigration closure = 1;
|
||||
optional RelaysMigration relays = 2;
|
||||
}
|
||||
|
||||
message ClosureMigration {
|
||||
int64 closes_at_unix = 1;
|
||||
}
|
||||
|
||||
message RelaysMigration {
|
||||
repeated string relays = 1;
|
||||
}
|
||||
|
||||
|
||||
message RequestNPubLinkingTokenRequest {
|
||||
string user_identifier = 1;
|
||||
}
|
||||
|
||||
message RequestNPubLinkingTokenResponse {
|
||||
string token = 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
message LinkNPubThroughTokenRequest {
|
||||
string token = 1;
|
||||
string nostr_pub = 2;
|
||||
}
|
||||
|
||||
message HttpCreds {
|
||||
string url = 1;
|
||||
string token = 2;
|
||||
syntax = "proto3";
|
||||
|
||||
package structs;
|
||||
|
||||
option go_package = "github.com/shocknet/lightning.pub";
|
||||
|
||||
message Empty {}
|
||||
|
||||
|
||||
message EncryptionExchangeRequest {
|
||||
string publicKey = 1;
|
||||
string deviceId = 2;
|
||||
}
|
||||
|
||||
message UsageMetric {
|
||||
int64 processed_at_ms = 1;
|
||||
int64 parsed_in_nano = 2;
|
||||
int64 auth_in_nano = 3;
|
||||
int64 validate_in_nano = 4;
|
||||
int64 handle_in_nano = 5;
|
||||
string rpc_name = 6;
|
||||
bool batch = 7;
|
||||
bool nostr = 8;
|
||||
int64 batch_size = 9;
|
||||
}
|
||||
|
||||
message UsageMetrics {
|
||||
repeated UsageMetric metrics = 1;
|
||||
}
|
||||
|
||||
message AppsMetricsRequest {
|
||||
optional int64 from_unix = 1;
|
||||
optional int64 to_unix = 2;
|
||||
optional bool include_operations = 3;
|
||||
}
|
||||
|
||||
message UsersInfo {
|
||||
int64 total = 1;
|
||||
int64 no_balance = 2;
|
||||
int64 negative_balance = 3;
|
||||
int64 always_been_inactive = 4;
|
||||
|
||||
int64 balance_avg = 5;
|
||||
int64 balance_median = 6;
|
||||
|
||||
}
|
||||
|
||||
message AppMetrics {
|
||||
Application app = 1;
|
||||
|
||||
UsersInfo users = 2;
|
||||
|
||||
int64 received = 5;
|
||||
int64 spent = 6;
|
||||
int64 available = 7;
|
||||
int64 fees = 8;
|
||||
int64 invoices = 9;
|
||||
|
||||
int64 total_fees = 10;
|
||||
|
||||
repeated UserOperation operations = 100;
|
||||
}
|
||||
|
||||
message AppsMetrics {
|
||||
repeated AppMetrics apps = 1;
|
||||
}
|
||||
|
||||
message LndMetricsRequest {
|
||||
optional int64 from_unix = 1;
|
||||
optional int64 to_unix = 2;
|
||||
}
|
||||
|
||||
message RoutingEvent {
|
||||
int64 incoming_channel_id = 1;
|
||||
int64 incoming_htlc_id=2;
|
||||
int64 outgoing_channel_id = 3;
|
||||
int64 outgoing_htlc_id =4;
|
||||
int64 timestamp_ns = 5;
|
||||
string event_type = 6;
|
||||
int64 incoming_amt_msat = 7;
|
||||
int64 outgoing_amt_msat = 8;
|
||||
string failure_string = 9;
|
||||
bool settled = 10;
|
||||
bool offchain = 11;
|
||||
bool forward_fail_event = 12;
|
||||
}
|
||||
message ChannelBalanceEvent {
|
||||
int64 block_height = 1;
|
||||
string channel_id = 2;
|
||||
int64 local_balance_sats = 3;
|
||||
int64 remote_balance_sats = 4;
|
||||
}
|
||||
|
||||
message ChainBalanceEvent {
|
||||
int64 block_height = 1;
|
||||
int64 confirmed_balance = 2;
|
||||
int64 unconfirmed_balance = 3;
|
||||
int64 total_balance = 4;
|
||||
}
|
||||
|
||||
message OpenChannel {
|
||||
string channel_id = 1;
|
||||
int64 capacity = 2;
|
||||
bool active = 3;
|
||||
int64 lifetime =4 ;
|
||||
int64 local_balance=5;
|
||||
int64 remote_balance = 6;
|
||||
}
|
||||
message ClosedChannel {
|
||||
string channel_id = 1;
|
||||
int64 capacity = 2;
|
||||
int64 closed_height =4;
|
||||
}
|
||||
|
||||
message ChannelRouting {
|
||||
string channel_id = 1;
|
||||
int64 send_errors = 2;
|
||||
int64 receive_errors = 3;
|
||||
int64 forward_errors_as_input = 4;
|
||||
int64 forward_errors_as_output = 5;
|
||||
int64 missed_forward_fee_as_input = 6;
|
||||
int64 missed_forward_fee_as_output = 7;
|
||||
int64 forward_fee_as_input = 8;
|
||||
int64 forward_fee_as_output = 9;
|
||||
int64 events_number = 10;
|
||||
}
|
||||
|
||||
message LndNodeMetrics {
|
||||
repeated ChannelBalanceEvent channels_balance_events = 1;
|
||||
repeated ChainBalanceEvent chain_balance_events = 2;
|
||||
int64 offline_channels = 4;
|
||||
int64 online_channels = 5;
|
||||
int64 pending_channels = 6;
|
||||
int64 closing_channels = 7;
|
||||
repeated OpenChannel open_channels = 8;
|
||||
repeated ClosedChannel closed_channels = 9;
|
||||
repeated ChannelRouting channel_routing = 10;
|
||||
}
|
||||
|
||||
message LndMetrics {
|
||||
repeated LndNodeMetrics nodes = 1;
|
||||
|
||||
}
|
||||
|
||||
message LndGetInfoRequest {
|
||||
int64 nodeId = 1;
|
||||
}
|
||||
|
||||
message SetMockInvoiceAsPaidRequest {
|
||||
string invoice = 1;
|
||||
int64 amount =2;
|
||||
}
|
||||
|
||||
message LndGetInfoResponse {
|
||||
string alias = 1;
|
||||
}
|
||||
|
||||
message BanUserRequest {
|
||||
string user_id = 1;
|
||||
}
|
||||
message BannedAppUser {
|
||||
string app_name = 1;
|
||||
string app_id = 2;
|
||||
string user_identifier = 3;
|
||||
string nostr_pub = 4;
|
||||
|
||||
}
|
||||
message BanUserResponse {
|
||||
int64 balance_sats = 1;
|
||||
repeated BannedAppUser banned_app_users = 2;
|
||||
}
|
||||
|
||||
message AddAppRequest {
|
||||
string name = 1;
|
||||
bool allow_user_creation = 2;
|
||||
}
|
||||
|
||||
message AuthAppRequest {
|
||||
string name = 1;
|
||||
optional bool allow_user_creation = 2;
|
||||
}
|
||||
|
||||
message Application {
|
||||
string name = 1;
|
||||
string id = 2;
|
||||
int64 balance = 3;
|
||||
string npub = 4;
|
||||
}
|
||||
|
||||
message AuthApp {
|
||||
Application app = 1;
|
||||
string auth_token = 2;
|
||||
}
|
||||
|
||||
|
||||
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 SendAppUserToAppUserPaymentRequest {
|
||||
string from_user_identifier = 1;
|
||||
string to_user_identifier = 2;
|
||||
int64 amount = 3;
|
||||
}
|
||||
|
||||
message SendAppUserToAppPaymentRequest {
|
||||
string from_user_identifier = 1;
|
||||
int64 amount = 2;
|
||||
}
|
||||
|
||||
message GetAppUserLNURLInfoRequest {
|
||||
string user_identifier = 1;
|
||||
string base_url_override = 2;
|
||||
}
|
||||
|
||||
message SetMockAppUserBalanceRequest {
|
||||
string user_identifier = 1;
|
||||
int64 amount = 2;
|
||||
}
|
||||
|
||||
message SetMockAppBalanceRequest {
|
||||
int64 amount = 1;
|
||||
}
|
||||
|
||||
enum AddressType {
|
||||
WITNESS_PUBKEY_HASH = 0;
|
||||
NESTED_PUBKEY_HASH = 1;
|
||||
TAPROOT_PUBKEY = 2;
|
||||
}
|
||||
message NewAddressRequest {
|
||||
AddressType addressType = 1;
|
||||
}
|
||||
message NewAddressResponse{
|
||||
string address = 1;
|
||||
}
|
||||
message PayAddressRequest{
|
||||
string address = 1;
|
||||
int64 amoutSats = 2;
|
||||
int64 satsPerVByte = 3;
|
||||
}
|
||||
|
||||
message PayAddressResponse{
|
||||
string txId = 1;
|
||||
string operation_id = 2;
|
||||
int64 service_fee = 3;
|
||||
int64 network_fee = 4;
|
||||
}
|
||||
|
||||
message NewInvoiceRequest{
|
||||
int64 amountSats = 1;
|
||||
string memo = 2;
|
||||
}
|
||||
|
||||
message NewInvoiceResponse{
|
||||
string invoice = 1;
|
||||
}
|
||||
message DecodeInvoiceRequest{
|
||||
string invoice = 1;
|
||||
}
|
||||
message DecodeInvoiceResponse{
|
||||
int64 amount=1;
|
||||
}
|
||||
message PayInvoiceRequest{
|
||||
string invoice = 1;
|
||||
int64 amount = 2;
|
||||
}
|
||||
|
||||
message PayInvoiceResponse{
|
||||
string preimage = 1;
|
||||
int64 amount_paid = 2;
|
||||
string operation_id = 3;
|
||||
int64 service_fee = 4;
|
||||
int64 network_fee = 5;
|
||||
}
|
||||
|
||||
message OpenChannelRequest{
|
||||
string destination = 1;
|
||||
int64 fundingAmount = 2;
|
||||
int64 pushAmount = 3;
|
||||
string closeAddress = 4;
|
||||
}
|
||||
|
||||
message OpenChannelResponse{
|
||||
string channelId = 1;
|
||||
}
|
||||
|
||||
message LnurlLinkResponse{
|
||||
string lnurl = 1;
|
||||
string k1 = 2;
|
||||
}
|
||||
|
||||
message LnurlWithdrawInfoResponse {
|
||||
string tag = 1;
|
||||
string callback = 2;
|
||||
string k1 = 3;
|
||||
string defaultDescription = 4;
|
||||
int64 minWithdrawable = 5; // millisatoshi - unsafe overflow possible, but very unlikely
|
||||
int64 maxWithdrawable = 6; // millisatoshi - unsafe overflow possible, but very unlikely
|
||||
string balanceCheck = 7;
|
||||
string payLink = 8;
|
||||
}
|
||||
message LnurlPayInfoResponse {
|
||||
string tag = 1;
|
||||
string callback = 2;
|
||||
int64 maxSendable = 3; // millisatoshi - unsafe overflow possible, but very unlikely
|
||||
int64 minSendable = 4; // millisatoshi - unsafe overflow possible, but very unlikely
|
||||
string metadata = 5;
|
||||
bool allowsNostr = 6;
|
||||
string nostrPubkey = 7;
|
||||
}
|
||||
message HandleLnurlPayResponse {
|
||||
string pr = 1;
|
||||
repeated Empty routes = 2;
|
||||
}
|
||||
|
||||
message UserInfo{
|
||||
string userId = 1;
|
||||
int64 balance = 2;
|
||||
int64 max_withdrawable = 3;
|
||||
string user_identifier = 4;
|
||||
}
|
||||
|
||||
message GetUserOperationsRequest{
|
||||
int64 latestIncomingInvoice = 1;
|
||||
int64 latestOutgoingInvoice = 2;
|
||||
int64 latestIncomingTx = 3;
|
||||
int64 latestOutgoingTx = 4;
|
||||
int64 latestIncomingUserToUserPayment = 5;
|
||||
int64 latestOutgoingUserToUserPayment = 6;
|
||||
int64 max_size = 7;
|
||||
}
|
||||
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;
|
||||
bool inbound =3;
|
||||
int64 amount = 4;
|
||||
string identifier = 5;
|
||||
string operationId = 6;
|
||||
int64 service_fee = 7;
|
||||
int64 network_fee = 8;
|
||||
bool confirmed = 9;
|
||||
string tx_hash = 10;
|
||||
bool internal = 11;
|
||||
}
|
||||
message UserOperations {
|
||||
int64 fromIndex=1;
|
||||
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 {
|
||||
string name = 1;
|
||||
int64 price_sats = 2;
|
||||
}
|
||||
|
||||
message Product {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
int64 price_sats = 3;
|
||||
}
|
||||
|
||||
message GetProductBuyLinkResponse {
|
||||
string link = 1;
|
||||
}
|
||||
|
||||
message LiveUserOperation {
|
||||
UserOperation operation = 1;
|
||||
}
|
||||
message MigrationUpdate {
|
||||
optional ClosureMigration closure = 1;
|
||||
optional RelaysMigration relays = 2;
|
||||
}
|
||||
|
||||
message ClosureMigration {
|
||||
int64 closes_at_unix = 1;
|
||||
}
|
||||
|
||||
message RelaysMigration {
|
||||
repeated string relays = 1;
|
||||
}
|
||||
|
||||
|
||||
message RequestNPubLinkingTokenRequest {
|
||||
string user_identifier = 1;
|
||||
}
|
||||
|
||||
message RequestNPubLinkingTokenResponse {
|
||||
string token = 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
message LinkNPubThroughTokenRequest {
|
||||
string token = 1;
|
||||
string nostr_pub = 2;
|
||||
}
|
||||
|
||||
message HttpCreds {
|
||||
string url = 1;
|
||||
string token = 2;
|
||||
}
|
||||
142
src/auth.ts
142
src/auth.ts
|
|
@ -1,72 +1,72 @@
|
|||
import express from 'express';
|
||||
import path from 'path';
|
||||
import { ServerOptions } from "../proto/autogenerated/ts/express_server";
|
||||
import { AdminContext, MetricsContext } from "../proto/autogenerated/ts/types";
|
||||
import Main from './services/main'
|
||||
import { ERROR, getLogger } from './services/helpers/logger.js'
|
||||
const serverOptions = (mainHandler: Main): ServerOptions => {
|
||||
const log = getLogger({})
|
||||
return {
|
||||
logger: { log, error: err => log(ERROR, err) },
|
||||
staticFiles: path.resolve('static'),
|
||||
AdminAuthGuard: adminAuth,
|
||||
MetricsAuthGuard: metricsAuth,
|
||||
AppAuthGuard: async (authHeader) => { return { app_id: mainHandler.applicationManager.DecodeAppToken(stripBearer(authHeader)) } },
|
||||
UserAuthGuard: async (authHeader) => { return mainHandler.appUserManager.DecodeUserToken(stripBearer(authHeader)) },
|
||||
GuestAuthGuard: async (_) => ({}),
|
||||
metricsCallback: metrics => mainHandler.settings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
|
||||
allowCors: true,
|
||||
logMethod: true,
|
||||
logBody: true
|
||||
//throwErrors: true
|
||||
}
|
||||
}
|
||||
|
||||
const stripBearer = (header?: string) => {
|
||||
if (!header) {
|
||||
return ""
|
||||
}
|
||||
if (header.startsWith("Bearer ")) {
|
||||
return header.substring("Bearer ".length)
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
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" }
|
||||
}
|
||||
|
||||
const metricsAuth = async (header: string | undefined): Promise<MetricsContext> => {
|
||||
const metricsToken = process.env.METRICS_TOKEN || process.env.ADMIN_TOKEN
|
||||
if (!metricsToken) {
|
||||
throw new Error("metrics auth disabled")
|
||||
}
|
||||
if (!header) {
|
||||
throw new Error("metrics header not found")
|
||||
}
|
||||
let h = header
|
||||
|
||||
if (header.startsWith("Bearer ")) {
|
||||
h = header.substring("Bearer ".length)
|
||||
}
|
||||
if (h !== metricsToken) {
|
||||
throw new Error("metrics token invalid")
|
||||
}
|
||||
return { operator_id: "metrics1" }
|
||||
}
|
||||
import express from 'express';
|
||||
import path from 'path';
|
||||
import { ServerOptions } from "../proto/autogenerated/ts/express_server";
|
||||
import { AdminContext, MetricsContext } from "../proto/autogenerated/ts/types";
|
||||
import Main from './services/main'
|
||||
import { ERROR, getLogger } from './services/helpers/logger.js'
|
||||
const serverOptions = (mainHandler: Main): ServerOptions => {
|
||||
const log = getLogger({})
|
||||
return {
|
||||
logger: { log, error: err => log(ERROR, err) },
|
||||
staticFiles: path.resolve('static'),
|
||||
AdminAuthGuard: adminAuth,
|
||||
MetricsAuthGuard: metricsAuth,
|
||||
AppAuthGuard: async (authHeader) => { return { app_id: mainHandler.applicationManager.DecodeAppToken(stripBearer(authHeader)) } },
|
||||
UserAuthGuard: async (authHeader) => { return mainHandler.appUserManager.DecodeUserToken(stripBearer(authHeader)) },
|
||||
GuestAuthGuard: async (_) => ({}),
|
||||
metricsCallback: metrics => mainHandler.settings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
|
||||
allowCors: true,
|
||||
logMethod: true,
|
||||
logBody: true
|
||||
//throwErrors: true
|
||||
}
|
||||
}
|
||||
|
||||
const stripBearer = (header?: string) => {
|
||||
if (!header) {
|
||||
return ""
|
||||
}
|
||||
if (header.startsWith("Bearer ")) {
|
||||
return header.substring("Bearer ".length)
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
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" }
|
||||
}
|
||||
|
||||
const metricsAuth = async (header: string | undefined): Promise<MetricsContext> => {
|
||||
const metricsToken = process.env.METRICS_TOKEN || process.env.ADMIN_TOKEN
|
||||
if (!metricsToken) {
|
||||
throw new Error("metrics auth disabled")
|
||||
}
|
||||
if (!header) {
|
||||
throw new Error("metrics header not found")
|
||||
}
|
||||
let h = header
|
||||
|
||||
if (header.startsWith("Bearer ")) {
|
||||
h = header.substring("Bearer ".length)
|
||||
}
|
||||
if (h !== metricsToken) {
|
||||
throw new Error("metrics token invalid")
|
||||
}
|
||||
return { operator_id: "metrics1" }
|
||||
}
|
||||
export default serverOptions
|
||||
|
|
@ -1,88 +1,88 @@
|
|||
/*
|
||||
This file contains functions that deal with encoding and decoding nprofiles,
|
||||
but with he addition of bridge urls in the nprofile.
|
||||
These functions are basically the same functions from nostr-tools package
|
||||
but with some tweaks to allow for the bridge inclusion.
|
||||
*/
|
||||
import { bytesToHex, concatBytes, hexToBytes } from '@noble/hashes/utils';
|
||||
import { bech32 } from 'bech32';
|
||||
|
||||
export const utf8Decoder = new TextDecoder('utf-8')
|
||||
export const utf8Encoder = new TextEncoder()
|
||||
|
||||
|
||||
export type CustomProfilePointer = {
|
||||
pubkey: string
|
||||
relays?: string[]
|
||||
bridge?: string[] // one bridge
|
||||
}
|
||||
|
||||
|
||||
|
||||
type TLV = { [t: number]: Uint8Array[] }
|
||||
|
||||
|
||||
const encodeTLV = (tlv: TLV): Uint8Array => {
|
||||
const entries: Uint8Array[] = []
|
||||
|
||||
Object.entries(tlv)
|
||||
/*
|
||||
the original function does a reverse() here,
|
||||
but here it causes the nprofile string to be different,
|
||||
even though it would still decode to the correct original inputs
|
||||
*/
|
||||
//.reverse()
|
||||
.forEach(([t, vs]) => {
|
||||
vs.forEach(v => {
|
||||
const entry = new Uint8Array(v.length + 2)
|
||||
entry.set([parseInt(t)], 0)
|
||||
entry.set([v.length], 1)
|
||||
entry.set(v, 2)
|
||||
entries.push(entry)
|
||||
})
|
||||
})
|
||||
return concatBytes(...entries);
|
||||
}
|
||||
|
||||
export const encodeNprofile = (profile: CustomProfilePointer): string => {
|
||||
const data = encodeTLV({
|
||||
0: [hexToBytes(profile.pubkey)],
|
||||
1: (profile.relays || []).map(url => utf8Encoder.encode(url)),
|
||||
2: (profile.bridge || []).map(url => utf8Encoder.encode(url))
|
||||
});
|
||||
const words = bech32.toWords(data)
|
||||
return bech32.encode("nprofile", words, 5000);
|
||||
}
|
||||
|
||||
const parseTLV = (data: Uint8Array): TLV => {
|
||||
const result: TLV = {}
|
||||
let rest = data
|
||||
while (rest.length > 0) {
|
||||
const t = rest[0]
|
||||
const l = rest[1]
|
||||
const v = rest.slice(2, 2 + l)
|
||||
rest = rest.slice(2 + l)
|
||||
if (v.length < l) throw new Error(`not enough data to read on TLV ${t}`)
|
||||
result[t] = result[t] || []
|
||||
result[t].push(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const decodeNprofile = (nprofile: string): CustomProfilePointer => {
|
||||
const { prefix, words } = bech32.decode(nprofile, 5000)
|
||||
if (prefix !== "nprofile") {
|
||||
throw new Error ("Expected nprofile prefix");
|
||||
}
|
||||
const data = new Uint8Array(bech32.fromWords(words))
|
||||
|
||||
const tlv = parseTLV(data);
|
||||
if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for nprofile')
|
||||
if (tlv[0][0].length !== 32) throw new Error('TLV 0 should be 32 bytes')
|
||||
|
||||
return {
|
||||
pubkey: bytesToHex(tlv[0][0]),
|
||||
relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [],
|
||||
bridge: tlv[2] ? tlv[2].map(d => utf8Decoder.decode(d)): []
|
||||
}
|
||||
/*
|
||||
This file contains functions that deal with encoding and decoding nprofiles,
|
||||
but with he addition of bridge urls in the nprofile.
|
||||
These functions are basically the same functions from nostr-tools package
|
||||
but with some tweaks to allow for the bridge inclusion.
|
||||
*/
|
||||
import { bytesToHex, concatBytes, hexToBytes } from '@noble/hashes/utils';
|
||||
import { bech32 } from 'bech32';
|
||||
|
||||
export const utf8Decoder = new TextDecoder('utf-8')
|
||||
export const utf8Encoder = new TextEncoder()
|
||||
|
||||
|
||||
export type CustomProfilePointer = {
|
||||
pubkey: string
|
||||
relays?: string[]
|
||||
bridge?: string[] // one bridge
|
||||
}
|
||||
|
||||
|
||||
|
||||
type TLV = { [t: number]: Uint8Array[] }
|
||||
|
||||
|
||||
const encodeTLV = (tlv: TLV): Uint8Array => {
|
||||
const entries: Uint8Array[] = []
|
||||
|
||||
Object.entries(tlv)
|
||||
/*
|
||||
the original function does a reverse() here,
|
||||
but here it causes the nprofile string to be different,
|
||||
even though it would still decode to the correct original inputs
|
||||
*/
|
||||
//.reverse()
|
||||
.forEach(([t, vs]) => {
|
||||
vs.forEach(v => {
|
||||
const entry = new Uint8Array(v.length + 2)
|
||||
entry.set([parseInt(t)], 0)
|
||||
entry.set([v.length], 1)
|
||||
entry.set(v, 2)
|
||||
entries.push(entry)
|
||||
})
|
||||
})
|
||||
return concatBytes(...entries);
|
||||
}
|
||||
|
||||
export const encodeNprofile = (profile: CustomProfilePointer): string => {
|
||||
const data = encodeTLV({
|
||||
0: [hexToBytes(profile.pubkey)],
|
||||
1: (profile.relays || []).map(url => utf8Encoder.encode(url)),
|
||||
2: (profile.bridge || []).map(url => utf8Encoder.encode(url))
|
||||
});
|
||||
const words = bech32.toWords(data)
|
||||
return bech32.encode("nprofile", words, 5000);
|
||||
}
|
||||
|
||||
const parseTLV = (data: Uint8Array): TLV => {
|
||||
const result: TLV = {}
|
||||
let rest = data
|
||||
while (rest.length > 0) {
|
||||
const t = rest[0]
|
||||
const l = rest[1]
|
||||
const v = rest.slice(2, 2 + l)
|
||||
rest = rest.slice(2 + l)
|
||||
if (v.length < l) throw new Error(`not enough data to read on TLV ${t}`)
|
||||
result[t] = result[t] || []
|
||||
result[t].push(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const decodeNprofile = (nprofile: string): CustomProfilePointer => {
|
||||
const { prefix, words } = bech32.decode(nprofile, 5000)
|
||||
if (prefix !== "nprofile") {
|
||||
throw new Error ("Expected nprofile prefix");
|
||||
}
|
||||
const data = new Uint8Array(bech32.fromWords(words))
|
||||
|
||||
const tlv = parseTLV(data);
|
||||
if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for nprofile')
|
||||
if (tlv[0][0].length !== 32) throw new Error('TLV 0 should be 32 bytes')
|
||||
|
||||
return {
|
||||
pubkey: bytesToHex(tlv[0][0]),
|
||||
relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [],
|
||||
bridge: tlv[2] ? tlv[2].map(d => utf8Decoder.decode(d)): []
|
||||
}
|
||||
}
|
||||
62
src/index.ts
62
src/index.ts
|
|
@ -1,31 +1,31 @@
|
|||
import 'dotenv/config'
|
||||
import NewServer from '../proto/autogenerated/ts/express_server.js'
|
||||
import GetServerMethods from './services/serverMethods/index.js'
|
||||
import serverOptions from './auth.js';
|
||||
import { LoadNosrtSettingsFromEnv } from './services/nostr/index.js'
|
||||
import nostrMiddleware from './nostrMiddleware.js'
|
||||
import { getLogger } from './services/helpers/logger.js';
|
||||
import { initMainHandler } from './services/main/init.js';
|
||||
import { LoadMainSettingsFromEnv } from './services/main/settings.js';
|
||||
|
||||
const start = async () => {
|
||||
const log = getLogger({})
|
||||
const mainSettings = LoadMainSettingsFromEnv()
|
||||
const keepOn = await initMainHandler(log, mainSettings)
|
||||
if (!keepOn) {
|
||||
log("manual process ended")
|
||||
return
|
||||
}
|
||||
const { apps, mainHandler, liquidityProviderInfo } = keepOn
|
||||
const serverMethods = GetServerMethods(mainHandler)
|
||||
const nostrSettings = LoadNosrtSettingsFromEnv()
|
||||
const { Send } = nostrMiddleware(serverMethods, mainHandler,
|
||||
{ ...nostrSettings, apps, clients: [liquidityProviderInfo] },
|
||||
(e, p) => mainHandler.liquidProvider.onEvent(e, p)
|
||||
)
|
||||
mainHandler.attachNostrSend(Send)
|
||||
mainHandler.StartBeacons()
|
||||
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
||||
Server.Listen(mainSettings.servicePort)
|
||||
}
|
||||
start()
|
||||
import 'dotenv/config'
|
||||
import NewServer from '../proto/autogenerated/ts/express_server.js'
|
||||
import GetServerMethods from './services/serverMethods/index.js'
|
||||
import serverOptions from './auth.js';
|
||||
import { LoadNosrtSettingsFromEnv } from './services/nostr/index.js'
|
||||
import nostrMiddleware from './nostrMiddleware.js'
|
||||
import { getLogger } from './services/helpers/logger.js';
|
||||
import { initMainHandler } from './services/main/init.js';
|
||||
import { LoadMainSettingsFromEnv } from './services/main/settings.js';
|
||||
|
||||
const start = async () => {
|
||||
const log = getLogger({})
|
||||
const mainSettings = LoadMainSettingsFromEnv()
|
||||
const keepOn = await initMainHandler(log, mainSettings)
|
||||
if (!keepOn) {
|
||||
log("manual process ended")
|
||||
return
|
||||
}
|
||||
const { apps, mainHandler, liquidityProviderInfo } = keepOn
|
||||
const serverMethods = GetServerMethods(mainHandler)
|
||||
const nostrSettings = LoadNosrtSettingsFromEnv()
|
||||
const { Send } = nostrMiddleware(serverMethods, mainHandler,
|
||||
{ ...nostrSettings, apps, clients: [liquidityProviderInfo] },
|
||||
(e, p) => mainHandler.liquidProvider.onEvent(e, p)
|
||||
)
|
||||
mainHandler.attachNostrSend(Send)
|
||||
mainHandler.StartBeacons()
|
||||
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
||||
Server.Listen(mainSettings.servicePort)
|
||||
}
|
||||
start()
|
||||
|
|
|
|||
|
|
@ -1,41 +1,41 @@
|
|||
import Main from "./services/main/index.js"
|
||||
import Nostr from "./services/nostr/index.js"
|
||||
import { NostrSend, NostrSettings } from "./services/nostr/handler.js"
|
||||
import * as Types from '../proto/autogenerated/ts/types.js'
|
||||
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
|
||||
import { ERROR, getLogger } from "./services/helpers/logger.js";
|
||||
|
||||
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: (e: { requestId: string }, fromPub: string) => void): { Stop: () => void, Send: NostrSend } => {
|
||||
const log = getLogger({})
|
||||
const nostrTransport = NewNostrTransport(serverMethods, {
|
||||
NostrUserAuthGuard: async (appId, pub) => {
|
||||
const app = await mainHandler.storage.applicationStorage.GetApplication(appId || "")
|
||||
const nostrUser = await mainHandler.storage.applicationStorage.GetOrCreateNostrAppUser(app, pub || "")
|
||||
return { user_id: nostrUser.user.user_id, app_user_id: nostrUser.identifier, app_id: appId || "" }
|
||||
},
|
||||
metricsCallback: metrics => mainHandler.settings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
|
||||
logger: { log: console.log, error: err => log(ERROR, err) }
|
||||
})
|
||||
const nostr = new Nostr(nostrSettings, event => {
|
||||
let j: NostrRequest
|
||||
try {
|
||||
j = JSON.parse(event.content)
|
||||
log("nostr event", j.rpcName || 'no rpc name')
|
||||
} catch {
|
||||
log(ERROR, "invalid json event received", event.content)
|
||||
return
|
||||
}
|
||||
if (!j.rpcName) {
|
||||
onClientEvent(j as { requestId: string }, event.pub)
|
||||
return
|
||||
}
|
||||
if (j.authIdentifier !== event.pub) {
|
||||
log(ERROR, "authIdentifier does not match", j.authIdentifier || "--", event.pub)
|
||||
return
|
||||
}
|
||||
nostrTransport({ ...j, appId: event.appId }, res => {
|
||||
nostr.Send({ type: 'app', appId: event.appId }, { type: 'content', pub: event.pub, content: JSON.stringify({ ...res, requestId: j.requestId }) })
|
||||
}, event.startAtNano, event.startAtMs)
|
||||
})
|
||||
return { Stop: () => nostr.Stop, Send: (...args) => nostr.Send(...args) }
|
||||
import Main from "./services/main/index.js"
|
||||
import Nostr from "./services/nostr/index.js"
|
||||
import { NostrSend, NostrSettings } from "./services/nostr/handler.js"
|
||||
import * as Types from '../proto/autogenerated/ts/types.js'
|
||||
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
|
||||
import { ERROR, getLogger } from "./services/helpers/logger.js";
|
||||
|
||||
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: (e: { requestId: string }, fromPub: string) => void): { Stop: () => void, Send: NostrSend } => {
|
||||
const log = getLogger({})
|
||||
const nostrTransport = NewNostrTransport(serverMethods, {
|
||||
NostrUserAuthGuard: async (appId, pub) => {
|
||||
const app = await mainHandler.storage.applicationStorage.GetApplication(appId || "")
|
||||
const nostrUser = await mainHandler.storage.applicationStorage.GetOrCreateNostrAppUser(app, pub || "")
|
||||
return { user_id: nostrUser.user.user_id, app_user_id: nostrUser.identifier, app_id: appId || "" }
|
||||
},
|
||||
metricsCallback: metrics => mainHandler.settings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
|
||||
logger: { log: console.log, error: err => log(ERROR, err) }
|
||||
})
|
||||
const nostr = new Nostr(nostrSettings, event => {
|
||||
let j: NostrRequest
|
||||
try {
|
||||
j = JSON.parse(event.content)
|
||||
log("nostr event", j.rpcName || 'no rpc name')
|
||||
} catch {
|
||||
log(ERROR, "invalid json event received", event.content)
|
||||
return
|
||||
}
|
||||
if (!j.rpcName) {
|
||||
onClientEvent(j as { requestId: string }, event.pub)
|
||||
return
|
||||
}
|
||||
if (j.authIdentifier !== event.pub) {
|
||||
log(ERROR, "authIdentifier does not match", j.authIdentifier || "--", event.pub)
|
||||
return
|
||||
}
|
||||
nostrTransport({ ...j, appId: event.appId }, res => {
|
||||
nostr.Send({ type: 'app', appId: event.appId }, { type: 'content', pub: event.pub, content: JSON.stringify({ ...res, requestId: j.requestId }) })
|
||||
}, event.startAtNano, event.startAtMs)
|
||||
})
|
||||
return { Stop: () => nostr.Stop, Send: (...args) => nostr.Send(...args) }
|
||||
}
|
||||
|
|
@ -1,28 +1,28 @@
|
|||
export const EnvMustBeNonEmptyString = (name: string): string => {
|
||||
const env = process.env[name]
|
||||
if (!env) throw new Error(`${name} ENV must be non empty`)
|
||||
return env
|
||||
}
|
||||
export const EnvMustBeInteger = (name: string): number => {
|
||||
const env = EnvMustBeNonEmptyString(name)
|
||||
if (isNaN(+env) || !Number.isInteger(+env)) {
|
||||
throw new Error(`${name} ENV must be an integer number`);
|
||||
}
|
||||
return +env
|
||||
}
|
||||
export const EnvCanBeInteger = (name: string, defaultValue = 0): number => {
|
||||
const env = process.env[name]
|
||||
if (!env) {
|
||||
return defaultValue
|
||||
}
|
||||
const envNum = +env
|
||||
if (isNaN(envNum) || !Number.isInteger(envNum)) {
|
||||
throw new Error(`${name} ENV must be an integer number or nothing`);
|
||||
}
|
||||
return envNum
|
||||
}
|
||||
export const EnvCanBeBoolean = (name: string): boolean => {
|
||||
const env = process.env[name]
|
||||
if (!env) return false
|
||||
return env.toLowerCase() === 'true'
|
||||
export const EnvMustBeNonEmptyString = (name: string): string => {
|
||||
const env = process.env[name]
|
||||
if (!env) throw new Error(`${name} ENV must be non empty`)
|
||||
return env
|
||||
}
|
||||
export const EnvMustBeInteger = (name: string): number => {
|
||||
const env = EnvMustBeNonEmptyString(name)
|
||||
if (isNaN(+env) || !Number.isInteger(+env)) {
|
||||
throw new Error(`${name} ENV must be an integer number`);
|
||||
}
|
||||
return +env
|
||||
}
|
||||
export const EnvCanBeInteger = (name: string, defaultValue = 0): number => {
|
||||
const env = process.env[name]
|
||||
if (!env) {
|
||||
return defaultValue
|
||||
}
|
||||
const envNum = +env
|
||||
if (isNaN(envNum) || !Number.isInteger(envNum)) {
|
||||
throw new Error(`${name} ENV must be an integer number or nothing`);
|
||||
}
|
||||
return envNum
|
||||
}
|
||||
export const EnvCanBeBoolean = (name: string): boolean => {
|
||||
const env = process.env[name]
|
||||
if (!env) return false
|
||||
return env.toLowerCase() === 'true'
|
||||
}
|
||||
|
|
@ -1,34 +1,34 @@
|
|||
import { PubLogger, getLogger } from "../helpers/logger.js"
|
||||
|
||||
type Item<T> = { res: (v: T) => void, rej: (message: string) => void }
|
||||
export default class FunctionQueue<T> {
|
||||
log: PubLogger
|
||||
queue: Item<T>[] = []
|
||||
running: boolean = false
|
||||
f: () => Promise<T>
|
||||
constructor(name: string, f: () => Promise<T>) {
|
||||
this.log = getLogger({ component: name })
|
||||
this.f = f
|
||||
}
|
||||
|
||||
Run = (item: Item<T>) => {
|
||||
this.queue.push(item)
|
||||
if (!this.running) {
|
||||
this.execF()
|
||||
}
|
||||
}
|
||||
|
||||
execF = async () => {
|
||||
this.running = true
|
||||
try {
|
||||
const res = await this.f()
|
||||
this.queue.forEach(q => q.res(res))
|
||||
} catch (err) {
|
||||
this.queue.forEach(q => q.rej((err as any).message))
|
||||
}
|
||||
this.queue = []
|
||||
this.running = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import { PubLogger, getLogger } from "../helpers/logger.js"
|
||||
|
||||
type Item<T> = { res: (v: T) => void, rej: (message: string) => void }
|
||||
export default class FunctionQueue<T> {
|
||||
log: PubLogger
|
||||
queue: Item<T>[] = []
|
||||
running: boolean = false
|
||||
f: () => Promise<T>
|
||||
constructor(name: string, f: () => Promise<T>) {
|
||||
this.log = getLogger({ component: name })
|
||||
this.f = f
|
||||
}
|
||||
|
||||
Run = (item: Item<T>) => {
|
||||
this.queue.push(item)
|
||||
if (!this.running) {
|
||||
this.execF()
|
||||
}
|
||||
}
|
||||
|
||||
execF = async () => {
|
||||
this.running = true
|
||||
try {
|
||||
const res = await this.f()
|
||||
this.queue.forEach(q => q.res(res))
|
||||
} catch (err) {
|
||||
this.queue.forEach(q => q.rej((err as any).message))
|
||||
}
|
||||
this.queue = []
|
||||
this.running = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,105 +1,105 @@
|
|||
import fs from 'fs'
|
||||
export const DEBUG = Symbol("DEBUG")
|
||||
export const ERROR = Symbol("ERROR")
|
||||
export const WARN = Symbol("WARN")
|
||||
type LoggerParams = { appName?: string, userId?: string, component?: string }
|
||||
export type PubLogger = (...message: (string | number | object | symbol)[]) => void
|
||||
type Writer = (message: string) => void
|
||||
const logsDir = process.env.LOGS_DIR || "logs"
|
||||
const logLevel = process.env.LOG_LEVEL || "DEBUG"
|
||||
try {
|
||||
fs.mkdirSync(logsDir)
|
||||
} catch { }
|
||||
if (logLevel !== "DEBUG" && logLevel !== "WARN" && logLevel !== "ERROR") {
|
||||
throw new Error("Invalid log level " + logLevel + " must be one of (DEBUG, WARN, ERROR)")
|
||||
}
|
||||
const z = (n: number) => n < 10 ? `0${n}` : `${n}`
|
||||
const openWriter = (fileName: string): Writer => {
|
||||
const now = new Date()
|
||||
const date = `${now.getFullYear()}-${z(now.getMonth() + 1)}-${z(now.getDate())}`
|
||||
const logStream = fs.createWriteStream(`${logsDir}/${fileName}_${date}.log`, { flags: 'a' });
|
||||
return (message) => {
|
||||
logStream.write(message + "\n")
|
||||
}
|
||||
}
|
||||
const rootWriter = openWriter("ROOT.log")
|
||||
if (!fs.existsSync(`${logsDir}/apps`)) {
|
||||
fs.mkdirSync(`${logsDir}/apps`, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(`${logsDir}/users`)) {
|
||||
fs.mkdirSync(`${logsDir}/users`, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(`${logsDir}/components`)) {
|
||||
fs.mkdirSync(`${logsDir}/components`, { recursive: true });
|
||||
}
|
||||
export const getLogger = (params: LoggerParams): PubLogger => {
|
||||
const writers: Writer[] = []
|
||||
if (params.appName) {
|
||||
writers.push(openWriter(`apps/${params.appName}`))
|
||||
}
|
||||
if (params.userId) {
|
||||
writers.push(openWriter(`users/${params.userId}`))
|
||||
}
|
||||
if (params.component) {
|
||||
writers.push(openWriter(`components/${params.component}`))
|
||||
}
|
||||
if (writers.length === 0) {
|
||||
writers.push(rootWriter)
|
||||
}
|
||||
|
||||
return (...message) => {
|
||||
switch (message[0]) {
|
||||
case DEBUG:
|
||||
if (logLevel !== "DEBUG") {
|
||||
return
|
||||
}
|
||||
message[0] = "DEBUG"
|
||||
break;
|
||||
case WARN:
|
||||
if (logLevel === "ERROR") {
|
||||
return
|
||||
}
|
||||
message[0] = "WARN"
|
||||
break;
|
||||
case ERROR:
|
||||
message[0] = "ERROR"
|
||||
break;
|
||||
default:
|
||||
if (logLevel !== "DEBUG") {
|
||||
return
|
||||
}
|
||||
}
|
||||
const now = new Date()
|
||||
const timestamp = `${now.getFullYear()}-${z(now.getMonth() + 1)}-${z(now.getDate())} ${z(now.getHours())}:${z(now.getMinutes())}:${z(now.getSeconds())}`
|
||||
const toLog = [timestamp]
|
||||
if (params.appName) {
|
||||
if (disabledApps.includes(params.appName)) {
|
||||
return
|
||||
}
|
||||
toLog.push(params.appName)
|
||||
}
|
||||
if (params.component) {
|
||||
if (disabledComponents.includes(params.component)) {
|
||||
return
|
||||
}
|
||||
toLog.push(params.component)
|
||||
}
|
||||
if (params.userId) {
|
||||
toLog.push(params.userId)
|
||||
}
|
||||
const parsed = message.map(m => typeof m === 'object' ? JSON.stringify(m, (_, v) => typeof v === 'bigint' ? v.toString() : v) : m)
|
||||
const final = `${toLog.join(" ")} >> ${parsed.join(" ")}`
|
||||
console.log(final)
|
||||
writers.forEach(w => w(final))
|
||||
}
|
||||
}
|
||||
let disabledApps: string[] = []
|
||||
let disabledComponents: string[] = []
|
||||
export const resetDisabledLoggers = () => {
|
||||
disabledApps = []
|
||||
disabledComponents = []
|
||||
}
|
||||
export const disableLoggers = (appNamesToDisable: string[], componentsToDisable: string[]) => {
|
||||
disabledApps.push(...appNamesToDisable)
|
||||
disabledComponents.push(...componentsToDisable)
|
||||
import fs from 'fs'
|
||||
export const DEBUG = Symbol("DEBUG")
|
||||
export const ERROR = Symbol("ERROR")
|
||||
export const WARN = Symbol("WARN")
|
||||
type LoggerParams = { appName?: string, userId?: string, component?: string }
|
||||
export type PubLogger = (...message: (string | number | object | symbol)[]) => void
|
||||
type Writer = (message: string) => void
|
||||
const logsDir = process.env.LOGS_DIR || "logs"
|
||||
const logLevel = process.env.LOG_LEVEL || "DEBUG"
|
||||
try {
|
||||
fs.mkdirSync(logsDir)
|
||||
} catch { }
|
||||
if (logLevel !== "DEBUG" && logLevel !== "WARN" && logLevel !== "ERROR") {
|
||||
throw new Error("Invalid log level " + logLevel + " must be one of (DEBUG, WARN, ERROR)")
|
||||
}
|
||||
const z = (n: number) => n < 10 ? `0${n}` : `${n}`
|
||||
const openWriter = (fileName: string): Writer => {
|
||||
const now = new Date()
|
||||
const date = `${now.getFullYear()}-${z(now.getMonth() + 1)}-${z(now.getDate())}`
|
||||
const logStream = fs.createWriteStream(`${logsDir}/${fileName}_${date}.log`, { flags: 'a' });
|
||||
return (message) => {
|
||||
logStream.write(message + "\n")
|
||||
}
|
||||
}
|
||||
const rootWriter = openWriter("ROOT.log")
|
||||
if (!fs.existsSync(`${logsDir}/apps`)) {
|
||||
fs.mkdirSync(`${logsDir}/apps`, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(`${logsDir}/users`)) {
|
||||
fs.mkdirSync(`${logsDir}/users`, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(`${logsDir}/components`)) {
|
||||
fs.mkdirSync(`${logsDir}/components`, { recursive: true });
|
||||
}
|
||||
export const getLogger = (params: LoggerParams): PubLogger => {
|
||||
const writers: Writer[] = []
|
||||
if (params.appName) {
|
||||
writers.push(openWriter(`apps/${params.appName}`))
|
||||
}
|
||||
if (params.userId) {
|
||||
writers.push(openWriter(`users/${params.userId}`))
|
||||
}
|
||||
if (params.component) {
|
||||
writers.push(openWriter(`components/${params.component}`))
|
||||
}
|
||||
if (writers.length === 0) {
|
||||
writers.push(rootWriter)
|
||||
}
|
||||
|
||||
return (...message) => {
|
||||
switch (message[0]) {
|
||||
case DEBUG:
|
||||
if (logLevel !== "DEBUG") {
|
||||
return
|
||||
}
|
||||
message[0] = "DEBUG"
|
||||
break;
|
||||
case WARN:
|
||||
if (logLevel === "ERROR") {
|
||||
return
|
||||
}
|
||||
message[0] = "WARN"
|
||||
break;
|
||||
case ERROR:
|
||||
message[0] = "ERROR"
|
||||
break;
|
||||
default:
|
||||
if (logLevel !== "DEBUG") {
|
||||
return
|
||||
}
|
||||
}
|
||||
const now = new Date()
|
||||
const timestamp = `${now.getFullYear()}-${z(now.getMonth() + 1)}-${z(now.getDate())} ${z(now.getHours())}:${z(now.getMinutes())}:${z(now.getSeconds())}`
|
||||
const toLog = [timestamp]
|
||||
if (params.appName) {
|
||||
if (disabledApps.includes(params.appName)) {
|
||||
return
|
||||
}
|
||||
toLog.push(params.appName)
|
||||
}
|
||||
if (params.component) {
|
||||
if (disabledComponents.includes(params.component)) {
|
||||
return
|
||||
}
|
||||
toLog.push(params.component)
|
||||
}
|
||||
if (params.userId) {
|
||||
toLog.push(params.userId)
|
||||
}
|
||||
const parsed = message.map(m => typeof m === 'object' ? JSON.stringify(m, (_, v) => typeof v === 'bigint' ? v.toString() : v) : m)
|
||||
const final = `${toLog.join(" ")} >> ${parsed.join(" ")}`
|
||||
console.log(final)
|
||||
writers.forEach(w => w(final))
|
||||
}
|
||||
}
|
||||
let disabledApps: string[] = []
|
||||
let disabledComponents: string[] = []
|
||||
export const resetDisabledLoggers = () => {
|
||||
disabledApps = []
|
||||
disabledComponents = []
|
||||
}
|
||||
export const disableLoggers = (appNamesToDisable: string[], componentsToDisable: string[]) => {
|
||||
disabledApps.push(...appNamesToDisable)
|
||||
disabledComponents.push(...componentsToDisable)
|
||||
}
|
||||
|
|
@ -1,34 +1,34 @@
|
|||
import { OpenChannelRequest, Invoice } from "../../../proto/lnd/lightning";
|
||||
|
||||
export const AddInvoiceReq = (value: number, expiry = 60 * 60, privateHints = false, memo?: string): Invoice => ({
|
||||
expiry: BigInt(expiry),
|
||||
memo: memo || "",
|
||||
private: privateHints,
|
||||
value: BigInt(value),
|
||||
|
||||
fallbackAddr: "",
|
||||
cltvExpiry: 0n,
|
||||
descriptionHash: Buffer.alloc(0),
|
||||
features: {},
|
||||
isAmp: false,
|
||||
rPreimage: Buffer.alloc(0),
|
||||
routeHints: [],
|
||||
valueMsat: 0n,
|
||||
|
||||
addIndex: 0n,
|
||||
ampInvoiceState: {},
|
||||
amtPaidMsat: 0n,
|
||||
amtPaidSat: 0n,
|
||||
creationDate: 0n,
|
||||
htlcs: [],
|
||||
isKeysend: false,
|
||||
paymentAddr: Buffer.alloc(0),
|
||||
paymentRequest: "",
|
||||
rHash: Buffer.alloc(0),
|
||||
settleDate: 0n,
|
||||
settleIndex: 0n,
|
||||
state: 0,
|
||||
|
||||
amtPaid: 0n,
|
||||
settled: false,
|
||||
import { OpenChannelRequest, Invoice } from "../../../proto/lnd/lightning";
|
||||
|
||||
export const AddInvoiceReq = (value: number, expiry = 60 * 60, privateHints = false, memo?: string): Invoice => ({
|
||||
expiry: BigInt(expiry),
|
||||
memo: memo || "",
|
||||
private: privateHints,
|
||||
value: BigInt(value),
|
||||
|
||||
fallbackAddr: "",
|
||||
cltvExpiry: 0n,
|
||||
descriptionHash: Buffer.alloc(0),
|
||||
features: {},
|
||||
isAmp: false,
|
||||
rPreimage: Buffer.alloc(0),
|
||||
routeHints: [],
|
||||
valueMsat: 0n,
|
||||
|
||||
addIndex: 0n,
|
||||
ampInvoiceState: {},
|
||||
amtPaidMsat: 0n,
|
||||
amtPaidSat: 0n,
|
||||
creationDate: 0n,
|
||||
htlcs: [],
|
||||
isKeysend: false,
|
||||
paymentAddr: Buffer.alloc(0),
|
||||
paymentRequest: "",
|
||||
rHash: Buffer.alloc(0),
|
||||
settleDate: 0n,
|
||||
settleIndex: 0n,
|
||||
state: 0,
|
||||
|
||||
amtPaid: 0n,
|
||||
settled: false,
|
||||
})
|
||||
|
|
@ -1,22 +1,22 @@
|
|||
import { EnvMustBeNonEmptyString, EnvMustBeInteger, EnvCanBeBoolean, EnvCanBeInteger } from '../helpers/envParser.js'
|
||||
import { LndSettings } from './settings.js'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
|
||||
const resolveHome = (filepath: string) => {
|
||||
if (filepath[0] === '~') {
|
||||
return path.join(os.homedir(), filepath.slice(1))
|
||||
}
|
||||
return filepath
|
||||
}
|
||||
|
||||
export const LoadLndSettingsFromEnv = (): LndSettings => {
|
||||
const lndAddr = process.env.LND_ADDRESS || "127.0.0.1:10009"
|
||||
const lndCertPath = process.env.LND_CERT_PATH || resolveHome("~/.lnd/tls.cert")
|
||||
const lndMacaroonPath = process.env.LND_MACAROON_PATH || resolveHome("~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon")
|
||||
const feeRateLimit = EnvCanBeInteger("OUTBOUND_MAX_FEE_BPS", 60) / 10000
|
||||
const feeFixedLimit = EnvCanBeInteger("OUTBOUND_MAX_FEE_EXTRA_SATS", 100)
|
||||
const mockLnd = EnvCanBeBoolean("MOCK_LND")
|
||||
const liquidityProviderPub = process.env.LIQUIDITY_PROVIDER_PUB || ""
|
||||
return { mainNode: { lndAddr, lndCertPath, lndMacaroonPath }, feeRateLimit, feeFixedLimit, mockLnd, liquidityProviderPub, useOnlyLiquidityProvider: false }
|
||||
}
|
||||
import { EnvMustBeNonEmptyString, EnvMustBeInteger, EnvCanBeBoolean, EnvCanBeInteger } from '../helpers/envParser.js'
|
||||
import { LndSettings } from './settings.js'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
|
||||
const resolveHome = (filepath: string) => {
|
||||
if (filepath[0] === '~') {
|
||||
return path.join(os.homedir(), filepath.slice(1))
|
||||
}
|
||||
return filepath
|
||||
}
|
||||
|
||||
export const LoadLndSettingsFromEnv = (): LndSettings => {
|
||||
const lndAddr = process.env.LND_ADDRESS || "127.0.0.1:10009"
|
||||
const lndCertPath = process.env.LND_CERT_PATH || resolveHome("~/.lnd/tls.cert")
|
||||
const lndMacaroonPath = process.env.LND_MACAROON_PATH || resolveHome("~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon")
|
||||
const feeRateLimit = EnvCanBeInteger("OUTBOUND_MAX_FEE_BPS", 60) / 10000
|
||||
const feeFixedLimit = EnvCanBeInteger("OUTBOUND_MAX_FEE_EXTRA_SATS", 100)
|
||||
const mockLnd = EnvCanBeBoolean("MOCK_LND")
|
||||
const liquidityProviderPub = process.env.LIQUIDITY_PROVIDER_PUB || ""
|
||||
return { mainNode: { lndAddr, lndCertPath, lndMacaroonPath }, feeRateLimit, feeFixedLimit, mockLnd, liquidityProviderPub, useOnlyLiquidityProvider: false }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,226 +1,226 @@
|
|||
import newNostrClient from '../../../proto/autogenerated/ts/nostr_client.js'
|
||||
import { NostrRequest } from '../../../proto/autogenerated/ts/nostr_transport.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { decodeNprofile } from '../../custom-nip19.js'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
import { NostrEvent, NostrSend } from '../nostr/handler.js'
|
||||
import { relayInit } from '../nostr/tools/relay.js'
|
||||
import { InvoicePaidCb } from './settings.js'
|
||||
|
||||
export type LiquidityRequest = { action: 'spend' | 'receive', amount: number }
|
||||
|
||||
export type nostrCallback<T> = { startedAtMillis: number, type: 'single' | 'stream', f: (res: T) => void }
|
||||
export class LiquidityProvider {
|
||||
client: ReturnType<typeof newNostrClient>
|
||||
clientCbs: Record<string, nostrCallback<any>> = {}
|
||||
clientId: string = ""
|
||||
myPub: string = ""
|
||||
log = getLogger({ component: 'liquidityProvider' })
|
||||
nostrSend: NostrSend | null = null
|
||||
ready = false
|
||||
pubDestination: string
|
||||
latestMaxWithdrawable: number | null = null
|
||||
invoicePaidCb: InvoicePaidCb
|
||||
connecting = false
|
||||
readyInterval: NodeJS.Timeout
|
||||
// make the sub process accept client
|
||||
constructor(pubDestination: string, invoicePaidCb: InvoicePaidCb) {
|
||||
if (!pubDestination) {
|
||||
this.log("No pub provider to liquidity provider, will not be initialized")
|
||||
}
|
||||
this.pubDestination = pubDestination
|
||||
this.invoicePaidCb = invoicePaidCb
|
||||
this.client = newNostrClient({
|
||||
pubDestination: this.pubDestination,
|
||||
retrieveNostrUserAuth: async () => this.myPub,
|
||||
}, this.clientSend, this.clientSub)
|
||||
|
||||
this.readyInterval = setInterval(() => {
|
||||
if (this.ready) {
|
||||
clearInterval(this.readyInterval)
|
||||
this.Connect()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
Stop = () => {
|
||||
clearInterval(this.readyInterval)
|
||||
}
|
||||
|
||||
Connect = async () => {
|
||||
await new Promise(res => setTimeout(res, 2000))
|
||||
this.log("ready")
|
||||
await this.CheckUserState()
|
||||
if (this.latestMaxWithdrawable === null) {
|
||||
return
|
||||
}
|
||||
this.log("subbing to user operations")
|
||||
this.client.GetLiveUserOperations(res => {
|
||||
console.log("got user operation", res)
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error getting user operations", res.reason)
|
||||
return
|
||||
}
|
||||
this.log("got user operation", res.operation)
|
||||
if (res.operation.type === Types.UserOperationType.INCOMING_INVOICE) {
|
||||
this.log("invoice was paid", res.operation.identifier)
|
||||
this.invoicePaidCb(res.operation.identifier, res.operation.amount, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
CheckUserState = async () => {
|
||||
const res = await this.client.GetUserInfo()
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error getting user info", res)
|
||||
return
|
||||
}
|
||||
this.latestMaxWithdrawable = res.max_withdrawable
|
||||
this.log("latest provider balance:", res.max_withdrawable)
|
||||
return res
|
||||
}
|
||||
|
||||
CanProviderHandle = (req: LiquidityRequest) => {
|
||||
if (this.latestMaxWithdrawable === null) {
|
||||
return false
|
||||
}
|
||||
if (req.action === 'spend') {
|
||||
return this.latestMaxWithdrawable > req.amount
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
AddInvoice = async (amount: number, memo: string) => {
|
||||
const res = await this.client.NewInvoice({ amountSats: amount, memo })
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error creating invoice", res.reason)
|
||||
throw new Error(res.reason)
|
||||
}
|
||||
this.log("new invoice", res.invoice)
|
||||
this.CheckUserState()
|
||||
return res.invoice
|
||||
}
|
||||
|
||||
PayInvoice = async (invoice: string) => {
|
||||
const res = await this.client.PayInvoice({ invoice, amount: 0 })
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error paying invoice", res.reason)
|
||||
throw new Error(res.reason)
|
||||
}
|
||||
this.log("paid invoice", res)
|
||||
this.CheckUserState()
|
||||
return res
|
||||
}
|
||||
|
||||
setNostrInfo = ({ clientId, myPub }: { myPub: string, clientId: string }) => {
|
||||
this.clientId = clientId
|
||||
this.myPub = myPub
|
||||
this.setSetIfReady()
|
||||
}
|
||||
|
||||
|
||||
|
||||
attachNostrSend(f: NostrSend) {
|
||||
this.nostrSend = f
|
||||
this.setSetIfReady()
|
||||
}
|
||||
|
||||
setSetIfReady = () => {
|
||||
if (this.nostrSend && !!this.pubDestination && !!this.clientId && !!this.myPub) {
|
||||
this.ready = true
|
||||
this.log("ready to send to ", this.pubDestination)
|
||||
}
|
||||
}
|
||||
|
||||
onEvent = async (res: { requestId: string }, fromPub: string) => {
|
||||
if (fromPub !== this.pubDestination) {
|
||||
this.log("got event from invalid pub", fromPub, this.pubDestination)
|
||||
return false
|
||||
}
|
||||
if (this.clientCbs[res.requestId]) {
|
||||
const cb = this.clientCbs[res.requestId]
|
||||
cb.f(res)
|
||||
if (cb.type === 'single') {
|
||||
delete this.clientCbs[res.requestId]
|
||||
this.log(this.getSingleSubs(), "single subs left")
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
clientSend = (to: string, message: NostrRequest): Promise<any> => {
|
||||
if (!this.ready || !this.nostrSend) {
|
||||
throw new Error("liquidity provider not initialized")
|
||||
}
|
||||
if (!message.requestId) {
|
||||
message.requestId = makeId(16)
|
||||
}
|
||||
const reqId = message.requestId
|
||||
if (this.clientCbs[reqId]) {
|
||||
throw new Error("request was already sent")
|
||||
}
|
||||
this.nostrSend({ type: 'client', clientId: this.clientId }, {
|
||||
type: 'content',
|
||||
pub: to,
|
||||
content: JSON.stringify(message)
|
||||
})
|
||||
|
||||
//this.nostrSend(this.relays, to, JSON.stringify(message), this.settings)
|
||||
|
||||
this.log("subbing to single send", reqId, message.rpcName || 'no rpc name')
|
||||
return new Promise(res => {
|
||||
this.clientCbs[reqId] = {
|
||||
startedAtMillis: Date.now(),
|
||||
type: 'single',
|
||||
f: (response: any) => { res(response) },
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
clientSub = (to: string, message: NostrRequest, cb: (res: any) => void): void => {
|
||||
if (!this.ready || !this.nostrSend) {
|
||||
throw new Error("liquidity provider not initialized")
|
||||
}
|
||||
if (!message.requestId) {
|
||||
message.requestId = message.rpcName
|
||||
}
|
||||
const reqId = message.requestId
|
||||
if (!reqId) {
|
||||
throw new Error("invalid sub")
|
||||
}
|
||||
if (this.clientCbs[reqId]) {
|
||||
this.clientCbs[reqId] = {
|
||||
startedAtMillis: Date.now(),
|
||||
type: 'stream',
|
||||
f: (response: any) => { cb(response) },
|
||||
}
|
||||
this.log("sub for", reqId, "was already registered, overriding")
|
||||
return
|
||||
}
|
||||
this.nostrSend({ type: 'client', clientId: this.clientId }, {
|
||||
type: 'content',
|
||||
pub: to,
|
||||
content: JSON.stringify(message)
|
||||
})
|
||||
this.log("subbing to stream", reqId)
|
||||
this.clientCbs[reqId] = {
|
||||
startedAtMillis: Date.now(),
|
||||
type: 'stream',
|
||||
f: (response: any) => { cb(response) }
|
||||
}
|
||||
}
|
||||
getSingleSubs = () => {
|
||||
return Object.entries(this.clientCbs).filter(([_, cb]) => cb.type === 'single')
|
||||
}
|
||||
}
|
||||
|
||||
export const makeId = (length: number) => {
|
||||
let result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const charactersLength = characters.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
import newNostrClient from '../../../proto/autogenerated/ts/nostr_client.js'
|
||||
import { NostrRequest } from '../../../proto/autogenerated/ts/nostr_transport.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { decodeNprofile } from '../../custom-nip19.js'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
import { NostrEvent, NostrSend } from '../nostr/handler.js'
|
||||
import { relayInit } from '../nostr/tools/relay.js'
|
||||
import { InvoicePaidCb } from './settings.js'
|
||||
|
||||
export type LiquidityRequest = { action: 'spend' | 'receive', amount: number }
|
||||
|
||||
export type nostrCallback<T> = { startedAtMillis: number, type: 'single' | 'stream', f: (res: T) => void }
|
||||
export class LiquidityProvider {
|
||||
client: ReturnType<typeof newNostrClient>
|
||||
clientCbs: Record<string, nostrCallback<any>> = {}
|
||||
clientId: string = ""
|
||||
myPub: string = ""
|
||||
log = getLogger({ component: 'liquidityProvider' })
|
||||
nostrSend: NostrSend | null = null
|
||||
ready = false
|
||||
pubDestination: string
|
||||
latestMaxWithdrawable: number | null = null
|
||||
invoicePaidCb: InvoicePaidCb
|
||||
connecting = false
|
||||
readyInterval: NodeJS.Timeout
|
||||
// make the sub process accept client
|
||||
constructor(pubDestination: string, invoicePaidCb: InvoicePaidCb) {
|
||||
if (!pubDestination) {
|
||||
this.log("No pub provider to liquidity provider, will not be initialized")
|
||||
}
|
||||
this.pubDestination = pubDestination
|
||||
this.invoicePaidCb = invoicePaidCb
|
||||
this.client = newNostrClient({
|
||||
pubDestination: this.pubDestination,
|
||||
retrieveNostrUserAuth: async () => this.myPub,
|
||||
}, this.clientSend, this.clientSub)
|
||||
|
||||
this.readyInterval = setInterval(() => {
|
||||
if (this.ready) {
|
||||
clearInterval(this.readyInterval)
|
||||
this.Connect()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
Stop = () => {
|
||||
clearInterval(this.readyInterval)
|
||||
}
|
||||
|
||||
Connect = async () => {
|
||||
await new Promise(res => setTimeout(res, 2000))
|
||||
this.log("ready")
|
||||
await this.CheckUserState()
|
||||
if (this.latestMaxWithdrawable === null) {
|
||||
return
|
||||
}
|
||||
this.log("subbing to user operations")
|
||||
this.client.GetLiveUserOperations(res => {
|
||||
console.log("got user operation", res)
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error getting user operations", res.reason)
|
||||
return
|
||||
}
|
||||
this.log("got user operation", res.operation)
|
||||
if (res.operation.type === Types.UserOperationType.INCOMING_INVOICE) {
|
||||
this.log("invoice was paid", res.operation.identifier)
|
||||
this.invoicePaidCb(res.operation.identifier, res.operation.amount, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
CheckUserState = async () => {
|
||||
const res = await this.client.GetUserInfo()
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error getting user info", res)
|
||||
return
|
||||
}
|
||||
this.latestMaxWithdrawable = res.max_withdrawable
|
||||
this.log("latest provider balance:", res.max_withdrawable)
|
||||
return res
|
||||
}
|
||||
|
||||
CanProviderHandle = (req: LiquidityRequest) => {
|
||||
if (this.latestMaxWithdrawable === null) {
|
||||
return false
|
||||
}
|
||||
if (req.action === 'spend') {
|
||||
return this.latestMaxWithdrawable > req.amount
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
AddInvoice = async (amount: number, memo: string) => {
|
||||
const res = await this.client.NewInvoice({ amountSats: amount, memo })
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error creating invoice", res.reason)
|
||||
throw new Error(res.reason)
|
||||
}
|
||||
this.log("new invoice", res.invoice)
|
||||
this.CheckUserState()
|
||||
return res.invoice
|
||||
}
|
||||
|
||||
PayInvoice = async (invoice: string) => {
|
||||
const res = await this.client.PayInvoice({ invoice, amount: 0 })
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error paying invoice", res.reason)
|
||||
throw new Error(res.reason)
|
||||
}
|
||||
this.log("paid invoice", res)
|
||||
this.CheckUserState()
|
||||
return res
|
||||
}
|
||||
|
||||
setNostrInfo = ({ clientId, myPub }: { myPub: string, clientId: string }) => {
|
||||
this.clientId = clientId
|
||||
this.myPub = myPub
|
||||
this.setSetIfReady()
|
||||
}
|
||||
|
||||
|
||||
|
||||
attachNostrSend(f: NostrSend) {
|
||||
this.nostrSend = f
|
||||
this.setSetIfReady()
|
||||
}
|
||||
|
||||
setSetIfReady = () => {
|
||||
if (this.nostrSend && !!this.pubDestination && !!this.clientId && !!this.myPub) {
|
||||
this.ready = true
|
||||
this.log("ready to send to ", this.pubDestination)
|
||||
}
|
||||
}
|
||||
|
||||
onEvent = async (res: { requestId: string }, fromPub: string) => {
|
||||
if (fromPub !== this.pubDestination) {
|
||||
this.log("got event from invalid pub", fromPub, this.pubDestination)
|
||||
return false
|
||||
}
|
||||
if (this.clientCbs[res.requestId]) {
|
||||
const cb = this.clientCbs[res.requestId]
|
||||
cb.f(res)
|
||||
if (cb.type === 'single') {
|
||||
delete this.clientCbs[res.requestId]
|
||||
this.log(this.getSingleSubs(), "single subs left")
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
clientSend = (to: string, message: NostrRequest): Promise<any> => {
|
||||
if (!this.ready || !this.nostrSend) {
|
||||
throw new Error("liquidity provider not initialized")
|
||||
}
|
||||
if (!message.requestId) {
|
||||
message.requestId = makeId(16)
|
||||
}
|
||||
const reqId = message.requestId
|
||||
if (this.clientCbs[reqId]) {
|
||||
throw new Error("request was already sent")
|
||||
}
|
||||
this.nostrSend({ type: 'client', clientId: this.clientId }, {
|
||||
type: 'content',
|
||||
pub: to,
|
||||
content: JSON.stringify(message)
|
||||
})
|
||||
|
||||
//this.nostrSend(this.relays, to, JSON.stringify(message), this.settings)
|
||||
|
||||
this.log("subbing to single send", reqId, message.rpcName || 'no rpc name')
|
||||
return new Promise(res => {
|
||||
this.clientCbs[reqId] = {
|
||||
startedAtMillis: Date.now(),
|
||||
type: 'single',
|
||||
f: (response: any) => { res(response) },
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
clientSub = (to: string, message: NostrRequest, cb: (res: any) => void): void => {
|
||||
if (!this.ready || !this.nostrSend) {
|
||||
throw new Error("liquidity provider not initialized")
|
||||
}
|
||||
if (!message.requestId) {
|
||||
message.requestId = message.rpcName
|
||||
}
|
||||
const reqId = message.requestId
|
||||
if (!reqId) {
|
||||
throw new Error("invalid sub")
|
||||
}
|
||||
if (this.clientCbs[reqId]) {
|
||||
this.clientCbs[reqId] = {
|
||||
startedAtMillis: Date.now(),
|
||||
type: 'stream',
|
||||
f: (response: any) => { cb(response) },
|
||||
}
|
||||
this.log("sub for", reqId, "was already registered, overriding")
|
||||
return
|
||||
}
|
||||
this.nostrSend({ type: 'client', clientId: this.clientId }, {
|
||||
type: 'content',
|
||||
pub: to,
|
||||
content: JSON.stringify(message)
|
||||
})
|
||||
this.log("subbing to stream", reqId)
|
||||
this.clientCbs[reqId] = {
|
||||
startedAtMillis: Date.now(),
|
||||
type: 'stream',
|
||||
f: (response: any) => { cb(response) }
|
||||
}
|
||||
}
|
||||
getSingleSubs = () => {
|
||||
return Object.entries(this.clientCbs).filter(([_, cb]) => cb.type === 'single')
|
||||
}
|
||||
}
|
||||
|
||||
export const makeId = (length: number) => {
|
||||
let result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const charactersLength = characters.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -1,437 +1,437 @@
|
|||
//const grpc = require('@grpc/grpc-js');
|
||||
import crypto from 'crypto'
|
||||
import { credentials, Metadata } from '@grpc/grpc-js'
|
||||
import { GrpcTransport } from "@protobuf-ts/grpc-transport";
|
||||
import fs from 'fs'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { LightningClient } from '../../../proto/lnd/lightning.client.js'
|
||||
import { InvoicesClient } from '../../../proto/lnd/invoices.client.js'
|
||||
import { RouterClient } from '../../../proto/lnd/router.client.js'
|
||||
import { ChainNotifierClient } from '../../../proto/lnd/chainnotifier.client.js'
|
||||
import { GetInfoResponse, AddressType, NewAddressResponse, AddInvoiceResponse, Invoice_InvoiceState, PayReq, Payment_PaymentStatus, Payment, PaymentFailureReason, SendCoinsResponse, EstimateFeeResponse, ChannelBalanceResponse, TransactionDetails, ListChannelsResponse, ClosedChannelsResponse, PendingChannelsResponse, ForwardingHistoryResponse } from '../../../proto/lnd/lightning.js'
|
||||
import { OpenChannelReq } from './openChannelReq.js';
|
||||
import { AddInvoiceReq } from './addInvoiceReq.js';
|
||||
import { PayInvoiceReq } from './payInvoiceReq.js';
|
||||
import { SendCoinsReq } from './sendCoinsReq.js';
|
||||
import { LndSettings, AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb, HtlcCb, BalanceInfo } from './settings.js';
|
||||
import { getLogger } from '../helpers/logger.js';
|
||||
import { HtlcEvent_EventType } from '../../../proto/lnd/router.js';
|
||||
import { LiquidityProvider, LiquidityRequest } from './liquidityProvider.js';
|
||||
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
|
||||
const deadLndRetrySeconds = 5
|
||||
export default class {
|
||||
lightning: LightningClient
|
||||
invoices: InvoicesClient
|
||||
router: RouterClient
|
||||
chainNotifier: ChainNotifierClient
|
||||
settings: LndSettings
|
||||
ready = false
|
||||
latestKnownBlockHeigh = 0
|
||||
latestKnownSettleIndex = 0
|
||||
abortController = new AbortController()
|
||||
addressPaidCb: AddressPaidCb
|
||||
invoicePaidCb: InvoicePaidCb
|
||||
newBlockCb: NewBlockCb
|
||||
htlcCb: HtlcCb
|
||||
log = getLogger({ component: 'lndManager' })
|
||||
outgoingOpsLocked = false
|
||||
liquidProvider: LiquidityProvider
|
||||
constructor(settings: LndSettings, liquidProvider: LiquidityProvider, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb) {
|
||||
this.settings = settings
|
||||
this.addressPaidCb = addressPaidCb
|
||||
this.invoicePaidCb = invoicePaidCb
|
||||
this.newBlockCb = newBlockCb
|
||||
this.htlcCb = htlcCb
|
||||
const { lndAddr, lndCertPath, lndMacaroonPath } = this.settings.mainNode
|
||||
const lndCert = fs.readFileSync(lndCertPath);
|
||||
const macaroon = fs.readFileSync(lndMacaroonPath).toString('hex');
|
||||
const sslCreds = credentials.createSsl(lndCert);
|
||||
const macaroonCreds = credentials.createFromMetadataGenerator(
|
||||
function (args: any, callback: any) {
|
||||
let metadata = new Metadata();
|
||||
metadata.add('macaroon', macaroon);
|
||||
callback(null, metadata);
|
||||
},
|
||||
);
|
||||
const creds = credentials.combineChannelCredentials(
|
||||
sslCreds,
|
||||
macaroonCreds,
|
||||
);
|
||||
const transport = new GrpcTransport({ host: lndAddr, channelCredentials: creds })
|
||||
this.lightning = new LightningClient(transport)
|
||||
this.invoices = new InvoicesClient(transport)
|
||||
this.router = new RouterClient(transport)
|
||||
this.chainNotifier = new ChainNotifierClient(transport)
|
||||
this.liquidProvider = liquidProvider
|
||||
}
|
||||
|
||||
LockOutgoingOperations(): void {
|
||||
this.outgoingOpsLocked = true
|
||||
}
|
||||
UnlockOutgoingOperations(): void {
|
||||
this.outgoingOpsLocked = false
|
||||
}
|
||||
|
||||
SetMockInvoiceAsPaid(invoice: string, amount: number): Promise<void> {
|
||||
throw new Error("SetMockInvoiceAsPaid only available in mock mode")
|
||||
}
|
||||
Stop() {
|
||||
this.abortController.abort()
|
||||
this.liquidProvider.Stop()
|
||||
}
|
||||
|
||||
async ShouldUseLiquidityProvider(req: LiquidityRequest): Promise<boolean> {
|
||||
if (this.settings.useOnlyLiquidityProvider) {
|
||||
return true
|
||||
}
|
||||
if (!this.liquidProvider.CanProviderHandle(req)) {
|
||||
return false
|
||||
}
|
||||
const channels = await this.ListChannels()
|
||||
if (channels.channels.length === 0) {
|
||||
this.log("no channels, will use liquidity provider")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
async Warmup() {
|
||||
this.SubscribeAddressPaid()
|
||||
this.SubscribeInvoicePaid()
|
||||
this.SubscribeNewBlock()
|
||||
this.SubscribeHtlcEvents()
|
||||
const now = Date.now()
|
||||
return new Promise<void>((res, rej) => {
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
await this.GetInfo()
|
||||
clearInterval(interval)
|
||||
this.ready = true
|
||||
res()
|
||||
} catch (err) {
|
||||
this.log("LND is not ready yet, will try again in 1 second")
|
||||
if (Date.now() - now > 1000 * 60) {
|
||||
rej(new Error("LND not ready after 1 minute"))
|
||||
}
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
async GetInfo(): Promise<NodeInfo> {
|
||||
const res = await this.lightning.getInfo({}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
async ListPendingChannels(): Promise<PendingChannelsResponse> {
|
||||
const res = await this.lightning.pendingChannels({}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
async ListChannels(): Promise<ListChannelsResponse> {
|
||||
const res = await this.lightning.listChannels({
|
||||
activeOnly: false, inactiveOnly: false, privateOnly: false, publicOnly: false, peer: Buffer.alloc(0)
|
||||
}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
async ListClosedChannels(): Promise<ClosedChannelsResponse> {
|
||||
const res = await this.lightning.closedChannels({
|
||||
abandoned: true,
|
||||
breach: true,
|
||||
cooperative: true,
|
||||
fundingCanceled: true,
|
||||
localForce: true,
|
||||
remoteForce: true
|
||||
}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async Health(): Promise<void> {
|
||||
if (!this.ready) {
|
||||
throw new Error("not ready")
|
||||
}
|
||||
const info = await this.GetInfo()
|
||||
if (!info.syncedToChain || !info.syncedToGraph) {
|
||||
throw new Error("not synced")
|
||||
}
|
||||
}
|
||||
|
||||
RestartStreams() {
|
||||
if (!this.ready) {
|
||||
return
|
||||
}
|
||||
this.log("LND is dead, will try to reconnect in", deadLndRetrySeconds, "seconds")
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
await this.Health()
|
||||
this.log("LND is back online")
|
||||
clearInterval(interval)
|
||||
this.Warmup()
|
||||
} catch (err) {
|
||||
this.log("LND still dead, will try again in", deadLndRetrySeconds, "seconds")
|
||||
}
|
||||
}, deadLndRetrySeconds * 1000)
|
||||
}
|
||||
|
||||
async SubscribeHtlcEvents() {
|
||||
const stream = this.router.subscribeHtlcEvents({}, { abort: this.abortController.signal })
|
||||
stream.responses.onMessage(htlc => {
|
||||
this.htlcCb(htlc)
|
||||
})
|
||||
stream.responses.onError(error => {
|
||||
this.log("Error with subscribeHtlcEvents stream")
|
||||
})
|
||||
stream.responses.onComplete(() => {
|
||||
this.log("subscribeHtlcEvents stream closed")
|
||||
})
|
||||
}
|
||||
|
||||
async SubscribeNewBlock() {
|
||||
const { blockHeight } = await this.GetInfo()
|
||||
const stream = this.chainNotifier.registerBlockEpochNtfn({ height: blockHeight, hash: Buffer.alloc(0) }, { abort: this.abortController.signal })
|
||||
stream.responses.onMessage(block => {
|
||||
this.newBlockCb(block.height)
|
||||
})
|
||||
stream.responses.onError(error => {
|
||||
this.log("Error with onchain tx stream")
|
||||
})
|
||||
stream.responses.onComplete(() => {
|
||||
this.log("onchain tx stream closed")
|
||||
})
|
||||
}
|
||||
|
||||
SubscribeAddressPaid(): void {
|
||||
const stream = this.lightning.subscribeTransactions({
|
||||
account: "",
|
||||
endHeight: 0,
|
||||
startHeight: this.latestKnownBlockHeigh,
|
||||
}, { abort: this.abortController.signal })
|
||||
stream.responses.onMessage(tx => {
|
||||
if (tx.blockHeight > this.latestKnownBlockHeigh) {
|
||||
this.latestKnownBlockHeigh = tx.blockHeight
|
||||
}
|
||||
if (tx.numConfirmations === 0) { // only process pending transactions, confirmed transaction are processed by the newBlock CB
|
||||
tx.outputDetails.forEach(output => {
|
||||
if (output.isOurAddress) {
|
||||
this.log("received chan TX", Number(output.amount), "sats", "for", output.address)
|
||||
this.addressPaidCb({ hash: tx.txHash, index: Number(output.outputIndex) }, output.address, Number(output.amount), false)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
stream.responses.onError(error => {
|
||||
this.log("Error with onchain tx stream")
|
||||
})
|
||||
stream.responses.onComplete(() => {
|
||||
this.log("onchain tx stream closed")
|
||||
})
|
||||
}
|
||||
|
||||
SubscribeInvoicePaid(): void {
|
||||
const stream = this.lightning.subscribeInvoices({
|
||||
settleIndex: BigInt(this.latestKnownSettleIndex),
|
||||
addIndex: 0n,
|
||||
}, { abort: this.abortController.signal })
|
||||
stream.responses.onMessage(invoice => {
|
||||
if (invoice.state === Invoice_InvoiceState.SETTLED) {
|
||||
this.log("An invoice was paid for", Number(invoice.amtPaidSat), "sats", invoice.paymentRequest)
|
||||
this.latestKnownSettleIndex = Number(invoice.settleIndex)
|
||||
this.invoicePaidCb(invoice.paymentRequest, Number(invoice.amtPaidSat), false)
|
||||
}
|
||||
})
|
||||
stream.responses.onError(error => {
|
||||
this.log("Error with invoice stream")
|
||||
})
|
||||
stream.responses.onComplete(() => {
|
||||
this.log("invoice stream closed")
|
||||
this.RestartStreams()
|
||||
})
|
||||
}
|
||||
|
||||
async NewAddress(addressType: Types.AddressType): Promise<NewAddressResponse> {
|
||||
this.log("generating new address")
|
||||
await this.Health()
|
||||
let lndAddressType: AddressType
|
||||
switch (addressType) {
|
||||
case Types.AddressType.NESTED_PUBKEY_HASH:
|
||||
lndAddressType = AddressType.NESTED_PUBKEY_HASH
|
||||
break;
|
||||
case Types.AddressType.WITNESS_PUBKEY_HASH:
|
||||
lndAddressType = AddressType.WITNESS_PUBKEY_HASH
|
||||
break;
|
||||
case Types.AddressType.TAPROOT_PUBKEY:
|
||||
lndAddressType = AddressType.TAPROOT_PUBKEY
|
||||
break;
|
||||
default:
|
||||
throw new Error("unknown address type " + addressType)
|
||||
}
|
||||
const res = await this.lightning.newAddress({ account: "", type: lndAddressType }, DeadLineMetadata())
|
||||
this.log("new address", res.response.address)
|
||||
return res.response
|
||||
}
|
||||
|
||||
async NewInvoice(value: number, memo: string, expiry: number): Promise<Invoice> {
|
||||
this.log("generating new invoice for", value, "sats")
|
||||
await this.Health()
|
||||
const shouldUseLiquidityProvider = await this.ShouldUseLiquidityProvider({ action: 'receive', amount: value })
|
||||
if (shouldUseLiquidityProvider) {
|
||||
const invoice = await this.liquidProvider.AddInvoice(value, memo)
|
||||
return { payRequest: invoice }
|
||||
}
|
||||
const res = await this.lightning.addInvoice(AddInvoiceReq(value, expiry, false, memo), DeadLineMetadata())
|
||||
this.log("new invoice", res.response.paymentRequest)
|
||||
return { payRequest: res.response.paymentRequest }
|
||||
}
|
||||
|
||||
async DecodeInvoice(paymentRequest: string): Promise<DecodedInvoice> {
|
||||
const res = await this.lightning.decodePayReq({ payReq: paymentRequest }, DeadLineMetadata())
|
||||
return { numSatoshis: Number(res.response.numSatoshis), paymentHash: res.response.paymentHash }
|
||||
}
|
||||
|
||||
GetFeeLimitAmount(amount: number): number {
|
||||
return Math.ceil(amount * this.settings.feeRateLimit + this.settings.feeFixedLimit);
|
||||
}
|
||||
|
||||
GetMaxWithinLimit(amount: number): number {
|
||||
return Math.max(0, Math.floor(amount * (1 - this.settings.feeRateLimit) - this.settings.feeFixedLimit))
|
||||
}
|
||||
|
||||
async ChannelBalance(): Promise<{ local: number, remote: number }> {
|
||||
const res = await this.lightning.channelBalance({})
|
||||
const r = res.response
|
||||
return { local: r.localBalance ? Number(r.localBalance.sat) : 0, remote: r.remoteBalance ? Number(r.remoteBalance.sat) : 0 }
|
||||
}
|
||||
async PayInvoice(invoice: string, amount: number, feeLimit: number): Promise<PaidInvoice> {
|
||||
if (this.outgoingOpsLocked) {
|
||||
this.log("outgoing ops locked, rejecting payment request")
|
||||
throw new Error("lnd node is currently out of sync")
|
||||
}
|
||||
await this.Health()
|
||||
this.log("paying invoice", invoice, "for", amount, "sats")
|
||||
const shouldUseLiquidityProvider = await this.ShouldUseLiquidityProvider({ action: 'spend', amount })
|
||||
if (shouldUseLiquidityProvider) {
|
||||
const res = await this.liquidProvider.PayInvoice(invoice)
|
||||
return { feeSat: res.network_fee + res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage }
|
||||
}
|
||||
const abortController = new AbortController()
|
||||
const req = PayInvoiceReq(invoice, amount, feeLimit)
|
||||
const stream = this.router.sendPaymentV2(req, { abort: abortController.signal })
|
||||
return new Promise((res, rej) => {
|
||||
stream.responses.onError(error => {
|
||||
this.log("invoice payment failed", error)
|
||||
rej(error)
|
||||
})
|
||||
stream.responses.onMessage(payment => {
|
||||
switch (payment.status) {
|
||||
case Payment_PaymentStatus.FAILED:
|
||||
console.log(payment)
|
||||
this.log("invoice payment failed", payment.failureReason)
|
||||
rej(PaymentFailureReason[payment.failureReason])
|
||||
return
|
||||
case Payment_PaymentStatus.SUCCEEDED:
|
||||
this.log("invoice payment succeded", Number(payment.valueSat))
|
||||
res({ feeSat: Math.ceil(Number(payment.feeMsat) / 1000), valueSat: Number(payment.valueSat), paymentPreimage: payment.paymentPreimage })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async EstimateChainFees(address: string, amount: number, targetConf: number): Promise<EstimateFeeResponse> {
|
||||
await this.Health()
|
||||
const res = await this.lightning.estimateFee({
|
||||
addrToAmount: { [address]: BigInt(amount) },
|
||||
minConfs: 1,
|
||||
spendUnconfirmed: false,
|
||||
targetConf: targetConf
|
||||
})
|
||||
return res.response
|
||||
}
|
||||
|
||||
async PayAddress(address: string, amount: number, satPerVByte: number, label = ""): Promise<SendCoinsResponse> {
|
||||
if (this.outgoingOpsLocked) {
|
||||
this.log("outgoing ops locked, rejecting payment request")
|
||||
throw new Error("lnd node is currently out of sync")
|
||||
}
|
||||
await this.Health()
|
||||
this.log("sending chain TX for", amount, "sats", "to", address)
|
||||
const res = await this.lightning.sendCoins(SendCoinsReq(address, amount, satPerVByte, label), DeadLineMetadata())
|
||||
this.log("sent chain TX for", amount, "sats", "to", address)
|
||||
return res.response
|
||||
}
|
||||
|
||||
async GetTransactions(startHeight: number): Promise<TransactionDetails> {
|
||||
await this.Health()
|
||||
const res = await this.lightning.getTransactions({ startHeight, endHeight: 0, account: "" }, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async GetChannelBalance() {
|
||||
const res = await this.lightning.channelBalance({}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async GetWalletBalance() {
|
||||
const res = await this.lightning.walletBalance({}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async GetBalance(): Promise<BalanceInfo> {
|
||||
const wRes = await this.lightning.walletBalance({}, DeadLineMetadata())
|
||||
const { confirmedBalance, unconfirmedBalance, totalBalance } = wRes.response
|
||||
const { response } = await this.lightning.listChannels({
|
||||
activeOnly: false, inactiveOnly: false, privateOnly: false, publicOnly: false, peer: Buffer.alloc(0)
|
||||
}, DeadLineMetadata())
|
||||
const channelsBalance = response.channels.map(c => ({
|
||||
channelId: c.chanId,
|
||||
localBalanceSats: Number(c.localBalance),
|
||||
remoteBalanceSats: Number(c.remoteBalance),
|
||||
htlcs: c.pendingHtlcs.map(htlc => ({ incoming: htlc.incoming, amount: Number(htlc.amount), index: Number(htlc.htlcIndex), fwIndex: Number(htlc.forwardingHtlcIndex) }))
|
||||
}))
|
||||
return { confirmedBalance: Number(confirmedBalance), unconfirmedBalance: Number(unconfirmedBalance), totalBalance: Number(totalBalance), channelsBalance }
|
||||
}
|
||||
|
||||
async GetForwardingHistory(indexOffset: number, startTime = 0): Promise<ForwardingHistoryResponse> {
|
||||
const { response } = await this.lightning.forwardingHistory({ indexOffset, numMaxEvents: 0, startTime: BigInt(startTime), endTime: 0n, peerAliasLookup: false }, DeadLineMetadata())
|
||||
return response
|
||||
}
|
||||
|
||||
async GetAllPaidInvoices(max: number) {
|
||||
const res = await this.lightning.listInvoices({ indexOffset: 0n, numMaxInvoices: BigInt(max), pendingOnly: false, reversed: true }, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
async GetAllPayments(max: number) {
|
||||
const res = await this.lightning.listPayments({ countTotalPayments: false, includeIncomplete: false, indexOffset: 0n, maxPayments: BigInt(max), reversed: true })
|
||||
return res.response
|
||||
}
|
||||
|
||||
async ConnectPeer(addr: { pubkey: string, host: string }) {
|
||||
const res = await this.lightning.connectPeer({
|
||||
addr,
|
||||
perm: true,
|
||||
timeout: 0n
|
||||
}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async ListPeers() {
|
||||
const res = await this.lightning.listPeers({ latestError: true }, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async OpenChannel(destination: string, closeAddress: string, fundingAmount: number, pushSats: number) {
|
||||
const abortController = new AbortController()
|
||||
const req = OpenChannelReq(destination, closeAddress, fundingAmount, pushSats)
|
||||
const stream = this.lightning.openChannel(req, { abort: abortController.signal })
|
||||
return new Promise((res, rej) => {
|
||||
stream.responses.onMessage(message => {
|
||||
console.log("message", message)
|
||||
switch (message.update.oneofKind) {
|
||||
case 'chanPending':
|
||||
res(Buffer.from(message.pendingChanId).toString('base64'))
|
||||
break
|
||||
}
|
||||
})
|
||||
stream.responses.onError(error => {
|
||||
console.log("error", error)
|
||||
rej(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
//const grpc = require('@grpc/grpc-js');
|
||||
import crypto from 'crypto'
|
||||
import { credentials, Metadata } from '@grpc/grpc-js'
|
||||
import { GrpcTransport } from "@protobuf-ts/grpc-transport";
|
||||
import fs from 'fs'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { LightningClient } from '../../../proto/lnd/lightning.client.js'
|
||||
import { InvoicesClient } from '../../../proto/lnd/invoices.client.js'
|
||||
import { RouterClient } from '../../../proto/lnd/router.client.js'
|
||||
import { ChainNotifierClient } from '../../../proto/lnd/chainnotifier.client.js'
|
||||
import { GetInfoResponse, AddressType, NewAddressResponse, AddInvoiceResponse, Invoice_InvoiceState, PayReq, Payment_PaymentStatus, Payment, PaymentFailureReason, SendCoinsResponse, EstimateFeeResponse, ChannelBalanceResponse, TransactionDetails, ListChannelsResponse, ClosedChannelsResponse, PendingChannelsResponse, ForwardingHistoryResponse } from '../../../proto/lnd/lightning.js'
|
||||
import { OpenChannelReq } from './openChannelReq.js';
|
||||
import { AddInvoiceReq } from './addInvoiceReq.js';
|
||||
import { PayInvoiceReq } from './payInvoiceReq.js';
|
||||
import { SendCoinsReq } from './sendCoinsReq.js';
|
||||
import { LndSettings, AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb, HtlcCb, BalanceInfo } from './settings.js';
|
||||
import { getLogger } from '../helpers/logger.js';
|
||||
import { HtlcEvent_EventType } from '../../../proto/lnd/router.js';
|
||||
import { LiquidityProvider, LiquidityRequest } from './liquidityProvider.js';
|
||||
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
|
||||
const deadLndRetrySeconds = 5
|
||||
export default class {
|
||||
lightning: LightningClient
|
||||
invoices: InvoicesClient
|
||||
router: RouterClient
|
||||
chainNotifier: ChainNotifierClient
|
||||
settings: LndSettings
|
||||
ready = false
|
||||
latestKnownBlockHeigh = 0
|
||||
latestKnownSettleIndex = 0
|
||||
abortController = new AbortController()
|
||||
addressPaidCb: AddressPaidCb
|
||||
invoicePaidCb: InvoicePaidCb
|
||||
newBlockCb: NewBlockCb
|
||||
htlcCb: HtlcCb
|
||||
log = getLogger({ component: 'lndManager' })
|
||||
outgoingOpsLocked = false
|
||||
liquidProvider: LiquidityProvider
|
||||
constructor(settings: LndSettings, liquidProvider: LiquidityProvider, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb) {
|
||||
this.settings = settings
|
||||
this.addressPaidCb = addressPaidCb
|
||||
this.invoicePaidCb = invoicePaidCb
|
||||
this.newBlockCb = newBlockCb
|
||||
this.htlcCb = htlcCb
|
||||
const { lndAddr, lndCertPath, lndMacaroonPath } = this.settings.mainNode
|
||||
const lndCert = fs.readFileSync(lndCertPath);
|
||||
const macaroon = fs.readFileSync(lndMacaroonPath).toString('hex');
|
||||
const sslCreds = credentials.createSsl(lndCert);
|
||||
const macaroonCreds = credentials.createFromMetadataGenerator(
|
||||
function (args: any, callback: any) {
|
||||
let metadata = new Metadata();
|
||||
metadata.add('macaroon', macaroon);
|
||||
callback(null, metadata);
|
||||
},
|
||||
);
|
||||
const creds = credentials.combineChannelCredentials(
|
||||
sslCreds,
|
||||
macaroonCreds,
|
||||
);
|
||||
const transport = new GrpcTransport({ host: lndAddr, channelCredentials: creds })
|
||||
this.lightning = new LightningClient(transport)
|
||||
this.invoices = new InvoicesClient(transport)
|
||||
this.router = new RouterClient(transport)
|
||||
this.chainNotifier = new ChainNotifierClient(transport)
|
||||
this.liquidProvider = liquidProvider
|
||||
}
|
||||
|
||||
LockOutgoingOperations(): void {
|
||||
this.outgoingOpsLocked = true
|
||||
}
|
||||
UnlockOutgoingOperations(): void {
|
||||
this.outgoingOpsLocked = false
|
||||
}
|
||||
|
||||
SetMockInvoiceAsPaid(invoice: string, amount: number): Promise<void> {
|
||||
throw new Error("SetMockInvoiceAsPaid only available in mock mode")
|
||||
}
|
||||
Stop() {
|
||||
this.abortController.abort()
|
||||
this.liquidProvider.Stop()
|
||||
}
|
||||
|
||||
async ShouldUseLiquidityProvider(req: LiquidityRequest): Promise<boolean> {
|
||||
if (this.settings.useOnlyLiquidityProvider) {
|
||||
return true
|
||||
}
|
||||
if (!this.liquidProvider.CanProviderHandle(req)) {
|
||||
return false
|
||||
}
|
||||
const channels = await this.ListChannels()
|
||||
if (channels.channels.length === 0) {
|
||||
this.log("no channels, will use liquidity provider")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
async Warmup() {
|
||||
this.SubscribeAddressPaid()
|
||||
this.SubscribeInvoicePaid()
|
||||
this.SubscribeNewBlock()
|
||||
this.SubscribeHtlcEvents()
|
||||
const now = Date.now()
|
||||
return new Promise<void>((res, rej) => {
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
await this.GetInfo()
|
||||
clearInterval(interval)
|
||||
this.ready = true
|
||||
res()
|
||||
} catch (err) {
|
||||
this.log("LND is not ready yet, will try again in 1 second")
|
||||
if (Date.now() - now > 1000 * 60) {
|
||||
rej(new Error("LND not ready after 1 minute"))
|
||||
}
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
async GetInfo(): Promise<NodeInfo> {
|
||||
const res = await this.lightning.getInfo({}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
async ListPendingChannels(): Promise<PendingChannelsResponse> {
|
||||
const res = await this.lightning.pendingChannels({}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
async ListChannels(): Promise<ListChannelsResponse> {
|
||||
const res = await this.lightning.listChannels({
|
||||
activeOnly: false, inactiveOnly: false, privateOnly: false, publicOnly: false, peer: Buffer.alloc(0)
|
||||
}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
async ListClosedChannels(): Promise<ClosedChannelsResponse> {
|
||||
const res = await this.lightning.closedChannels({
|
||||
abandoned: true,
|
||||
breach: true,
|
||||
cooperative: true,
|
||||
fundingCanceled: true,
|
||||
localForce: true,
|
||||
remoteForce: true
|
||||
}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async Health(): Promise<void> {
|
||||
if (!this.ready) {
|
||||
throw new Error("not ready")
|
||||
}
|
||||
const info = await this.GetInfo()
|
||||
if (!info.syncedToChain || !info.syncedToGraph) {
|
||||
throw new Error("not synced")
|
||||
}
|
||||
}
|
||||
|
||||
RestartStreams() {
|
||||
if (!this.ready) {
|
||||
return
|
||||
}
|
||||
this.log("LND is dead, will try to reconnect in", deadLndRetrySeconds, "seconds")
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
await this.Health()
|
||||
this.log("LND is back online")
|
||||
clearInterval(interval)
|
||||
this.Warmup()
|
||||
} catch (err) {
|
||||
this.log("LND still dead, will try again in", deadLndRetrySeconds, "seconds")
|
||||
}
|
||||
}, deadLndRetrySeconds * 1000)
|
||||
}
|
||||
|
||||
async SubscribeHtlcEvents() {
|
||||
const stream = this.router.subscribeHtlcEvents({}, { abort: this.abortController.signal })
|
||||
stream.responses.onMessage(htlc => {
|
||||
this.htlcCb(htlc)
|
||||
})
|
||||
stream.responses.onError(error => {
|
||||
this.log("Error with subscribeHtlcEvents stream")
|
||||
})
|
||||
stream.responses.onComplete(() => {
|
||||
this.log("subscribeHtlcEvents stream closed")
|
||||
})
|
||||
}
|
||||
|
||||
async SubscribeNewBlock() {
|
||||
const { blockHeight } = await this.GetInfo()
|
||||
const stream = this.chainNotifier.registerBlockEpochNtfn({ height: blockHeight, hash: Buffer.alloc(0) }, { abort: this.abortController.signal })
|
||||
stream.responses.onMessage(block => {
|
||||
this.newBlockCb(block.height)
|
||||
})
|
||||
stream.responses.onError(error => {
|
||||
this.log("Error with onchain tx stream")
|
||||
})
|
||||
stream.responses.onComplete(() => {
|
||||
this.log("onchain tx stream closed")
|
||||
})
|
||||
}
|
||||
|
||||
SubscribeAddressPaid(): void {
|
||||
const stream = this.lightning.subscribeTransactions({
|
||||
account: "",
|
||||
endHeight: 0,
|
||||
startHeight: this.latestKnownBlockHeigh,
|
||||
}, { abort: this.abortController.signal })
|
||||
stream.responses.onMessage(tx => {
|
||||
if (tx.blockHeight > this.latestKnownBlockHeigh) {
|
||||
this.latestKnownBlockHeigh = tx.blockHeight
|
||||
}
|
||||
if (tx.numConfirmations === 0) { // only process pending transactions, confirmed transaction are processed by the newBlock CB
|
||||
tx.outputDetails.forEach(output => {
|
||||
if (output.isOurAddress) {
|
||||
this.log("received chan TX", Number(output.amount), "sats", "for", output.address)
|
||||
this.addressPaidCb({ hash: tx.txHash, index: Number(output.outputIndex) }, output.address, Number(output.amount), false)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
stream.responses.onError(error => {
|
||||
this.log("Error with onchain tx stream")
|
||||
})
|
||||
stream.responses.onComplete(() => {
|
||||
this.log("onchain tx stream closed")
|
||||
})
|
||||
}
|
||||
|
||||
SubscribeInvoicePaid(): void {
|
||||
const stream = this.lightning.subscribeInvoices({
|
||||
settleIndex: BigInt(this.latestKnownSettleIndex),
|
||||
addIndex: 0n,
|
||||
}, { abort: this.abortController.signal })
|
||||
stream.responses.onMessage(invoice => {
|
||||
if (invoice.state === Invoice_InvoiceState.SETTLED) {
|
||||
this.log("An invoice was paid for", Number(invoice.amtPaidSat), "sats", invoice.paymentRequest)
|
||||
this.latestKnownSettleIndex = Number(invoice.settleIndex)
|
||||
this.invoicePaidCb(invoice.paymentRequest, Number(invoice.amtPaidSat), false)
|
||||
}
|
||||
})
|
||||
stream.responses.onError(error => {
|
||||
this.log("Error with invoice stream")
|
||||
})
|
||||
stream.responses.onComplete(() => {
|
||||
this.log("invoice stream closed")
|
||||
this.RestartStreams()
|
||||
})
|
||||
}
|
||||
|
||||
async NewAddress(addressType: Types.AddressType): Promise<NewAddressResponse> {
|
||||
this.log("generating new address")
|
||||
await this.Health()
|
||||
let lndAddressType: AddressType
|
||||
switch (addressType) {
|
||||
case Types.AddressType.NESTED_PUBKEY_HASH:
|
||||
lndAddressType = AddressType.NESTED_PUBKEY_HASH
|
||||
break;
|
||||
case Types.AddressType.WITNESS_PUBKEY_HASH:
|
||||
lndAddressType = AddressType.WITNESS_PUBKEY_HASH
|
||||
break;
|
||||
case Types.AddressType.TAPROOT_PUBKEY:
|
||||
lndAddressType = AddressType.TAPROOT_PUBKEY
|
||||
break;
|
||||
default:
|
||||
throw new Error("unknown address type " + addressType)
|
||||
}
|
||||
const res = await this.lightning.newAddress({ account: "", type: lndAddressType }, DeadLineMetadata())
|
||||
this.log("new address", res.response.address)
|
||||
return res.response
|
||||
}
|
||||
|
||||
async NewInvoice(value: number, memo: string, expiry: number): Promise<Invoice> {
|
||||
this.log("generating new invoice for", value, "sats")
|
||||
await this.Health()
|
||||
const shouldUseLiquidityProvider = await this.ShouldUseLiquidityProvider({ action: 'receive', amount: value })
|
||||
if (shouldUseLiquidityProvider) {
|
||||
const invoice = await this.liquidProvider.AddInvoice(value, memo)
|
||||
return { payRequest: invoice }
|
||||
}
|
||||
const res = await this.lightning.addInvoice(AddInvoiceReq(value, expiry, false, memo), DeadLineMetadata())
|
||||
this.log("new invoice", res.response.paymentRequest)
|
||||
return { payRequest: res.response.paymentRequest }
|
||||
}
|
||||
|
||||
async DecodeInvoice(paymentRequest: string): Promise<DecodedInvoice> {
|
||||
const res = await this.lightning.decodePayReq({ payReq: paymentRequest }, DeadLineMetadata())
|
||||
return { numSatoshis: Number(res.response.numSatoshis), paymentHash: res.response.paymentHash }
|
||||
}
|
||||
|
||||
GetFeeLimitAmount(amount: number): number {
|
||||
return Math.ceil(amount * this.settings.feeRateLimit + this.settings.feeFixedLimit);
|
||||
}
|
||||
|
||||
GetMaxWithinLimit(amount: number): number {
|
||||
return Math.max(0, Math.floor(amount * (1 - this.settings.feeRateLimit) - this.settings.feeFixedLimit))
|
||||
}
|
||||
|
||||
async ChannelBalance(): Promise<{ local: number, remote: number }> {
|
||||
const res = await this.lightning.channelBalance({})
|
||||
const r = res.response
|
||||
return { local: r.localBalance ? Number(r.localBalance.sat) : 0, remote: r.remoteBalance ? Number(r.remoteBalance.sat) : 0 }
|
||||
}
|
||||
async PayInvoice(invoice: string, amount: number, feeLimit: number): Promise<PaidInvoice> {
|
||||
if (this.outgoingOpsLocked) {
|
||||
this.log("outgoing ops locked, rejecting payment request")
|
||||
throw new Error("lnd node is currently out of sync")
|
||||
}
|
||||
await this.Health()
|
||||
this.log("paying invoice", invoice, "for", amount, "sats")
|
||||
const shouldUseLiquidityProvider = await this.ShouldUseLiquidityProvider({ action: 'spend', amount })
|
||||
if (shouldUseLiquidityProvider) {
|
||||
const res = await this.liquidProvider.PayInvoice(invoice)
|
||||
return { feeSat: res.network_fee + res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage }
|
||||
}
|
||||
const abortController = new AbortController()
|
||||
const req = PayInvoiceReq(invoice, amount, feeLimit)
|
||||
const stream = this.router.sendPaymentV2(req, { abort: abortController.signal })
|
||||
return new Promise((res, rej) => {
|
||||
stream.responses.onError(error => {
|
||||
this.log("invoice payment failed", error)
|
||||
rej(error)
|
||||
})
|
||||
stream.responses.onMessage(payment => {
|
||||
switch (payment.status) {
|
||||
case Payment_PaymentStatus.FAILED:
|
||||
console.log(payment)
|
||||
this.log("invoice payment failed", payment.failureReason)
|
||||
rej(PaymentFailureReason[payment.failureReason])
|
||||
return
|
||||
case Payment_PaymentStatus.SUCCEEDED:
|
||||
this.log("invoice payment succeded", Number(payment.valueSat))
|
||||
res({ feeSat: Math.ceil(Number(payment.feeMsat) / 1000), valueSat: Number(payment.valueSat), paymentPreimage: payment.paymentPreimage })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async EstimateChainFees(address: string, amount: number, targetConf: number): Promise<EstimateFeeResponse> {
|
||||
await this.Health()
|
||||
const res = await this.lightning.estimateFee({
|
||||
addrToAmount: { [address]: BigInt(amount) },
|
||||
minConfs: 1,
|
||||
spendUnconfirmed: false,
|
||||
targetConf: targetConf
|
||||
})
|
||||
return res.response
|
||||
}
|
||||
|
||||
async PayAddress(address: string, amount: number, satPerVByte: number, label = ""): Promise<SendCoinsResponse> {
|
||||
if (this.outgoingOpsLocked) {
|
||||
this.log("outgoing ops locked, rejecting payment request")
|
||||
throw new Error("lnd node is currently out of sync")
|
||||
}
|
||||
await this.Health()
|
||||
this.log("sending chain TX for", amount, "sats", "to", address)
|
||||
const res = await this.lightning.sendCoins(SendCoinsReq(address, amount, satPerVByte, label), DeadLineMetadata())
|
||||
this.log("sent chain TX for", amount, "sats", "to", address)
|
||||
return res.response
|
||||
}
|
||||
|
||||
async GetTransactions(startHeight: number): Promise<TransactionDetails> {
|
||||
await this.Health()
|
||||
const res = await this.lightning.getTransactions({ startHeight, endHeight: 0, account: "" }, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async GetChannelBalance() {
|
||||
const res = await this.lightning.channelBalance({}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async GetWalletBalance() {
|
||||
const res = await this.lightning.walletBalance({}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async GetBalance(): Promise<BalanceInfo> {
|
||||
const wRes = await this.lightning.walletBalance({}, DeadLineMetadata())
|
||||
const { confirmedBalance, unconfirmedBalance, totalBalance } = wRes.response
|
||||
const { response } = await this.lightning.listChannels({
|
||||
activeOnly: false, inactiveOnly: false, privateOnly: false, publicOnly: false, peer: Buffer.alloc(0)
|
||||
}, DeadLineMetadata())
|
||||
const channelsBalance = response.channels.map(c => ({
|
||||
channelId: c.chanId,
|
||||
localBalanceSats: Number(c.localBalance),
|
||||
remoteBalanceSats: Number(c.remoteBalance),
|
||||
htlcs: c.pendingHtlcs.map(htlc => ({ incoming: htlc.incoming, amount: Number(htlc.amount), index: Number(htlc.htlcIndex), fwIndex: Number(htlc.forwardingHtlcIndex) }))
|
||||
}))
|
||||
return { confirmedBalance: Number(confirmedBalance), unconfirmedBalance: Number(unconfirmedBalance), totalBalance: Number(totalBalance), channelsBalance }
|
||||
}
|
||||
|
||||
async GetForwardingHistory(indexOffset: number, startTime = 0): Promise<ForwardingHistoryResponse> {
|
||||
const { response } = await this.lightning.forwardingHistory({ indexOffset, numMaxEvents: 0, startTime: BigInt(startTime), endTime: 0n, peerAliasLookup: false }, DeadLineMetadata())
|
||||
return response
|
||||
}
|
||||
|
||||
async GetAllPaidInvoices(max: number) {
|
||||
const res = await this.lightning.listInvoices({ indexOffset: 0n, numMaxInvoices: BigInt(max), pendingOnly: false, reversed: true }, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
async GetAllPayments(max: number) {
|
||||
const res = await this.lightning.listPayments({ countTotalPayments: false, includeIncomplete: false, indexOffset: 0n, maxPayments: BigInt(max), reversed: true })
|
||||
return res.response
|
||||
}
|
||||
|
||||
async ConnectPeer(addr: { pubkey: string, host: string }) {
|
||||
const res = await this.lightning.connectPeer({
|
||||
addr,
|
||||
perm: true,
|
||||
timeout: 0n
|
||||
}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async ListPeers() {
|
||||
const res = await this.lightning.listPeers({ latestError: true }, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async OpenChannel(destination: string, closeAddress: string, fundingAmount: number, pushSats: number) {
|
||||
const abortController = new AbortController()
|
||||
const req = OpenChannelReq(destination, closeAddress, fundingAmount, pushSats)
|
||||
const stream = this.lightning.openChannel(req, { abort: abortController.signal })
|
||||
return new Promise((res, rej) => {
|
||||
stream.responses.onMessage(message => {
|
||||
console.log("message", message)
|
||||
switch (message.update.oneofKind) {
|
||||
case 'chanPending':
|
||||
res(Buffer.from(message.pendingChanId).toString('base64'))
|
||||
break
|
||||
}
|
||||
})
|
||||
stream.responses.onError(error => {
|
||||
console.log("error", error)
|
||||
rej(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
import fetch from "node-fetch"
|
||||
|
||||
export class LSP {
|
||||
serviceUrl: string
|
||||
constructor(serviceUrl: string) {
|
||||
this.serviceUrl = serviceUrl
|
||||
}
|
||||
|
||||
getInfo = async () => {
|
||||
const res = await fetch(`${this.serviceUrl}/getinfo`)
|
||||
const json = await res.json() as { options: {}, uris: string[] }
|
||||
}
|
||||
|
||||
createOrder = async (req: { public_key: string }) => {
|
||||
const res = await fetch(`${this.serviceUrl}/create_order`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(req),
|
||||
headers: { "Content-Type": "application/json" }
|
||||
})
|
||||
const json = await res.json() as {}
|
||||
return json
|
||||
}
|
||||
|
||||
getOrder = async (orderId: string) => {
|
||||
const res = await fetch(`${this.serviceUrl}/get_order&order_id=${orderId}`)
|
||||
const json = await res.json() as {}
|
||||
return json
|
||||
}
|
||||
import fetch from "node-fetch"
|
||||
|
||||
export class LSP {
|
||||
serviceUrl: string
|
||||
constructor(serviceUrl: string) {
|
||||
this.serviceUrl = serviceUrl
|
||||
}
|
||||
|
||||
getInfo = async () => {
|
||||
const res = await fetch(`${this.serviceUrl}/getinfo`)
|
||||
const json = await res.json() as { options: {}, uris: string[] }
|
||||
}
|
||||
|
||||
createOrder = async (req: { public_key: string }) => {
|
||||
const res = await fetch(`${this.serviceUrl}/create_order`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(req),
|
||||
headers: { "Content-Type": "application/json" }
|
||||
})
|
||||
const json = await res.json() as {}
|
||||
return json
|
||||
}
|
||||
|
||||
getOrder = async (orderId: string) => {
|
||||
const res = await fetch(`${this.serviceUrl}/get_order&order_id=${orderId}`)
|
||||
const json = await res.json() as {}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +1,33 @@
|
|||
import { CommitmentType, OpenChannelRequest } from "../../../proto/lnd/lightning.js";
|
||||
|
||||
export const OpenChannelReq = (destination: string, closeAddress: string, fundingAmount: number, pushSats: number): OpenChannelRequest => ({
|
||||
nodePubkey: Buffer.from(destination, 'hex'),
|
||||
closeAddress: closeAddress,
|
||||
localFundingAmount: BigInt(fundingAmount),
|
||||
pushSat: BigInt(pushSats),
|
||||
|
||||
satPerVbyte: 0n, // TBD
|
||||
private: false,
|
||||
minConfs: 0, // TBD
|
||||
baseFee: 1n, // TBD
|
||||
feeRate: 1n, // TBD
|
||||
targetConf: 0,
|
||||
zeroConf: false,
|
||||
maxLocalCsv: 0,
|
||||
remoteCsvDelay: 0,
|
||||
spendUnconfirmed: false,
|
||||
minHtlcMsat: 1n,
|
||||
remoteChanReserveSat: 10000n,
|
||||
remoteMaxHtlcs: 483,
|
||||
remoteMaxValueInFlightMsat: 990000000n,
|
||||
useBaseFee: true,
|
||||
useFeeRate: true,
|
||||
|
||||
// Default stuff
|
||||
commitmentType: CommitmentType.ANCHORS,
|
||||
scidAlias: false,
|
||||
nodePubkeyString: "",
|
||||
satPerByte: 0n,
|
||||
|
||||
fundingShim: undefined
|
||||
import { CommitmentType, OpenChannelRequest } from "../../../proto/lnd/lightning.js";
|
||||
|
||||
export const OpenChannelReq = (destination: string, closeAddress: string, fundingAmount: number, pushSats: number): OpenChannelRequest => ({
|
||||
nodePubkey: Buffer.from(destination, 'hex'),
|
||||
closeAddress: closeAddress,
|
||||
localFundingAmount: BigInt(fundingAmount),
|
||||
pushSat: BigInt(pushSats),
|
||||
|
||||
satPerVbyte: 0n, // TBD
|
||||
private: false,
|
||||
minConfs: 0, // TBD
|
||||
baseFee: 1n, // TBD
|
||||
feeRate: 1n, // TBD
|
||||
targetConf: 0,
|
||||
zeroConf: false,
|
||||
maxLocalCsv: 0,
|
||||
remoteCsvDelay: 0,
|
||||
spendUnconfirmed: false,
|
||||
minHtlcMsat: 1n,
|
||||
remoteChanReserveSat: 10000n,
|
||||
remoteMaxHtlcs: 483,
|
||||
remoteMaxValueInFlightMsat: 990000000n,
|
||||
useBaseFee: true,
|
||||
useFeeRate: true,
|
||||
|
||||
// Default stuff
|
||||
commitmentType: CommitmentType.ANCHORS,
|
||||
scidAlias: false,
|
||||
nodePubkeyString: "",
|
||||
satPerByte: 0n,
|
||||
|
||||
fundingShim: undefined
|
||||
})
|
||||
|
|
@ -1,30 +1,30 @@
|
|||
import { OpenChannelRequest } from "../../../proto/lnd/lightning";
|
||||
import { SendPaymentRequest } from "../../../proto/lnd/router";
|
||||
|
||||
export const PayInvoiceReq = (invoice: string, amount: number, feeLimit: number): SendPaymentRequest => ({
|
||||
amt: BigInt(amount),
|
||||
feeLimitSat: BigInt(feeLimit),
|
||||
noInflightUpdates: true,
|
||||
paymentRequest: invoice,
|
||||
maxParts: 3,
|
||||
timeoutSeconds: 50,
|
||||
|
||||
allowSelfPayment: false,
|
||||
amp: false,
|
||||
amtMsat: 0n,
|
||||
cltvLimit: 0,
|
||||
dest: Buffer.alloc(0),
|
||||
destCustomRecords: {},
|
||||
destFeatures: [],
|
||||
feeLimitMsat: 0n,
|
||||
finalCltvDelta: 0,
|
||||
lastHopPubkey: Buffer.alloc(0),
|
||||
maxShardSizeMsat: 0n,
|
||||
outgoingChanIds: [],
|
||||
paymentAddr: Buffer.alloc(0),
|
||||
paymentHash: Buffer.alloc(0),
|
||||
routeHints: [],
|
||||
timePref: 0,
|
||||
|
||||
outgoingChanId: '0'
|
||||
import { OpenChannelRequest } from "../../../proto/lnd/lightning";
|
||||
import { SendPaymentRequest } from "../../../proto/lnd/router";
|
||||
|
||||
export const PayInvoiceReq = (invoice: string, amount: number, feeLimit: number): SendPaymentRequest => ({
|
||||
amt: BigInt(amount),
|
||||
feeLimitSat: BigInt(feeLimit),
|
||||
noInflightUpdates: true,
|
||||
paymentRequest: invoice,
|
||||
maxParts: 3,
|
||||
timeoutSeconds: 50,
|
||||
|
||||
allowSelfPayment: false,
|
||||
amp: false,
|
||||
amtMsat: 0n,
|
||||
cltvLimit: 0,
|
||||
dest: Buffer.alloc(0),
|
||||
destCustomRecords: {},
|
||||
destFeatures: [],
|
||||
feeLimitMsat: 0n,
|
||||
finalCltvDelta: 0,
|
||||
lastHopPubkey: Buffer.alloc(0),
|
||||
maxShardSizeMsat: 0n,
|
||||
outgoingChanIds: [],
|
||||
paymentAddr: Buffer.alloc(0),
|
||||
paymentHash: Buffer.alloc(0),
|
||||
routeHints: [],
|
||||
timePref: 0,
|
||||
|
||||
outgoingChanId: '0'
|
||||
})
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import { SendCoinsRequest } from "../../../proto/lnd/lightning";
|
||||
|
||||
export const SendCoinsReq = (address: string, amount: number, satPerVByte: number, label = ""): SendCoinsRequest => ({
|
||||
addr: address,
|
||||
amount: BigInt(amount),
|
||||
label: label,
|
||||
satPerVbyte: BigInt(satPerVByte),
|
||||
targetConf: 0,
|
||||
minConfs: 1,
|
||||
sendAll: false,
|
||||
spendUnconfirmed: false,
|
||||
satPerByte: BigInt(0)
|
||||
import { SendCoinsRequest } from "../../../proto/lnd/lightning";
|
||||
|
||||
export const SendCoinsReq = (address: string, amount: number, satPerVByte: number, label = ""): SendCoinsRequest => ({
|
||||
addr: address,
|
||||
amount: BigInt(amount),
|
||||
label: label,
|
||||
satPerVbyte: BigInt(satPerVByte),
|
||||
targetConf: 0,
|
||||
minConfs: 1,
|
||||
sendAll: false,
|
||||
spendUnconfirmed: false,
|
||||
satPerByte: BigInt(0)
|
||||
})
|
||||
|
|
@ -1,60 +1,60 @@
|
|||
import { HtlcEvent } from "../../../proto/lnd/router"
|
||||
export type NodeSettings = {
|
||||
lndAddr: string
|
||||
lndCertPath: string
|
||||
lndMacaroonPath: string
|
||||
}
|
||||
export type LndSettings = {
|
||||
mainNode: NodeSettings
|
||||
feeRateLimit: number
|
||||
feeFixedLimit: number
|
||||
mockLnd: boolean
|
||||
liquidityProviderPub: string
|
||||
useOnlyLiquidityProvider: boolean
|
||||
|
||||
otherNode?: NodeSettings
|
||||
thirdNode?: NodeSettings
|
||||
}
|
||||
type TxOutput = {
|
||||
hash: string
|
||||
index: number
|
||||
}
|
||||
export type ChannelBalance = {
|
||||
channelId: string;
|
||||
localBalanceSats: number;
|
||||
remoteBalanceSats: number;
|
||||
htlcs: { incoming: boolean, amount: number }[]
|
||||
}
|
||||
export type BalanceInfo = {
|
||||
confirmedBalance: number;
|
||||
unconfirmedBalance: number;
|
||||
totalBalance: number;
|
||||
channelsBalance: ChannelBalance[];
|
||||
}
|
||||
|
||||
export type AddressPaidCb = (txOutput: TxOutput, address: string, amount: number, internal: boolean) => void
|
||||
export type InvoicePaidCb = (paymentRequest: string, amount: number, internal: boolean) => void
|
||||
export type NewBlockCb = (height: number) => void
|
||||
export type HtlcCb = (event: HtlcEvent) => void
|
||||
|
||||
export type NodeInfo = {
|
||||
alias: string
|
||||
syncedToChain: boolean
|
||||
syncedToGraph: boolean
|
||||
blockHeight: number
|
||||
blockHash: string
|
||||
identityPubkey: string
|
||||
uris: string[]
|
||||
}
|
||||
export type Invoice = {
|
||||
payRequest: string
|
||||
}
|
||||
export type DecodedInvoice = {
|
||||
numSatoshis: number
|
||||
paymentHash: string
|
||||
}
|
||||
export type PaidInvoice = {
|
||||
feeSat: number
|
||||
valueSat: number
|
||||
paymentPreimage: string
|
||||
import { HtlcEvent } from "../../../proto/lnd/router"
|
||||
export type NodeSettings = {
|
||||
lndAddr: string
|
||||
lndCertPath: string
|
||||
lndMacaroonPath: string
|
||||
}
|
||||
export type LndSettings = {
|
||||
mainNode: NodeSettings
|
||||
feeRateLimit: number
|
||||
feeFixedLimit: number
|
||||
mockLnd: boolean
|
||||
liquidityProviderPub: string
|
||||
useOnlyLiquidityProvider: boolean
|
||||
|
||||
otherNode?: NodeSettings
|
||||
thirdNode?: NodeSettings
|
||||
}
|
||||
type TxOutput = {
|
||||
hash: string
|
||||
index: number
|
||||
}
|
||||
export type ChannelBalance = {
|
||||
channelId: string;
|
||||
localBalanceSats: number;
|
||||
remoteBalanceSats: number;
|
||||
htlcs: { incoming: boolean, amount: number }[]
|
||||
}
|
||||
export type BalanceInfo = {
|
||||
confirmedBalance: number;
|
||||
unconfirmedBalance: number;
|
||||
totalBalance: number;
|
||||
channelsBalance: ChannelBalance[];
|
||||
}
|
||||
|
||||
export type AddressPaidCb = (txOutput: TxOutput, address: string, amount: number, internal: boolean) => void
|
||||
export type InvoicePaidCb = (paymentRequest: string, amount: number, internal: boolean) => void
|
||||
export type NewBlockCb = (height: number) => void
|
||||
export type HtlcCb = (event: HtlcEvent) => void
|
||||
|
||||
export type NodeInfo = {
|
||||
alias: string
|
||||
syncedToChain: boolean
|
||||
syncedToGraph: boolean
|
||||
blockHeight: number
|
||||
blockHash: string
|
||||
identityPubkey: string
|
||||
uris: string[]
|
||||
}
|
||||
export type Invoice = {
|
||||
payRequest: string
|
||||
}
|
||||
export type DecodedInvoice = {
|
||||
numSatoshis: number
|
||||
paymentHash: string
|
||||
}
|
||||
export type PaidInvoice = {
|
||||
feeSat: number
|
||||
valueSat: number
|
||||
paymentPreimage: string
|
||||
}
|
||||
|
|
@ -1,86 +1,86 @@
|
|||
import jwt from 'jsonwebtoken'
|
||||
import Storage from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
|
||||
import { MainSettings } from './settings.js'
|
||||
import ApplicationManager from './applicationManager.js'
|
||||
export default class {
|
||||
storage: Storage
|
||||
settings: MainSettings
|
||||
applicationManager: ApplicationManager
|
||||
constructor(storage: Storage, settings: MainSettings, applicationManager: ApplicationManager) {
|
||||
this.storage = storage
|
||||
this.settings = settings
|
||||
this.applicationManager = applicationManager
|
||||
}
|
||||
SignUserToken(userId: string, appId: string, userIdentifier: string): string {
|
||||
return jwt.sign({ user_id: userId, app_id: appId, app_user_id: userIdentifier }, this.settings.jwtSecret);
|
||||
}
|
||||
|
||||
DecodeUserToken(token?: string): { user_id: string, app_id: string, app_user_id: 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")
|
||||
const decoded = jwt.verify(token, this.settings.jwtSecret) as { user_id: string, app_id: string, app_user_id: string }
|
||||
if (!decoded.user_id || !decoded.app_id || !decoded.app_user_id) {
|
||||
throw new Error("the provided token is not a valid app user token token")
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
async BanUser(userId: string): Promise<Types.BanUserResponse> {
|
||||
const banned = await this.storage.userStorage.BanUser(userId)
|
||||
const appUsers = await this.storage.applicationStorage.GetAllAppUsersFromUser(userId)
|
||||
return {
|
||||
balance_sats: banned.balance_sats,
|
||||
banned_app_users: appUsers.map(appUser => ({
|
||||
app_id: appUser.application.app_id,
|
||||
app_name: appUser.application.name,
|
||||
user_identifier: appUser.identifier,
|
||||
nostr_pub: appUser.nostr_public_key || ""
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async GetUserInfo(ctx: Types.UserContext): Promise<Types.UserInfo> {
|
||||
const user = await this.storage.userStorage.GetUser(ctx.user_id)
|
||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||
const appUser = await this.storage.applicationStorage.GetAppUserFromUser(app, user.user_id)
|
||||
if (!appUser) {
|
||||
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
|
||||
}
|
||||
return {
|
||||
userId: ctx.user_id,
|
||||
balance: user.balance_sats,
|
||||
max_withdrawable: this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats, true),
|
||||
user_identifier: appUser.identifier
|
||||
}
|
||||
}
|
||||
|
||||
async NewInvoice(ctx: Types.UserContext, req: Types.NewInvoiceRequest): Promise<Types.NewInvoiceResponse> {
|
||||
return this.applicationManager.AddAppUserInvoice(ctx.app_id, {
|
||||
http_callback_url: "",
|
||||
invoice_req: req,
|
||||
payer_identifier: ctx.app_user_id,
|
||||
receiver_identifier: ctx.app_user_id
|
||||
})
|
||||
}
|
||||
|
||||
async PayInvoice(ctx: Types.UserContext, req: Types.PayInvoiceRequest): Promise<Types.PayInvoiceResponse> {
|
||||
return this.applicationManager.PayAppUserInvoice(ctx.app_id, {
|
||||
amount: req.amount,
|
||||
invoice: req.invoice,
|
||||
user_identifier: ctx.app_user_id
|
||||
})
|
||||
}
|
||||
async PayAddress(ctx: Types.UserContext, req: Types.PayInvoiceRequest): Promise<Types.PayInvoiceResponse> {
|
||||
return this.applicationManager.PayAppUserInvoice(ctx.app_id, {
|
||||
amount: req.amount,
|
||||
invoice: req.invoice,
|
||||
user_identifier: ctx.app_user_id
|
||||
})
|
||||
}
|
||||
import jwt from 'jsonwebtoken'
|
||||
import Storage from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
|
||||
import { MainSettings } from './settings.js'
|
||||
import ApplicationManager from './applicationManager.js'
|
||||
export default class {
|
||||
storage: Storage
|
||||
settings: MainSettings
|
||||
applicationManager: ApplicationManager
|
||||
constructor(storage: Storage, settings: MainSettings, applicationManager: ApplicationManager) {
|
||||
this.storage = storage
|
||||
this.settings = settings
|
||||
this.applicationManager = applicationManager
|
||||
}
|
||||
SignUserToken(userId: string, appId: string, userIdentifier: string): string {
|
||||
return jwt.sign({ user_id: userId, app_id: appId, app_user_id: userIdentifier }, this.settings.jwtSecret);
|
||||
}
|
||||
|
||||
DecodeUserToken(token?: string): { user_id: string, app_id: string, app_user_id: 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")
|
||||
const decoded = jwt.verify(token, this.settings.jwtSecret) as { user_id: string, app_id: string, app_user_id: string }
|
||||
if (!decoded.user_id || !decoded.app_id || !decoded.app_user_id) {
|
||||
throw new Error("the provided token is not a valid app user token token")
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
async BanUser(userId: string): Promise<Types.BanUserResponse> {
|
||||
const banned = await this.storage.userStorage.BanUser(userId)
|
||||
const appUsers = await this.storage.applicationStorage.GetAllAppUsersFromUser(userId)
|
||||
return {
|
||||
balance_sats: banned.balance_sats,
|
||||
banned_app_users: appUsers.map(appUser => ({
|
||||
app_id: appUser.application.app_id,
|
||||
app_name: appUser.application.name,
|
||||
user_identifier: appUser.identifier,
|
||||
nostr_pub: appUser.nostr_public_key || ""
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async GetUserInfo(ctx: Types.UserContext): Promise<Types.UserInfo> {
|
||||
const user = await this.storage.userStorage.GetUser(ctx.user_id)
|
||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||
const appUser = await this.storage.applicationStorage.GetAppUserFromUser(app, user.user_id)
|
||||
if (!appUser) {
|
||||
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
|
||||
}
|
||||
return {
|
||||
userId: ctx.user_id,
|
||||
balance: user.balance_sats,
|
||||
max_withdrawable: this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats, true),
|
||||
user_identifier: appUser.identifier
|
||||
}
|
||||
}
|
||||
|
||||
async NewInvoice(ctx: Types.UserContext, req: Types.NewInvoiceRequest): Promise<Types.NewInvoiceResponse> {
|
||||
return this.applicationManager.AddAppUserInvoice(ctx.app_id, {
|
||||
http_callback_url: "",
|
||||
invoice_req: req,
|
||||
payer_identifier: ctx.app_user_id,
|
||||
receiver_identifier: ctx.app_user_id
|
||||
})
|
||||
}
|
||||
|
||||
async PayInvoice(ctx: Types.UserContext, req: Types.PayInvoiceRequest): Promise<Types.PayInvoiceResponse> {
|
||||
return this.applicationManager.PayAppUserInvoice(ctx.app_id, {
|
||||
amount: req.amount,
|
||||
invoice: req.invoice,
|
||||
user_identifier: ctx.app_user_id
|
||||
})
|
||||
}
|
||||
async PayAddress(ctx: Types.UserContext, req: Types.PayInvoiceRequest): Promise<Types.PayInvoiceResponse> {
|
||||
return this.applicationManager.PayAppUserInvoice(ctx.app_id, {
|
||||
amount: req.amount,
|
||||
invoice: req.invoice,
|
||||
user_identifier: ctx.app_user_id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,265 +1,265 @@
|
|||
import jwt from 'jsonwebtoken'
|
||||
import Storage from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.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'
|
||||
import { PubLogger, getLogger } from '../helpers/logger.js'
|
||||
import crypto from 'crypto'
|
||||
import { Application } from '../storage/entity/Application.js'
|
||||
|
||||
const TOKEN_EXPIRY_TIME = 2 * 60 * 1000 // 2 minutes, in milliseconds
|
||||
|
||||
type NsecLinkingData = {
|
||||
serialId: number,
|
||||
expiry: number
|
||||
}
|
||||
export default class {
|
||||
storage: Storage
|
||||
settings: MainSettings
|
||||
paymentManager: PaymentManager
|
||||
nPubLinkingTokens = new Map<string, NsecLinkingData>();
|
||||
linkingTokenInterval: NodeJS.Timeout | null = null
|
||||
serviceBeaconInterval: NodeJS.Timeout | null = null
|
||||
log: PubLogger
|
||||
constructor(storage: Storage, settings: MainSettings, paymentManager: PaymentManager) {
|
||||
this.log = getLogger({ component: "ApplicationManager" })
|
||||
this.storage = storage
|
||||
this.settings = settings
|
||||
this.paymentManager = paymentManager
|
||||
this.StartLinkingTokenInterval()
|
||||
}
|
||||
|
||||
StartLinkingTokenInterval() {
|
||||
this.linkingTokenInterval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (let [token, data] of this.nPubLinkingTokens) {
|
||||
if (data.expiry <= now) {
|
||||
const copy = { ...data }
|
||||
if (this.nPubLinkingTokens.delete(token)) {
|
||||
console.log("Expired an npub linking token for user serial id: ", copy.serialId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 60 * 1000); // 1 minute
|
||||
}
|
||||
|
||||
async StartAppsServiceBeacon(publishBeacon: (app: Application) => void) {
|
||||
this.serviceBeaconInterval = setInterval(async () => {
|
||||
try {
|
||||
const apps = await this.storage.applicationStorage.GetApplications()
|
||||
apps.forEach(app => {
|
||||
publishBeacon(app)
|
||||
})
|
||||
} catch (e) {
|
||||
this.log("error in beacon", (e as any).message)
|
||||
}
|
||||
}, 60 * 1000)
|
||||
}
|
||||
|
||||
Stop() {
|
||||
if (this.linkingTokenInterval) {
|
||||
clearInterval(this.linkingTokenInterval)
|
||||
}
|
||||
if (this.serviceBeaconInterval) {
|
||||
clearInterval(this.serviceBeaconInterval)
|
||||
}
|
||||
}
|
||||
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")
|
||||
const decoded = jwt.verify(token, this.settings.jwtSecret) as { appId?: string }
|
||||
if (!decoded.appId) {
|
||||
throw new Error("the provided token is not an app token")
|
||||
}
|
||||
return decoded.appId
|
||||
}
|
||||
|
||||
async SetMockAppUserBalance(appId: string, req: Types.SetMockAppUserBalanceRequest) {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const { user } = await this.storage.applicationStorage.GetOrCreateApplicationUser(app, req.user_identifier, 0)
|
||||
await this.paymentManager.SetMockUserBalance(user.user.user_id, req.amount)
|
||||
}
|
||||
|
||||
async SetMockAppBalance(appId: string, req: Types.SetMockAppBalanceRequest) {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
await this.paymentManager.SetMockUserBalance(app.owner.user_id, req.amount)
|
||||
}
|
||||
|
||||
|
||||
async AddApp(req: Types.AddAppRequest): Promise<Types.AuthApp> {
|
||||
const app = await this.storage.applicationStorage.AddApplication(req.name, req.allow_user_creation)
|
||||
getLogger({ appName: app.name })("app created")
|
||||
|
||||
return {
|
||||
app: {
|
||||
id: app.app_id,
|
||||
name: app.name,
|
||||
balance: app.owner.balance_sats,
|
||||
npub: app.nostr_public_key || ""
|
||||
},
|
||||
auth_token: this.SignAppToken(app.app_id)
|
||||
}
|
||||
}
|
||||
|
||||
async AuthApp(req: Types.AuthAppRequest): Promise<Types.AuthApp> {
|
||||
const app = await this.storage.applicationStorage.GetApplicationByName(req.name)
|
||||
if (typeof req.allow_user_creation === 'boolean') {
|
||||
await this.storage.applicationStorage.UpdateApplication(app, { allow_user_creation: req.allow_user_creation })
|
||||
}
|
||||
return {
|
||||
app: {
|
||||
id: app.app_id,
|
||||
name: app.name,
|
||||
balance: app.owner.balance_sats,
|
||||
npub: app.nostr_public_key || ""
|
||||
},
|
||||
auth_token: this.SignAppToken(app.app_id)
|
||||
}
|
||||
}
|
||||
|
||||
async GetApp(appId: string): Promise<Types.Application> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
return {
|
||||
name: app.name,
|
||||
id: app.app_id,
|
||||
balance: app.owner.balance_sats,
|
||||
npub: app.nostr_public_key || ""
|
||||
}
|
||||
}
|
||||
|
||||
async AddAppUser(appId: string, req: Types.AddAppUserRequest): Promise<Types.AppUser> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const log = getLogger({ appName: app.name })
|
||||
let u: ApplicationUser
|
||||
if (req.fail_if_exists) {
|
||||
u = await this.storage.applicationStorage.AddApplicationUser(app, req.identifier, req.balance)
|
||||
log(u.identifier, u.user.user_id, "user created")
|
||||
} else {
|
||||
const { user, created } = await this.storage.applicationStorage.GetOrCreateApplicationUser(app, req.identifier, req.balance)
|
||||
u = user
|
||||
if (created) log(u.identifier, u.user.user_id, "user created")
|
||||
}
|
||||
return {
|
||||
identifier: u.identifier,
|
||||
info: {
|
||||
userId: u.user.user_id,
|
||||
balance: u.user.balance_sats,
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true),
|
||||
user_identifier: u.identifier
|
||||
},
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true)
|
||||
}
|
||||
}
|
||||
|
||||
async AddAppInvoice(appId: string, req: Types.AddAppInvoiceRequest): Promise<Types.NewInvoiceResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const { user: payer } = await this.storage.applicationStorage.GetOrCreateApplicationUser(app, req.payer_identifier, 0)
|
||||
const opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app }
|
||||
const invoice = await this.paymentManager.NewInvoice(app.owner.user_id, req.invoice_req, opts)
|
||||
getLogger({ appName: app.name })("app invoice created to be paid by", payer.identifier)
|
||||
return invoice
|
||||
}
|
||||
|
||||
async AddAppUserInvoice(appId: string, req: Types.AddAppUserInvoiceRequest): Promise<Types.NewInvoiceResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const log = getLogger({ appName: app.name })
|
||||
const receiver = await this.storage.applicationStorage.GetApplicationUser(app, req.receiver_identifier)
|
||||
const { user: payer } = await this.storage.applicationStorage.GetOrCreateApplicationUser(app, req.payer_identifier, 0)
|
||||
const opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app }
|
||||
log("generating invoice...")
|
||||
const appUserInvoice = await this.paymentManager.NewInvoice(receiver.user.user_id, req.invoice_req, opts)
|
||||
log(receiver.identifier, "invoice created to be paid by", payer.identifier)
|
||||
return {
|
||||
invoice: appUserInvoice.invoice
|
||||
}
|
||||
}
|
||||
|
||||
async GetAppUser(appId: string, req: Types.GetAppUserRequest): Promise<Types.AppUser> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true)
|
||||
return {
|
||||
max_withdrawable: max, identifier: req.user_identifier, info: {
|
||||
userId: user.user.user_id, balance: user.user.balance_sats,
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true),
|
||||
user_identifier: user.identifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async PayAppUserInvoice(appId: string, req: Types.PayAppUserInvoiceRequest): Promise<Types.PayInvoiceResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||
const paid = await this.paymentManager.PayInvoice(appUser.user.user_id, req, app)
|
||||
getLogger({ appName: app.name })(appUser.identifier, "invoice paid", paid.amount_paid, "sats")
|
||||
return paid
|
||||
}
|
||||
|
||||
async SendAppUserToAppUserPayment(appId: string, req: Types.SendAppUserToAppUserPaymentRequest): Promise<void> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const fromUser = await this.storage.applicationStorage.GetApplicationUser(app, req.from_user_identifier)
|
||||
const { user: toUser } = await this.storage.applicationStorage.GetOrCreateApplicationUser(app, req.to_user_identifier, 0)
|
||||
await this.paymentManager.SendUserToUserPayment(fromUser.user.user_id, toUser.user.user_id, req.amount, app)
|
||||
getLogger({ appName: app.name })(toUser.identifier, "received internal payment by", fromUser.identifier, "of", req.amount, "sats")
|
||||
}
|
||||
|
||||
async SendAppUserToAppPayment(appId: string, req: Types.SendAppUserToAppPaymentRequest): Promise<void> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const fromUser = await this.storage.applicationStorage.GetApplicationUser(app, req.from_user_identifier)
|
||||
await this.paymentManager.SendUserToUserPayment(fromUser.user.user_id, app.owner.user_id, req.amount, app)
|
||||
getLogger({ appName: app.name })("app received internal payment by", fromUser.identifier, "of", req.amount, "sats")
|
||||
}
|
||||
async GetAppUserLNURLInfo(appId: string, req: Types.GetAppUserLNURLInfoRequest): Promise<Types.LnurlPayInfoResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||
return this.paymentManager.GetLnurlPayInfoFromUser(user.user.user_id, app, req.base_url_override)
|
||||
}
|
||||
async RequestNPubLinkingToken(appId: string, req: Types.RequestNPubLinkingTokenRequest): Promise<Types.RequestNPubLinkingTokenResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId);
|
||||
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier);
|
||||
if (Array.from(this.nPubLinkingTokens.values()).find(t => t.serialId === user.serial_id)) {
|
||||
throw new Error("App user already waiting on linking");
|
||||
}
|
||||
if (user.nostr_public_key) {
|
||||
throw new Error("User already has an npub");
|
||||
}
|
||||
const token = crypto.randomBytes(32).toString("hex");
|
||||
this.nPubLinkingTokens.set(token, { serialId: user.serial_id, expiry: Date.now() + TOKEN_EXPIRY_TIME })
|
||||
return { token };
|
||||
}
|
||||
|
||||
|
||||
|
||||
async LinkNpubThroughToken(ctx: Types.UserContext, req: Types.LinkNPubThroughTokenRequest): Promise<void> {
|
||||
const { app_id: appId, app_user_id: appUserId } = ctx
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, appUserId)
|
||||
await this.storage.txQueue.PushToQueue({
|
||||
dbTx: true,
|
||||
exec: async tx => {
|
||||
await this.storage.applicationStorage.RemoveApplicationUserAndBaseUser(appUser, tx);
|
||||
const entry = this.nPubLinkingTokens.get(req.token)
|
||||
if (entry && entry.expiry > Date.now()) {
|
||||
const copy = { ...entry }
|
||||
const deleted = this.nPubLinkingTokens.delete(req.token)
|
||||
if (deleted) {
|
||||
await this.storage.applicationStorage.AddNPubToApplicationUser(copy.serialId, req.nostr_pub, tx)
|
||||
} else {
|
||||
throw new Error("An uknown error occured")
|
||||
}
|
||||
} else {
|
||||
throw new Error("Token invalid or expired")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
import jwt from 'jsonwebtoken'
|
||||
import Storage from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.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'
|
||||
import { PubLogger, getLogger } from '../helpers/logger.js'
|
||||
import crypto from 'crypto'
|
||||
import { Application } from '../storage/entity/Application.js'
|
||||
|
||||
const TOKEN_EXPIRY_TIME = 2 * 60 * 1000 // 2 minutes, in milliseconds
|
||||
|
||||
type NsecLinkingData = {
|
||||
serialId: number,
|
||||
expiry: number
|
||||
}
|
||||
export default class {
|
||||
storage: Storage
|
||||
settings: MainSettings
|
||||
paymentManager: PaymentManager
|
||||
nPubLinkingTokens = new Map<string, NsecLinkingData>();
|
||||
linkingTokenInterval: NodeJS.Timeout | null = null
|
||||
serviceBeaconInterval: NodeJS.Timeout | null = null
|
||||
log: PubLogger
|
||||
constructor(storage: Storage, settings: MainSettings, paymentManager: PaymentManager) {
|
||||
this.log = getLogger({ component: "ApplicationManager" })
|
||||
this.storage = storage
|
||||
this.settings = settings
|
||||
this.paymentManager = paymentManager
|
||||
this.StartLinkingTokenInterval()
|
||||
}
|
||||
|
||||
StartLinkingTokenInterval() {
|
||||
this.linkingTokenInterval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (let [token, data] of this.nPubLinkingTokens) {
|
||||
if (data.expiry <= now) {
|
||||
const copy = { ...data }
|
||||
if (this.nPubLinkingTokens.delete(token)) {
|
||||
console.log("Expired an npub linking token for user serial id: ", copy.serialId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 60 * 1000); // 1 minute
|
||||
}
|
||||
|
||||
async StartAppsServiceBeacon(publishBeacon: (app: Application) => void) {
|
||||
this.serviceBeaconInterval = setInterval(async () => {
|
||||
try {
|
||||
const apps = await this.storage.applicationStorage.GetApplications()
|
||||
apps.forEach(app => {
|
||||
publishBeacon(app)
|
||||
})
|
||||
} catch (e) {
|
||||
this.log("error in beacon", (e as any).message)
|
||||
}
|
||||
}, 60 * 1000)
|
||||
}
|
||||
|
||||
Stop() {
|
||||
if (this.linkingTokenInterval) {
|
||||
clearInterval(this.linkingTokenInterval)
|
||||
}
|
||||
if (this.serviceBeaconInterval) {
|
||||
clearInterval(this.serviceBeaconInterval)
|
||||
}
|
||||
}
|
||||
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")
|
||||
const decoded = jwt.verify(token, this.settings.jwtSecret) as { appId?: string }
|
||||
if (!decoded.appId) {
|
||||
throw new Error("the provided token is not an app token")
|
||||
}
|
||||
return decoded.appId
|
||||
}
|
||||
|
||||
async SetMockAppUserBalance(appId: string, req: Types.SetMockAppUserBalanceRequest) {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const { user } = await this.storage.applicationStorage.GetOrCreateApplicationUser(app, req.user_identifier, 0)
|
||||
await this.paymentManager.SetMockUserBalance(user.user.user_id, req.amount)
|
||||
}
|
||||
|
||||
async SetMockAppBalance(appId: string, req: Types.SetMockAppBalanceRequest) {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
await this.paymentManager.SetMockUserBalance(app.owner.user_id, req.amount)
|
||||
}
|
||||
|
||||
|
||||
async AddApp(req: Types.AddAppRequest): Promise<Types.AuthApp> {
|
||||
const app = await this.storage.applicationStorage.AddApplication(req.name, req.allow_user_creation)
|
||||
getLogger({ appName: app.name })("app created")
|
||||
|
||||
return {
|
||||
app: {
|
||||
id: app.app_id,
|
||||
name: app.name,
|
||||
balance: app.owner.balance_sats,
|
||||
npub: app.nostr_public_key || ""
|
||||
},
|
||||
auth_token: this.SignAppToken(app.app_id)
|
||||
}
|
||||
}
|
||||
|
||||
async AuthApp(req: Types.AuthAppRequest): Promise<Types.AuthApp> {
|
||||
const app = await this.storage.applicationStorage.GetApplicationByName(req.name)
|
||||
if (typeof req.allow_user_creation === 'boolean') {
|
||||
await this.storage.applicationStorage.UpdateApplication(app, { allow_user_creation: req.allow_user_creation })
|
||||
}
|
||||
return {
|
||||
app: {
|
||||
id: app.app_id,
|
||||
name: app.name,
|
||||
balance: app.owner.balance_sats,
|
||||
npub: app.nostr_public_key || ""
|
||||
},
|
||||
auth_token: this.SignAppToken(app.app_id)
|
||||
}
|
||||
}
|
||||
|
||||
async GetApp(appId: string): Promise<Types.Application> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
return {
|
||||
name: app.name,
|
||||
id: app.app_id,
|
||||
balance: app.owner.balance_sats,
|
||||
npub: app.nostr_public_key || ""
|
||||
}
|
||||
}
|
||||
|
||||
async AddAppUser(appId: string, req: Types.AddAppUserRequest): Promise<Types.AppUser> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const log = getLogger({ appName: app.name })
|
||||
let u: ApplicationUser
|
||||
if (req.fail_if_exists) {
|
||||
u = await this.storage.applicationStorage.AddApplicationUser(app, req.identifier, req.balance)
|
||||
log(u.identifier, u.user.user_id, "user created")
|
||||
} else {
|
||||
const { user, created } = await this.storage.applicationStorage.GetOrCreateApplicationUser(app, req.identifier, req.balance)
|
||||
u = user
|
||||
if (created) log(u.identifier, u.user.user_id, "user created")
|
||||
}
|
||||
return {
|
||||
identifier: u.identifier,
|
||||
info: {
|
||||
userId: u.user.user_id,
|
||||
balance: u.user.balance_sats,
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true),
|
||||
user_identifier: u.identifier
|
||||
},
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true)
|
||||
}
|
||||
}
|
||||
|
||||
async AddAppInvoice(appId: string, req: Types.AddAppInvoiceRequest): Promise<Types.NewInvoiceResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const { user: payer } = await this.storage.applicationStorage.GetOrCreateApplicationUser(app, req.payer_identifier, 0)
|
||||
const opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app }
|
||||
const invoice = await this.paymentManager.NewInvoice(app.owner.user_id, req.invoice_req, opts)
|
||||
getLogger({ appName: app.name })("app invoice created to be paid by", payer.identifier)
|
||||
return invoice
|
||||
}
|
||||
|
||||
async AddAppUserInvoice(appId: string, req: Types.AddAppUserInvoiceRequest): Promise<Types.NewInvoiceResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const log = getLogger({ appName: app.name })
|
||||
const receiver = await this.storage.applicationStorage.GetApplicationUser(app, req.receiver_identifier)
|
||||
const { user: payer } = await this.storage.applicationStorage.GetOrCreateApplicationUser(app, req.payer_identifier, 0)
|
||||
const opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app }
|
||||
log("generating invoice...")
|
||||
const appUserInvoice = await this.paymentManager.NewInvoice(receiver.user.user_id, req.invoice_req, opts)
|
||||
log(receiver.identifier, "invoice created to be paid by", payer.identifier)
|
||||
return {
|
||||
invoice: appUserInvoice.invoice
|
||||
}
|
||||
}
|
||||
|
||||
async GetAppUser(appId: string, req: Types.GetAppUserRequest): Promise<Types.AppUser> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true)
|
||||
return {
|
||||
max_withdrawable: max, identifier: req.user_identifier, info: {
|
||||
userId: user.user.user_id, balance: user.user.balance_sats,
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true),
|
||||
user_identifier: user.identifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async PayAppUserInvoice(appId: string, req: Types.PayAppUserInvoiceRequest): Promise<Types.PayInvoiceResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||
const paid = await this.paymentManager.PayInvoice(appUser.user.user_id, req, app)
|
||||
getLogger({ appName: app.name })(appUser.identifier, "invoice paid", paid.amount_paid, "sats")
|
||||
return paid
|
||||
}
|
||||
|
||||
async SendAppUserToAppUserPayment(appId: string, req: Types.SendAppUserToAppUserPaymentRequest): Promise<void> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const fromUser = await this.storage.applicationStorage.GetApplicationUser(app, req.from_user_identifier)
|
||||
const { user: toUser } = await this.storage.applicationStorage.GetOrCreateApplicationUser(app, req.to_user_identifier, 0)
|
||||
await this.paymentManager.SendUserToUserPayment(fromUser.user.user_id, toUser.user.user_id, req.amount, app)
|
||||
getLogger({ appName: app.name })(toUser.identifier, "received internal payment by", fromUser.identifier, "of", req.amount, "sats")
|
||||
}
|
||||
|
||||
async SendAppUserToAppPayment(appId: string, req: Types.SendAppUserToAppPaymentRequest): Promise<void> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const fromUser = await this.storage.applicationStorage.GetApplicationUser(app, req.from_user_identifier)
|
||||
await this.paymentManager.SendUserToUserPayment(fromUser.user.user_id, app.owner.user_id, req.amount, app)
|
||||
getLogger({ appName: app.name })("app received internal payment by", fromUser.identifier, "of", req.amount, "sats")
|
||||
}
|
||||
async GetAppUserLNURLInfo(appId: string, req: Types.GetAppUserLNURLInfoRequest): Promise<Types.LnurlPayInfoResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||
return this.paymentManager.GetLnurlPayInfoFromUser(user.user.user_id, app, req.base_url_override)
|
||||
}
|
||||
async RequestNPubLinkingToken(appId: string, req: Types.RequestNPubLinkingTokenRequest): Promise<Types.RequestNPubLinkingTokenResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId);
|
||||
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier);
|
||||
if (Array.from(this.nPubLinkingTokens.values()).find(t => t.serialId === user.serial_id)) {
|
||||
throw new Error("App user already waiting on linking");
|
||||
}
|
||||
if (user.nostr_public_key) {
|
||||
throw new Error("User already has an npub");
|
||||
}
|
||||
const token = crypto.randomBytes(32).toString("hex");
|
||||
this.nPubLinkingTokens.set(token, { serialId: user.serial_id, expiry: Date.now() + TOKEN_EXPIRY_TIME })
|
||||
return { token };
|
||||
}
|
||||
|
||||
|
||||
|
||||
async LinkNpubThroughToken(ctx: Types.UserContext, req: Types.LinkNPubThroughTokenRequest): Promise<void> {
|
||||
const { app_id: appId, app_user_id: appUserId } = ctx
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, appUserId)
|
||||
await this.storage.txQueue.PushToQueue({
|
||||
dbTx: true,
|
||||
exec: async tx => {
|
||||
await this.storage.applicationStorage.RemoveApplicationUserAndBaseUser(appUser, tx);
|
||||
const entry = this.nPubLinkingTokens.get(req.token)
|
||||
if (entry && entry.expiry > Date.now()) {
|
||||
const copy = { ...entry }
|
||||
const deleted = this.nPubLinkingTokens.delete(req.token)
|
||||
if (deleted) {
|
||||
await this.storage.applicationStorage.AddNPubToApplicationUser(copy.serialId, req.nostr_pub, tx)
|
||||
} else {
|
||||
throw new Error("An uknown error occured")
|
||||
}
|
||||
} else {
|
||||
throw new Error("Token invalid or expired")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,253 +1,253 @@
|
|||
import fetch from "node-fetch"
|
||||
import Storage, { LoadStorageSettingsFromEnv } from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import ProductManager from './productManager.js'
|
||||
import ApplicationManager from './applicationManager.js'
|
||||
import PaymentManager, { PendingTx } from './paymentManager.js'
|
||||
import { MainSettings } from './settings.js'
|
||||
import LND from "../lnd/lnd.js"
|
||||
import { AddressPaidCb, HtlcCb, InvoicePaidCb, NewBlockCb } from "../lnd/settings.js"
|
||||
import { ERROR, getLogger, PubLogger } from "../helpers/logger.js"
|
||||
import AppUserManager from "./appUserManager.js"
|
||||
import { Application } from '../storage/entity/Application.js'
|
||||
import { UserReceivingInvoice } from '../storage/entity/UserReceivingInvoice.js'
|
||||
import { UnsignedEvent } from '../nostr/tools/event.js'
|
||||
import { NostrSend } from '../nostr/handler.js'
|
||||
import MetricsManager from '../metrics/index.js'
|
||||
import { LoggedEvent } from '../storage/eventsLog.js'
|
||||
import { LiquidityProvider } from "../lnd/liquidityProvider.js"
|
||||
|
||||
type UserOperationsSub = {
|
||||
id: string
|
||||
newIncomingInvoice: (operation: Types.UserOperation) => void
|
||||
newOutgoingInvoice: (operation: Types.UserOperation) => void
|
||||
newIncomingTx: (operation: Types.UserOperation) => void
|
||||
newOutgoingTx: (operation: Types.UserOperation) => void
|
||||
}
|
||||
const appTag = "Lightning.Pub"
|
||||
export default class {
|
||||
storage: Storage
|
||||
lnd: LND
|
||||
settings: MainSettings
|
||||
userOperationsSub: UserOperationsSub | null = null
|
||||
productManager: ProductManager
|
||||
applicationManager: ApplicationManager
|
||||
appUserManager: AppUserManager
|
||||
paymentManager: PaymentManager
|
||||
paymentSubs: Record<string, ((op: Types.UserOperation) => void) | null> = {}
|
||||
metricsManager: MetricsManager
|
||||
liquidProvider: LiquidityProvider
|
||||
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
|
||||
constructor(settings: MainSettings, storage: Storage) {
|
||||
this.settings = settings
|
||||
this.storage = storage
|
||||
this.liquidProvider = new LiquidityProvider(settings.lndSettings.liquidityProviderPub, this.invoicePaidCb)
|
||||
this.lnd = new LND(settings.lndSettings, this.liquidProvider, this.addressPaidCb, this.invoicePaidCb, this.newBlockCb, this.htlcCb)
|
||||
this.metricsManager = new MetricsManager(this.storage, this.lnd)
|
||||
|
||||
this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.addressPaidCb, this.invoicePaidCb)
|
||||
this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings)
|
||||
this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager)
|
||||
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)
|
||||
|
||||
}
|
||||
Stop() {
|
||||
this.lnd.Stop()
|
||||
this.applicationManager.Stop()
|
||||
this.paymentManager.Stop()
|
||||
}
|
||||
|
||||
StartBeacons() {
|
||||
this.applicationManager.StartAppsServiceBeacon(app => {
|
||||
this.UpdateBeacon(app, { type: 'service', name: app.name })
|
||||
})
|
||||
}
|
||||
|
||||
attachNostrSend(f: NostrSend) {
|
||||
this.nostrSend = f
|
||||
this.liquidProvider.attachNostrSend(f)
|
||||
}
|
||||
|
||||
htlcCb: HtlcCb = (e) => {
|
||||
this.metricsManager.HtlcCb(e)
|
||||
}
|
||||
|
||||
newBlockCb: NewBlockCb = (height) => {
|
||||
this.NewBlockHandler(height)
|
||||
}
|
||||
|
||||
NewBlockHandler = async (height: number) => {
|
||||
let confirmed: (PendingTx & { confs: number; })[]
|
||||
let log = getLogger({})
|
||||
|
||||
try {
|
||||
const balanceEvents = await this.paymentManager.GetLndBalance()
|
||||
await this.metricsManager.NewBlockCb(height, balanceEvents)
|
||||
confirmed = await this.paymentManager.CheckNewlyConfirmedTxs(height)
|
||||
} catch (err: any) {
|
||||
log(ERROR, "failed to check transactions after new block", err.message || err)
|
||||
return
|
||||
}
|
||||
await Promise.all(confirmed.map(async c => {
|
||||
if (c.type === 'outgoing') {
|
||||
await this.storage.paymentStorage.UpdateUserTransactionPayment(c.tx.serial_id, { confs: c.confs })
|
||||
const { linkedApplication, user, address, paid_amount: amount, service_fees: serviceFee, serial_id: serialId, chain_fees } = c.tx;
|
||||
const operationId = `${Types.UserOperationType.OUTGOING_TX}-${serialId}`
|
||||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: false, type: Types.UserOperationType.OUTGOING_TX, identifier: address, operationId, network_fee: chain_fees, service_fee: serviceFee, confirmed: true, tx_hash: c.tx.tx_hash, internal: c.tx.internal }
|
||||
this.sendOperationToNostr(linkedApplication!, user.user_id, op)
|
||||
} else {
|
||||
this.storage.StartTransaction(async tx => {
|
||||
const { user_address: userAddress, paid_amount: amount, service_fee: serviceFee, serial_id: serialId, tx_hash } = c.tx
|
||||
if (!userAddress.linkedApplication) {
|
||||
log(ERROR, "an address was paid, that has no linked application")
|
||||
return
|
||||
}
|
||||
const updateResult = await this.storage.paymentStorage.UpdateAddressReceivingTransaction(serialId, { confs: c.confs }, tx)
|
||||
if (!updateResult.affected) {
|
||||
throw new Error("unable to flag chain transaction as paid")
|
||||
}
|
||||
const addressData = `${userAddress.address}:${tx_hash}`
|
||||
this.storage.eventsLog.LogEvent({ type: 'address_paid', userId: userAddress.user.user_id, appId: userAddress.linkedApplication.app_id, appUserId: "", balance: userAddress.user.balance_sats, data: addressData, amount })
|
||||
await this.storage.userStorage.IncrementUserBalance(userAddress.user.user_id, amount - serviceFee, addressData, tx)
|
||||
if (serviceFee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(userAddress.linkedApplication.owner.user_id, serviceFee, 'fees', tx)
|
||||
}
|
||||
const operationId = `${Types.UserOperationType.INCOMING_TX}-${serialId}`
|
||||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_TX, identifier: userAddress.address, operationId, network_fee: 0, service_fee: serviceFee, confirmed: true, tx_hash: c.tx.tx_hash, internal: c.tx.internal }
|
||||
this.sendOperationToNostr(userAddress.linkedApplication!, userAddress.user.user_id, op)
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
addressPaidCb: AddressPaidCb = (txOutput, address, amount, internal) => {
|
||||
this.storage.StartTransaction(async tx => {
|
||||
const { blockHeight } = await this.lnd.GetInfo()
|
||||
const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx)
|
||||
if (!userAddress) { return }
|
||||
let log = getLogger({})
|
||||
if (!userAddress.linkedApplication) {
|
||||
log(ERROR, "an address was paid, that has no linked application")
|
||||
return
|
||||
}
|
||||
log = getLogger({ appName: userAddress.linkedApplication.name })
|
||||
const isAppUserPayment = userAddress.user.user_id !== userAddress.linkedApplication.owner.user_id
|
||||
let fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_TX, amount, isAppUserPayment)
|
||||
if (userAddress.linkedApplication && userAddress.linkedApplication.owner.user_id === userAddress.user.user_id) {
|
||||
fee = 0
|
||||
}
|
||||
try {
|
||||
// This call will fail if the transaction is already registered
|
||||
const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction(userAddress, txOutput.hash, txOutput.index, amount, fee, internal, blockHeight, tx)
|
||||
if (internal) {
|
||||
const addressData = `${address}:${txOutput.hash}`
|
||||
this.storage.eventsLog.LogEvent({ type: 'address_paid', userId: userAddress.user.user_id, appId: userAddress.linkedApplication.app_id, appUserId: "", balance: userAddress.user.balance_sats, data: addressData, amount })
|
||||
await this.storage.userStorage.IncrementUserBalance(userAddress.user.user_id, addedTx.paid_amount - fee, addressData, tx)
|
||||
if (fee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(userAddress.linkedApplication.owner.user_id, fee, 'fees', tx)
|
||||
}
|
||||
|
||||
}
|
||||
const operationId = `${Types.UserOperationType.INCOMING_TX}-${addedTx.serial_id}`
|
||||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_TX, identifier: userAddress.address, operationId, network_fee: 0, service_fee: fee, confirmed: internal, tx_hash: txOutput.hash, internal: false }
|
||||
this.sendOperationToNostr(userAddress.linkedApplication, userAddress.user.user_id, op)
|
||||
} catch {
|
||||
log(ERROR, "cannot process address paid transaction, already registered")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
invoicePaidCb: InvoicePaidCb = (paymentRequest, amount, internal) => {
|
||||
this.storage.StartTransaction(async tx => {
|
||||
let log = getLogger({})
|
||||
const userInvoice = await this.storage.paymentStorage.GetInvoiceOwner(paymentRequest, tx)
|
||||
if (!userInvoice) { return }
|
||||
if (userInvoice.paid_at_unix > 0 && internal) { log("cannot pay internally, invoice already paid"); return }
|
||||
if (userInvoice.paid_at_unix > 0 && !internal && userInvoice.paidByLnd) { log("invoice already paid by lnd"); return }
|
||||
if (!userInvoice.linkedApplication) {
|
||||
log(ERROR, "an invoice was paid, that has no linked application")
|
||||
return
|
||||
}
|
||||
log = getLogger({ appName: userInvoice.linkedApplication.name })
|
||||
const isAppUserPayment = userInvoice.user.user_id !== userInvoice.linkedApplication.owner.user_id
|
||||
let fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_INVOICE, amount, isAppUserPayment)
|
||||
if (userInvoice.linkedApplication && userInvoice.linkedApplication.owner.user_id === userInvoice.user.user_id) {
|
||||
fee = 0
|
||||
}
|
||||
try {
|
||||
await this.storage.paymentStorage.FlagInvoiceAsPaid(userInvoice, amount, fee, internal, tx)
|
||||
this.storage.eventsLog.LogEvent({ type: 'invoice_paid', userId: userInvoice.user.user_id, appId: userInvoice.linkedApplication.app_id, appUserId: "", balance: userInvoice.user.balance_sats, data: paymentRequest, amount })
|
||||
await this.storage.userStorage.IncrementUserBalance(userInvoice.user.user_id, amount - fee, userInvoice.invoice, tx)
|
||||
if (fee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(userInvoice.linkedApplication.owner.user_id, fee, 'fees', tx)
|
||||
}
|
||||
await this.triggerPaidCallback(log, userInvoice.callbackUrl)
|
||||
const operationId = `${Types.UserOperationType.INCOMING_INVOICE}-${userInvoice.serial_id}`
|
||||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_INVOICE, identifier: userInvoice.invoice, operationId, network_fee: 0, service_fee: fee, confirmed: true, tx_hash: "", internal }
|
||||
this.sendOperationToNostr(userInvoice.linkedApplication, userInvoice.user.user_id, op)
|
||||
this.createZapReceipt(log, userInvoice)
|
||||
log("paid invoice processed successfully")
|
||||
} catch (err: any) {
|
||||
log(ERROR, "cannot process paid invoice", err.message || "")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async triggerPaidCallback(log: PubLogger, url: string) {
|
||||
if (!url) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await fetch(url + "&ok=true")
|
||||
} catch (err: any) {
|
||||
log(ERROR, "error sending paid callback for invoice", err.message || "")
|
||||
}
|
||||
}
|
||||
|
||||
async sendOperationToNostr(app: Application, userId: string, op: Types.UserOperation) {
|
||||
const user = await this.storage.applicationStorage.GetAppUserFromUser(app, userId)
|
||||
if (!user || !user.nostr_public_key) {
|
||||
getLogger({ appName: app.name })("cannot notify user, not a nostr user")
|
||||
return
|
||||
}
|
||||
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } = { operation: op, requestId: "GetLiveUserOperations", status: 'OK' }
|
||||
this.nostrSend({ type: 'app', appId: app.app_id }, { type: 'content', content: JSON.stringify(message), pub: user.nostr_public_key })
|
||||
}
|
||||
|
||||
async UpdateBeacon(app: Application, content: { type: 'service', name: string }) {
|
||||
if (!app.nostr_public_key) {
|
||||
getLogger({ appName: app.name })("cannot update beacon, public key not set")
|
||||
return
|
||||
}
|
||||
const tags = [["d", appTag]]
|
||||
const event: UnsignedEvent = {
|
||||
content: JSON.stringify(content),
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 30078,
|
||||
pubkey: app.nostr_public_key,
|
||||
tags,
|
||||
}
|
||||
this.nostrSend({ type: 'app', appId: app.app_id }, { type: 'event', event })
|
||||
}
|
||||
|
||||
async createZapReceipt(log: PubLogger, invoice: UserReceivingInvoice) {
|
||||
const zapInfo = invoice.zap_info
|
||||
if (!zapInfo || !invoice.linkedApplication || !invoice.linkedApplication.nostr_public_key) {
|
||||
log(ERROR, "no zap info linked to payment")
|
||||
return
|
||||
}
|
||||
const tags = [["p", zapInfo.pub], ["bolt11", invoice.invoice], ["description", zapInfo.description]]
|
||||
if (zapInfo.eventId) {
|
||||
tags.push(["e", zapInfo.eventId])
|
||||
}
|
||||
const event: UnsignedEvent = {
|
||||
content: "",
|
||||
created_at: invoice.paid_at_unix,
|
||||
kind: 9735,
|
||||
pubkey: invoice.linkedApplication.nostr_public_key,
|
||||
tags,
|
||||
}
|
||||
log({ unsigned: event })
|
||||
this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event })
|
||||
}
|
||||
}
|
||||
import fetch from "node-fetch"
|
||||
import Storage, { LoadStorageSettingsFromEnv } from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import ProductManager from './productManager.js'
|
||||
import ApplicationManager from './applicationManager.js'
|
||||
import PaymentManager, { PendingTx } from './paymentManager.js'
|
||||
import { MainSettings } from './settings.js'
|
||||
import LND from "../lnd/lnd.js"
|
||||
import { AddressPaidCb, HtlcCb, InvoicePaidCb, NewBlockCb } from "../lnd/settings.js"
|
||||
import { ERROR, getLogger, PubLogger } from "../helpers/logger.js"
|
||||
import AppUserManager from "./appUserManager.js"
|
||||
import { Application } from '../storage/entity/Application.js'
|
||||
import { UserReceivingInvoice } from '../storage/entity/UserReceivingInvoice.js'
|
||||
import { UnsignedEvent } from '../nostr/tools/event.js'
|
||||
import { NostrSend } from '../nostr/handler.js'
|
||||
import MetricsManager from '../metrics/index.js'
|
||||
import { LoggedEvent } from '../storage/eventsLog.js'
|
||||
import { LiquidityProvider } from "../lnd/liquidityProvider.js"
|
||||
|
||||
type UserOperationsSub = {
|
||||
id: string
|
||||
newIncomingInvoice: (operation: Types.UserOperation) => void
|
||||
newOutgoingInvoice: (operation: Types.UserOperation) => void
|
||||
newIncomingTx: (operation: Types.UserOperation) => void
|
||||
newOutgoingTx: (operation: Types.UserOperation) => void
|
||||
}
|
||||
const appTag = "Lightning.Pub"
|
||||
export default class {
|
||||
storage: Storage
|
||||
lnd: LND
|
||||
settings: MainSettings
|
||||
userOperationsSub: UserOperationsSub | null = null
|
||||
productManager: ProductManager
|
||||
applicationManager: ApplicationManager
|
||||
appUserManager: AppUserManager
|
||||
paymentManager: PaymentManager
|
||||
paymentSubs: Record<string, ((op: Types.UserOperation) => void) | null> = {}
|
||||
metricsManager: MetricsManager
|
||||
liquidProvider: LiquidityProvider
|
||||
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
|
||||
constructor(settings: MainSettings, storage: Storage) {
|
||||
this.settings = settings
|
||||
this.storage = storage
|
||||
this.liquidProvider = new LiquidityProvider(settings.lndSettings.liquidityProviderPub, this.invoicePaidCb)
|
||||
this.lnd = new LND(settings.lndSettings, this.liquidProvider, this.addressPaidCb, this.invoicePaidCb, this.newBlockCb, this.htlcCb)
|
||||
this.metricsManager = new MetricsManager(this.storage, this.lnd)
|
||||
|
||||
this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.addressPaidCb, this.invoicePaidCb)
|
||||
this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings)
|
||||
this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager)
|
||||
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)
|
||||
|
||||
}
|
||||
Stop() {
|
||||
this.lnd.Stop()
|
||||
this.applicationManager.Stop()
|
||||
this.paymentManager.Stop()
|
||||
}
|
||||
|
||||
StartBeacons() {
|
||||
this.applicationManager.StartAppsServiceBeacon(app => {
|
||||
this.UpdateBeacon(app, { type: 'service', name: app.name })
|
||||
})
|
||||
}
|
||||
|
||||
attachNostrSend(f: NostrSend) {
|
||||
this.nostrSend = f
|
||||
this.liquidProvider.attachNostrSend(f)
|
||||
}
|
||||
|
||||
htlcCb: HtlcCb = (e) => {
|
||||
this.metricsManager.HtlcCb(e)
|
||||
}
|
||||
|
||||
newBlockCb: NewBlockCb = (height) => {
|
||||
this.NewBlockHandler(height)
|
||||
}
|
||||
|
||||
NewBlockHandler = async (height: number) => {
|
||||
let confirmed: (PendingTx & { confs: number; })[]
|
||||
let log = getLogger({})
|
||||
|
||||
try {
|
||||
const balanceEvents = await this.paymentManager.GetLndBalance()
|
||||
await this.metricsManager.NewBlockCb(height, balanceEvents)
|
||||
confirmed = await this.paymentManager.CheckNewlyConfirmedTxs(height)
|
||||
} catch (err: any) {
|
||||
log(ERROR, "failed to check transactions after new block", err.message || err)
|
||||
return
|
||||
}
|
||||
await Promise.all(confirmed.map(async c => {
|
||||
if (c.type === 'outgoing') {
|
||||
await this.storage.paymentStorage.UpdateUserTransactionPayment(c.tx.serial_id, { confs: c.confs })
|
||||
const { linkedApplication, user, address, paid_amount: amount, service_fees: serviceFee, serial_id: serialId, chain_fees } = c.tx;
|
||||
const operationId = `${Types.UserOperationType.OUTGOING_TX}-${serialId}`
|
||||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: false, type: Types.UserOperationType.OUTGOING_TX, identifier: address, operationId, network_fee: chain_fees, service_fee: serviceFee, confirmed: true, tx_hash: c.tx.tx_hash, internal: c.tx.internal }
|
||||
this.sendOperationToNostr(linkedApplication!, user.user_id, op)
|
||||
} else {
|
||||
this.storage.StartTransaction(async tx => {
|
||||
const { user_address: userAddress, paid_amount: amount, service_fee: serviceFee, serial_id: serialId, tx_hash } = c.tx
|
||||
if (!userAddress.linkedApplication) {
|
||||
log(ERROR, "an address was paid, that has no linked application")
|
||||
return
|
||||
}
|
||||
const updateResult = await this.storage.paymentStorage.UpdateAddressReceivingTransaction(serialId, { confs: c.confs }, tx)
|
||||
if (!updateResult.affected) {
|
||||
throw new Error("unable to flag chain transaction as paid")
|
||||
}
|
||||
const addressData = `${userAddress.address}:${tx_hash}`
|
||||
this.storage.eventsLog.LogEvent({ type: 'address_paid', userId: userAddress.user.user_id, appId: userAddress.linkedApplication.app_id, appUserId: "", balance: userAddress.user.balance_sats, data: addressData, amount })
|
||||
await this.storage.userStorage.IncrementUserBalance(userAddress.user.user_id, amount - serviceFee, addressData, tx)
|
||||
if (serviceFee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(userAddress.linkedApplication.owner.user_id, serviceFee, 'fees', tx)
|
||||
}
|
||||
const operationId = `${Types.UserOperationType.INCOMING_TX}-${serialId}`
|
||||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_TX, identifier: userAddress.address, operationId, network_fee: 0, service_fee: serviceFee, confirmed: true, tx_hash: c.tx.tx_hash, internal: c.tx.internal }
|
||||
this.sendOperationToNostr(userAddress.linkedApplication!, userAddress.user.user_id, op)
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
addressPaidCb: AddressPaidCb = (txOutput, address, amount, internal) => {
|
||||
this.storage.StartTransaction(async tx => {
|
||||
const { blockHeight } = await this.lnd.GetInfo()
|
||||
const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx)
|
||||
if (!userAddress) { return }
|
||||
let log = getLogger({})
|
||||
if (!userAddress.linkedApplication) {
|
||||
log(ERROR, "an address was paid, that has no linked application")
|
||||
return
|
||||
}
|
||||
log = getLogger({ appName: userAddress.linkedApplication.name })
|
||||
const isAppUserPayment = userAddress.user.user_id !== userAddress.linkedApplication.owner.user_id
|
||||
let fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_TX, amount, isAppUserPayment)
|
||||
if (userAddress.linkedApplication && userAddress.linkedApplication.owner.user_id === userAddress.user.user_id) {
|
||||
fee = 0
|
||||
}
|
||||
try {
|
||||
// This call will fail if the transaction is already registered
|
||||
const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction(userAddress, txOutput.hash, txOutput.index, amount, fee, internal, blockHeight, tx)
|
||||
if (internal) {
|
||||
const addressData = `${address}:${txOutput.hash}`
|
||||
this.storage.eventsLog.LogEvent({ type: 'address_paid', userId: userAddress.user.user_id, appId: userAddress.linkedApplication.app_id, appUserId: "", balance: userAddress.user.balance_sats, data: addressData, amount })
|
||||
await this.storage.userStorage.IncrementUserBalance(userAddress.user.user_id, addedTx.paid_amount - fee, addressData, tx)
|
||||
if (fee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(userAddress.linkedApplication.owner.user_id, fee, 'fees', tx)
|
||||
}
|
||||
|
||||
}
|
||||
const operationId = `${Types.UserOperationType.INCOMING_TX}-${addedTx.serial_id}`
|
||||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_TX, identifier: userAddress.address, operationId, network_fee: 0, service_fee: fee, confirmed: internal, tx_hash: txOutput.hash, internal: false }
|
||||
this.sendOperationToNostr(userAddress.linkedApplication, userAddress.user.user_id, op)
|
||||
} catch {
|
||||
log(ERROR, "cannot process address paid transaction, already registered")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
invoicePaidCb: InvoicePaidCb = (paymentRequest, amount, internal) => {
|
||||
this.storage.StartTransaction(async tx => {
|
||||
let log = getLogger({})
|
||||
const userInvoice = await this.storage.paymentStorage.GetInvoiceOwner(paymentRequest, tx)
|
||||
if (!userInvoice) { return }
|
||||
if (userInvoice.paid_at_unix > 0 && internal) { log("cannot pay internally, invoice already paid"); return }
|
||||
if (userInvoice.paid_at_unix > 0 && !internal && userInvoice.paidByLnd) { log("invoice already paid by lnd"); return }
|
||||
if (!userInvoice.linkedApplication) {
|
||||
log(ERROR, "an invoice was paid, that has no linked application")
|
||||
return
|
||||
}
|
||||
log = getLogger({ appName: userInvoice.linkedApplication.name })
|
||||
const isAppUserPayment = userInvoice.user.user_id !== userInvoice.linkedApplication.owner.user_id
|
||||
let fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_INVOICE, amount, isAppUserPayment)
|
||||
if (userInvoice.linkedApplication && userInvoice.linkedApplication.owner.user_id === userInvoice.user.user_id) {
|
||||
fee = 0
|
||||
}
|
||||
try {
|
||||
await this.storage.paymentStorage.FlagInvoiceAsPaid(userInvoice, amount, fee, internal, tx)
|
||||
this.storage.eventsLog.LogEvent({ type: 'invoice_paid', userId: userInvoice.user.user_id, appId: userInvoice.linkedApplication.app_id, appUserId: "", balance: userInvoice.user.balance_sats, data: paymentRequest, amount })
|
||||
await this.storage.userStorage.IncrementUserBalance(userInvoice.user.user_id, amount - fee, userInvoice.invoice, tx)
|
||||
if (fee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(userInvoice.linkedApplication.owner.user_id, fee, 'fees', tx)
|
||||
}
|
||||
await this.triggerPaidCallback(log, userInvoice.callbackUrl)
|
||||
const operationId = `${Types.UserOperationType.INCOMING_INVOICE}-${userInvoice.serial_id}`
|
||||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_INVOICE, identifier: userInvoice.invoice, operationId, network_fee: 0, service_fee: fee, confirmed: true, tx_hash: "", internal }
|
||||
this.sendOperationToNostr(userInvoice.linkedApplication, userInvoice.user.user_id, op)
|
||||
this.createZapReceipt(log, userInvoice)
|
||||
log("paid invoice processed successfully")
|
||||
} catch (err: any) {
|
||||
log(ERROR, "cannot process paid invoice", err.message || "")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async triggerPaidCallback(log: PubLogger, url: string) {
|
||||
if (!url) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await fetch(url + "&ok=true")
|
||||
} catch (err: any) {
|
||||
log(ERROR, "error sending paid callback for invoice", err.message || "")
|
||||
}
|
||||
}
|
||||
|
||||
async sendOperationToNostr(app: Application, userId: string, op: Types.UserOperation) {
|
||||
const user = await this.storage.applicationStorage.GetAppUserFromUser(app, userId)
|
||||
if (!user || !user.nostr_public_key) {
|
||||
getLogger({ appName: app.name })("cannot notify user, not a nostr user")
|
||||
return
|
||||
}
|
||||
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } = { operation: op, requestId: "GetLiveUserOperations", status: 'OK' }
|
||||
this.nostrSend({ type: 'app', appId: app.app_id }, { type: 'content', content: JSON.stringify(message), pub: user.nostr_public_key })
|
||||
}
|
||||
|
||||
async UpdateBeacon(app: Application, content: { type: 'service', name: string }) {
|
||||
if (!app.nostr_public_key) {
|
||||
getLogger({ appName: app.name })("cannot update beacon, public key not set")
|
||||
return
|
||||
}
|
||||
const tags = [["d", appTag]]
|
||||
const event: UnsignedEvent = {
|
||||
content: JSON.stringify(content),
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 30078,
|
||||
pubkey: app.nostr_public_key,
|
||||
tags,
|
||||
}
|
||||
this.nostrSend({ type: 'app', appId: app.app_id }, { type: 'event', event })
|
||||
}
|
||||
|
||||
async createZapReceipt(log: PubLogger, invoice: UserReceivingInvoice) {
|
||||
const zapInfo = invoice.zap_info
|
||||
if (!zapInfo || !invoice.linkedApplication || !invoice.linkedApplication.nostr_public_key) {
|
||||
log(ERROR, "no zap info linked to payment")
|
||||
return
|
||||
}
|
||||
const tags = [["p", zapInfo.pub], ["bolt11", invoice.invoice], ["description", zapInfo.description]]
|
||||
if (zapInfo.eventId) {
|
||||
tags.push(["e", zapInfo.eventId])
|
||||
}
|
||||
const event: UnsignedEvent = {
|
||||
content: "",
|
||||
created_at: invoice.paid_at_unix,
|
||||
kind: 9735,
|
||||
pubkey: invoice.linkedApplication.nostr_public_key,
|
||||
tags,
|
||||
}
|
||||
log({ unsigned: event })
|
||||
this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,73 +1,73 @@
|
|||
import { PubLogger, getLogger } from "../helpers/logger.js"
|
||||
import { LiquidityProvider } from "../lnd/liquidityProvider.js"
|
||||
import Storage from "../storage/index.js"
|
||||
import { TypeOrmMigrationRunner } from "../storage/migrations/runner.js"
|
||||
import Main from "./index.js"
|
||||
import SanityChecker from "./sanityChecker.js"
|
||||
import { MainSettings } from "./settings.js"
|
||||
export type AppData = {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
appId: string;
|
||||
name: string;
|
||||
}
|
||||
export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings) => {
|
||||
const storageManager = new Storage(mainSettings.storageSettings)
|
||||
const manualMigration = await TypeOrmMigrationRunner(log, storageManager, mainSettings.storageSettings.dbSettings, process.argv[2])
|
||||
if (manualMigration) {
|
||||
return
|
||||
}
|
||||
|
||||
const mainHandler = new Main(mainSettings, storageManager)
|
||||
await mainHandler.lnd.Warmup()
|
||||
if (!mainSettings.skipSanityCheck) {
|
||||
const sanityChecker = new SanityChecker(storageManager, mainHandler.lnd)
|
||||
await sanityChecker.VerifyEventsLog()
|
||||
}
|
||||
await mainHandler.paymentManager.watchDog.Start()
|
||||
const appsData = await mainHandler.storage.applicationStorage.GetApplications()
|
||||
const existingWalletApp = await appsData.find(app => app.name === 'wallet' || app.name === 'wallet-test')
|
||||
if (!existingWalletApp) {
|
||||
log("no default wallet app found, creating one...")
|
||||
const newWalletApp = await mainHandler.storage.applicationStorage.AddApplication('wallet', true)
|
||||
appsData.push(newWalletApp)
|
||||
}
|
||||
const apps: AppData[] = await Promise.all(appsData.map(app => {
|
||||
if (!app.nostr_private_key || !app.nostr_public_key) { // TMP --
|
||||
return mainHandler.storage.applicationStorage.GenerateApplicationKeys(app);
|
||||
} // --
|
||||
else {
|
||||
return { privateKey: app.nostr_private_key, publicKey: app.nostr_public_key, appId: app.app_id, name: app.name }
|
||||
}
|
||||
}))
|
||||
const liquidityProviderApp = apps.find(app => app.name === 'wallet' || app.name === 'wallet-test')
|
||||
if (!liquidityProviderApp) {
|
||||
throw new Error("wallet app not initialized correctly")
|
||||
}
|
||||
const liquidityProviderInfo = {
|
||||
privateKey: liquidityProviderApp.privateKey,
|
||||
publicKey: liquidityProviderApp.publicKey,
|
||||
name: "liquidity_provider", clientId: `client_${liquidityProviderApp.appId}`
|
||||
}
|
||||
mainHandler.liquidProvider.setNostrInfo({ clientId: liquidityProviderInfo.clientId, myPub: liquidityProviderInfo.publicKey })
|
||||
const stop = await processArgs(mainHandler)
|
||||
if (stop) {
|
||||
return
|
||||
}
|
||||
return { mainHandler, apps, liquidityProviderInfo, liquidityProviderApp }
|
||||
}
|
||||
|
||||
const processArgs = async (mainHandler: Main) => {
|
||||
switch (process.argv[2]) {
|
||||
case 'updateUserBalance':
|
||||
await mainHandler.storage.userStorage.UpdateUser(process.argv[3], { balance_sats: +process.argv[4] })
|
||||
getLogger({ userId: process.argv[3] })(`user balance updated correctly`)
|
||||
return false
|
||||
case 'unlock':
|
||||
await mainHandler.storage.userStorage.UnbanUser(process.argv[3])
|
||||
getLogger({ userId: process.argv[3] })(`user unlocked`)
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
import { PubLogger, getLogger } from "../helpers/logger.js"
|
||||
import { LiquidityProvider } from "../lnd/liquidityProvider.js"
|
||||
import Storage from "../storage/index.js"
|
||||
import { TypeOrmMigrationRunner } from "../storage/migrations/runner.js"
|
||||
import Main from "./index.js"
|
||||
import SanityChecker from "./sanityChecker.js"
|
||||
import { MainSettings } from "./settings.js"
|
||||
export type AppData = {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
appId: string;
|
||||
name: string;
|
||||
}
|
||||
export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings) => {
|
||||
const storageManager = new Storage(mainSettings.storageSettings)
|
||||
const manualMigration = await TypeOrmMigrationRunner(log, storageManager, mainSettings.storageSettings.dbSettings, process.argv[2])
|
||||
if (manualMigration) {
|
||||
return
|
||||
}
|
||||
|
||||
const mainHandler = new Main(mainSettings, storageManager)
|
||||
await mainHandler.lnd.Warmup()
|
||||
if (!mainSettings.skipSanityCheck) {
|
||||
const sanityChecker = new SanityChecker(storageManager, mainHandler.lnd)
|
||||
await sanityChecker.VerifyEventsLog()
|
||||
}
|
||||
await mainHandler.paymentManager.watchDog.Start()
|
||||
const appsData = await mainHandler.storage.applicationStorage.GetApplications()
|
||||
const existingWalletApp = await appsData.find(app => app.name === 'wallet' || app.name === 'wallet-test')
|
||||
if (!existingWalletApp) {
|
||||
log("no default wallet app found, creating one...")
|
||||
const newWalletApp = await mainHandler.storage.applicationStorage.AddApplication('wallet', true)
|
||||
appsData.push(newWalletApp)
|
||||
}
|
||||
const apps: AppData[] = await Promise.all(appsData.map(app => {
|
||||
if (!app.nostr_private_key || !app.nostr_public_key) { // TMP --
|
||||
return mainHandler.storage.applicationStorage.GenerateApplicationKeys(app);
|
||||
} // --
|
||||
else {
|
||||
return { privateKey: app.nostr_private_key, publicKey: app.nostr_public_key, appId: app.app_id, name: app.name }
|
||||
}
|
||||
}))
|
||||
const liquidityProviderApp = apps.find(app => app.name === 'wallet' || app.name === 'wallet-test')
|
||||
if (!liquidityProviderApp) {
|
||||
throw new Error("wallet app not initialized correctly")
|
||||
}
|
||||
const liquidityProviderInfo = {
|
||||
privateKey: liquidityProviderApp.privateKey,
|
||||
publicKey: liquidityProviderApp.publicKey,
|
||||
name: "liquidity_provider", clientId: `client_${liquidityProviderApp.appId}`
|
||||
}
|
||||
mainHandler.liquidProvider.setNostrInfo({ clientId: liquidityProviderInfo.clientId, myPub: liquidityProviderInfo.publicKey })
|
||||
const stop = await processArgs(mainHandler)
|
||||
if (stop) {
|
||||
return
|
||||
}
|
||||
return { mainHandler, apps, liquidityProviderInfo, liquidityProviderApp }
|
||||
}
|
||||
|
||||
const processArgs = async (mainHandler: Main) => {
|
||||
switch (process.argv[2]) {
|
||||
case 'updateUserBalance':
|
||||
await mainHandler.storage.userStorage.UpdateUser(process.argv[3], { balance_sats: +process.argv[4] })
|
||||
getLogger({ userId: process.argv[3] })(`user balance updated correctly`)
|
||||
return false
|
||||
case 'unlock':
|
||||
await mainHandler.storage.userStorage.UnbanUser(process.argv[3])
|
||||
getLogger({ userId: process.argv[3] })(`user unlocked`)
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,39 +1,39 @@
|
|||
import Storage from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
|
||||
|
||||
import { MainSettings } from './settings.js'
|
||||
import PaymentManager from './paymentManager.js'
|
||||
import { defaultInvoiceExpiry } from '../storage/paymentStorage.js'
|
||||
|
||||
export default class {
|
||||
storage: Storage
|
||||
settings: MainSettings
|
||||
paymentManager: PaymentManager
|
||||
|
||||
constructor(storage: Storage, paymentManager: PaymentManager, settings: MainSettings) {
|
||||
this.storage = storage
|
||||
this.settings = settings
|
||||
this.paymentManager = paymentManager
|
||||
}
|
||||
|
||||
async AddProduct(userId: string, req: Types.AddProductRequest): Promise<Types.Product> {
|
||||
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,
|
||||
name: newProduct.name,
|
||||
price_sats: newProduct.price_sats,
|
||||
}
|
||||
}
|
||||
|
||||
async NewProductInvoice(id: string): Promise<Types.NewInvoiceResponse> {
|
||||
const product = await this.storage.productStorage.GetProduct(id)
|
||||
const productInvoice = await this.paymentManager.NewInvoice(product.owner.user_id, {
|
||||
amountSats: product.price_sats, memo: product.name
|
||||
}, { product, expiry: defaultInvoiceExpiry })
|
||||
return {
|
||||
invoice: productInvoice.invoice
|
||||
}
|
||||
}
|
||||
import Storage from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
|
||||
|
||||
import { MainSettings } from './settings.js'
|
||||
import PaymentManager from './paymentManager.js'
|
||||
import { defaultInvoiceExpiry } from '../storage/paymentStorage.js'
|
||||
|
||||
export default class {
|
||||
storage: Storage
|
||||
settings: MainSettings
|
||||
paymentManager: PaymentManager
|
||||
|
||||
constructor(storage: Storage, paymentManager: PaymentManager, settings: MainSettings) {
|
||||
this.storage = storage
|
||||
this.settings = settings
|
||||
this.paymentManager = paymentManager
|
||||
}
|
||||
|
||||
async AddProduct(userId: string, req: Types.AddProductRequest): Promise<Types.Product> {
|
||||
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,
|
||||
name: newProduct.name,
|
||||
price_sats: newProduct.price_sats,
|
||||
}
|
||||
}
|
||||
|
||||
async NewProductInvoice(id: string): Promise<Types.NewInvoiceResponse> {
|
||||
const product = await this.storage.productStorage.GetProduct(id)
|
||||
const productInvoice = await this.paymentManager.NewInvoice(product.owner.user_id, {
|
||||
amountSats: product.price_sats, memo: product.name
|
||||
}, { product, expiry: defaultInvoiceExpiry })
|
||||
return {
|
||||
invoice: productInvoice.invoice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,268 +1,268 @@
|
|||
import Storage from '../storage/index.js'
|
||||
import LND from "../lnd/lnd.js"
|
||||
import { LoggedEvent } from '../storage/eventsLog.js'
|
||||
import { Invoice, Payment } from '../../../proto/lnd/lightning';
|
||||
import { getLogger } from '../helpers/logger.js';
|
||||
const LN_INVOICE_REGEX = /^(lightning:)?(lnbc|lntb)[0-9a-zA-Z]+$/;
|
||||
const BITCOIN_ADDRESS_REGEX = /^(bitcoin:)?([13][a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-zA-HJ-NP-Z0-9]{39,59})$/;
|
||||
type UniqueDecrementReasons = 'ban'
|
||||
type UniqueIncrementReasons = 'fees' | 'routing_fee_refund' | 'payment_refund'
|
||||
type CommonReasons = 'invoice' | 'address' | 'u2u'
|
||||
type Reason = UniqueDecrementReasons | UniqueIncrementReasons | CommonReasons
|
||||
const incrementTwiceAllowed = ['fees', 'ban']
|
||||
export default class SanityChecker {
|
||||
storage: Storage
|
||||
lnd: LND
|
||||
|
||||
events: LoggedEvent[] = []
|
||||
invoices: Invoice[] = []
|
||||
payments: Payment[] = []
|
||||
incrementSources: Record<string, boolean> = {}
|
||||
decrementSources: Record<string, boolean> = {}
|
||||
decrementEvents: Record<string, { userId: string, refund: number, failure: boolean }> = {}
|
||||
log = getLogger({ component: "SanityChecker" })
|
||||
users: Record<string, { ts: number, updatedBalance: number }> = {}
|
||||
constructor(storage: Storage, lnd: LND) {
|
||||
this.storage = storage
|
||||
this.lnd = lnd
|
||||
}
|
||||
|
||||
parseDataField(data: string): { type: Reason, data: string, txHash?: string, serialId?: number } {
|
||||
const parts = data.split(":")
|
||||
if (parts.length === 1) {
|
||||
const [fullData] = parts
|
||||
if (fullData === 'fees' || fullData === 'ban') {
|
||||
return { type: fullData, data: fullData }
|
||||
} else if (LN_INVOICE_REGEX.test(fullData)) {
|
||||
return { type: 'invoice', data: fullData }
|
||||
} else if (BITCOIN_ADDRESS_REGEX.test(fullData)) {
|
||||
return { type: 'address', data: fullData }
|
||||
} else {
|
||||
return { type: 'u2u', data: fullData }
|
||||
}
|
||||
} else if (parts.length === 2) {
|
||||
const [prefix, data] = parts
|
||||
if (prefix === 'routing_fee_refund' || prefix === 'payment_refund') {
|
||||
return { type: prefix, data }
|
||||
} else if (BITCOIN_ADDRESS_REGEX.test(prefix)) {
|
||||
return { type: 'address', data: prefix, txHash: data }
|
||||
} else {
|
||||
return { type: 'u2u', data: prefix, serialId: +data }
|
||||
}
|
||||
}
|
||||
throw new Error("unknown data format")
|
||||
}
|
||||
|
||||
async verifyDecrementEvent(e: LoggedEvent) {
|
||||
if (this.decrementSources[e.data]) {
|
||||
throw new Error("entry decremented more that once " + e.data)
|
||||
}
|
||||
this.decrementSources[e.data] = !incrementTwiceAllowed.includes(e.data)
|
||||
this.users[e.userId] = this.checkUserEntry(e, this.users[e.userId])
|
||||
const parsed = this.parseDataField(e.data)
|
||||
switch (parsed.type) {
|
||||
case 'ban':
|
||||
return
|
||||
case 'address':
|
||||
return this.validateUserTransactionPayment({ address: parsed.data, txHash: parsed.txHash, userId: e.userId })
|
||||
case 'invoice':
|
||||
return this.validateUserInvoicePayment({ invoice: parsed.data, userId: e.userId, amt: e.amount })
|
||||
case 'u2u':
|
||||
return this.validateUser2UserPayment({ fromUser: e.userId, toUser: parsed.data, serialId: parsed.serialId })
|
||||
default:
|
||||
throw new Error("unknown decrement type " + parsed.type)
|
||||
}
|
||||
}
|
||||
|
||||
async validateUserTransactionPayment({ address, txHash, userId }: { userId: string, address: string, txHash?: string }) {
|
||||
if (!txHash) {
|
||||
throw new Error("no tx hash provided to payment for address " + address)
|
||||
}
|
||||
const entry = await this.storage.paymentStorage.GetUserTransactionPaymentOwner(address, txHash)
|
||||
if (!entry) {
|
||||
throw new Error("no payment found for tx hash " + txHash)
|
||||
}
|
||||
if (entry.user.user_id !== userId) {
|
||||
throw new Error("payment user id mismatch for tx hash " + txHash)
|
||||
}
|
||||
if (entry.paid_at_unix <= 0) {
|
||||
throw new Error("payment not paid for tx hash " + txHash)
|
||||
}
|
||||
}
|
||||
|
||||
async validateUserInvoicePayment({ invoice, userId, amt }: { userId: string, invoice: string, amt: number }) {
|
||||
const entry = await this.storage.paymentStorage.GetPaymentOwner(invoice)
|
||||
if (!entry) {
|
||||
throw new Error("no payment found for invoice " + invoice)
|
||||
}
|
||||
if (entry.user.user_id !== userId) {
|
||||
throw new Error("payment user id mismatch for invoice " + invoice)
|
||||
}
|
||||
if (entry.paid_at_unix === 0) {
|
||||
throw new Error("payment never settled for invoice " + invoice) // TODO: check if this is correct
|
||||
}
|
||||
if (entry.paid_at_unix === -1) {
|
||||
this.decrementEvents[invoice] = { userId, refund: amt, failure: true }
|
||||
} else {
|
||||
const refund = amt - (entry.paid_amount + entry.routing_fees + entry.service_fees)
|
||||
this.decrementEvents[invoice] = { userId, refund, failure: false }
|
||||
}
|
||||
if (!entry.internal) {
|
||||
const lndEntry = this.payments.find(i => i.paymentRequest === invoice)
|
||||
if (!lndEntry) {
|
||||
throw new Error("payment not found in lnd for invoice " + invoice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async validateUser2UserPayment({ fromUser, toUser, serialId }: { fromUser: string, toUser: string, serialId?: number }) {
|
||||
if (!serialId) {
|
||||
throw new Error("no serial id provided to u2u payment")
|
||||
}
|
||||
const entry = await this.storage.paymentStorage.GetUser2UserPayment(serialId)
|
||||
if (!entry) {
|
||||
throw new Error("no payment u2u found for serial id " + serialId)
|
||||
}
|
||||
if (entry.from_user.user_id !== fromUser || entry.to_user.user_id !== toUser) {
|
||||
throw new Error("u2u payment user id mismatch for serial id " + serialId)
|
||||
}
|
||||
if (entry.paid_at_unix <= 0) {
|
||||
throw new Error("payment not paid for serial id " + serialId)
|
||||
}
|
||||
}
|
||||
|
||||
async verifyIncrementEvent(e: LoggedEvent) {
|
||||
if (this.incrementSources[e.data]) {
|
||||
throw new Error("entry incremented more that once " + e.data)
|
||||
}
|
||||
this.incrementSources[e.data] = !incrementTwiceAllowed.includes(e.data)
|
||||
this.users[e.userId] = this.checkUserEntry(e, this.users[e.userId])
|
||||
const parsed = this.parseDataField(e.data)
|
||||
switch (parsed.type) {
|
||||
case 'fees':
|
||||
return
|
||||
case 'address':
|
||||
return this.validateAddressReceivingTransaction({ address: parsed.data, txHash: parsed.txHash, userId: e.userId })
|
||||
case 'invoice':
|
||||
return this.validateReceivingInvoice({ invoice: parsed.data, userId: e.userId })
|
||||
case 'u2u':
|
||||
return this.validateUser2UserPayment({ fromUser: parsed.data, toUser: e.userId, serialId: parsed.serialId })
|
||||
case 'routing_fee_refund':
|
||||
return this.validateRoutingFeeRefund({ amt: e.amount, invoice: parsed.data, userId: e.userId })
|
||||
case 'payment_refund':
|
||||
return this.validatePaymentRefund({ amt: e.amount, invoice: parsed.data, userId: e.userId })
|
||||
default:
|
||||
throw new Error("unknown increment type " + parsed.type)
|
||||
}
|
||||
}
|
||||
|
||||
async validateAddressReceivingTransaction({ userId, address, txHash }: { userId: string, address: string, txHash?: string }) {
|
||||
if (!txHash) {
|
||||
throw new Error("no tx hash provided to address " + address)
|
||||
}
|
||||
const entry = await this.storage.paymentStorage.GetAddressReceivingTransactionOwner(address, txHash)
|
||||
if (!entry) {
|
||||
throw new Error("no tx found for tx hash " + txHash)
|
||||
}
|
||||
if (entry.user_address.user.user_id !== userId) {
|
||||
throw new Error("tx user id mismatch for tx hash " + txHash)
|
||||
}
|
||||
if (entry.paid_at_unix <= 0) {
|
||||
throw new Error("tx not paid for tx hash " + txHash)
|
||||
}
|
||||
}
|
||||
|
||||
async validateReceivingInvoice({ userId, invoice }: { userId: string, invoice: string }) {
|
||||
const entry = await this.storage.paymentStorage.GetInvoiceOwner(invoice)
|
||||
if (!entry) {
|
||||
throw new Error("no invoice found for invoice " + invoice)
|
||||
}
|
||||
if (entry.user.user_id !== userId) {
|
||||
throw new Error("invoice user id mismatch for invoice " + invoice)
|
||||
}
|
||||
if (entry.paid_at_unix <= 0) {
|
||||
throw new Error("invoice not paid for invoice " + invoice)
|
||||
}
|
||||
if (!entry.internal) {
|
||||
const entry = this.invoices.find(i => i.paymentRequest === invoice)
|
||||
if (!entry) {
|
||||
throw new Error("invoice not found in lnd " + invoice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async validateRoutingFeeRefund({ amt, invoice, userId }: { userId: string, invoice: string, amt: number }) {
|
||||
const entry = this.decrementEvents[invoice]
|
||||
if (!entry) {
|
||||
throw new Error("no decrement event found for invoice routing fee refound " + invoice)
|
||||
}
|
||||
if (entry.userId !== userId) {
|
||||
throw new Error("user id mismatch for routing fee refund " + invoice)
|
||||
}
|
||||
if (entry.failure) {
|
||||
throw new Error("payment failled, should not refund routing fees " + invoice)
|
||||
}
|
||||
if (entry.refund !== amt) {
|
||||
throw new Error("refund amount mismatch for routing fee refund " + invoice)
|
||||
}
|
||||
}
|
||||
|
||||
async validatePaymentRefund({ amt, invoice, userId }: { userId: string, invoice: string, amt: number }) {
|
||||
const entry = this.decrementEvents[invoice]
|
||||
if (!entry) {
|
||||
throw new Error("no decrement event found for invoice payment refund " + invoice)
|
||||
}
|
||||
if (entry.userId !== userId) {
|
||||
throw new Error("user id mismatch for payment refund " + invoice)
|
||||
}
|
||||
if (!entry.failure) {
|
||||
throw new Error("payment did not fail, should not refund payment " + invoice)
|
||||
}
|
||||
if (entry.refund !== amt) {
|
||||
throw new Error("refund amount mismatch for payment refund " + invoice)
|
||||
}
|
||||
}
|
||||
|
||||
async VerifyEventsLog() {
|
||||
this.events = await this.storage.eventsLog.GetAllLogs()
|
||||
this.invoices = (await this.lnd.GetAllPaidInvoices(1000)).invoices
|
||||
this.payments = (await this.lnd.GetAllPayments(1000)).payments
|
||||
this.incrementSources = {}
|
||||
this.decrementSources = {}
|
||||
this.users = {}
|
||||
this.users = {}
|
||||
this.decrementEvents = {}
|
||||
for (let i = 0; i < this.events.length; i++) {
|
||||
const e = this.events[i]
|
||||
if (e.type === 'balance_decrement') {
|
||||
await this.verifyDecrementEvent(e)
|
||||
} else if (e.type === 'balance_increment') {
|
||||
await this.verifyIncrementEvent(e)
|
||||
} else {
|
||||
await this.storage.paymentStorage.VerifyDbEvent(e)
|
||||
}
|
||||
}
|
||||
await Promise.all(Object.entries(this.users).map(async ([userId, u]) => {
|
||||
const user = await this.storage.userStorage.GetUser(userId)
|
||||
if (user.balance_sats !== u.updatedBalance) {
|
||||
throw new Error("sanity check on balance failed, expected: " + u.updatedBalance + " found: " + user.balance_sats)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
checkUserEntry(e: LoggedEvent, u: { ts: number, updatedBalance: number } | undefined) {
|
||||
const newEntry = { ts: e.timestampMs, updatedBalance: e.balance + e.amount * (e.type === 'balance_decrement' ? -1 : 1) }
|
||||
if (!u) {
|
||||
this.log(e.userId, "balance starts at", e.balance, "sats and moves by", e.amount * (e.type === 'balance_decrement' ? -1 : 1), "sats, resulting in", newEntry.updatedBalance, "sats")
|
||||
return newEntry
|
||||
}
|
||||
if (e.timestampMs < u.ts) {
|
||||
throw new Error("entry out of order " + e.timestampMs + " " + u.ts)
|
||||
}
|
||||
if (e.balance !== u.updatedBalance) {
|
||||
throw new Error("inconsistent balance update got: " + e.balance + " expected " + u.updatedBalance)
|
||||
}
|
||||
this.log(e.userId, "balance updates from", e.balance, "sats and moves by", e.amount * (e.type === 'balance_decrement' ? -1 : 1), "sats, resulting in", newEntry.updatedBalance, "sats")
|
||||
return newEntry
|
||||
}
|
||||
import Storage from '../storage/index.js'
|
||||
import LND from "../lnd/lnd.js"
|
||||
import { LoggedEvent } from '../storage/eventsLog.js'
|
||||
import { Invoice, Payment } from '../../../proto/lnd/lightning';
|
||||
import { getLogger } from '../helpers/logger.js';
|
||||
const LN_INVOICE_REGEX = /^(lightning:)?(lnbc|lntb)[0-9a-zA-Z]+$/;
|
||||
const BITCOIN_ADDRESS_REGEX = /^(bitcoin:)?([13][a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-zA-HJ-NP-Z0-9]{39,59})$/;
|
||||
type UniqueDecrementReasons = 'ban'
|
||||
type UniqueIncrementReasons = 'fees' | 'routing_fee_refund' | 'payment_refund'
|
||||
type CommonReasons = 'invoice' | 'address' | 'u2u'
|
||||
type Reason = UniqueDecrementReasons | UniqueIncrementReasons | CommonReasons
|
||||
const incrementTwiceAllowed = ['fees', 'ban']
|
||||
export default class SanityChecker {
|
||||
storage: Storage
|
||||
lnd: LND
|
||||
|
||||
events: LoggedEvent[] = []
|
||||
invoices: Invoice[] = []
|
||||
payments: Payment[] = []
|
||||
incrementSources: Record<string, boolean> = {}
|
||||
decrementSources: Record<string, boolean> = {}
|
||||
decrementEvents: Record<string, { userId: string, refund: number, failure: boolean }> = {}
|
||||
log = getLogger({ component: "SanityChecker" })
|
||||
users: Record<string, { ts: number, updatedBalance: number }> = {}
|
||||
constructor(storage: Storage, lnd: LND) {
|
||||
this.storage = storage
|
||||
this.lnd = lnd
|
||||
}
|
||||
|
||||
parseDataField(data: string): { type: Reason, data: string, txHash?: string, serialId?: number } {
|
||||
const parts = data.split(":")
|
||||
if (parts.length === 1) {
|
||||
const [fullData] = parts
|
||||
if (fullData === 'fees' || fullData === 'ban') {
|
||||
return { type: fullData, data: fullData }
|
||||
} else if (LN_INVOICE_REGEX.test(fullData)) {
|
||||
return { type: 'invoice', data: fullData }
|
||||
} else if (BITCOIN_ADDRESS_REGEX.test(fullData)) {
|
||||
return { type: 'address', data: fullData }
|
||||
} else {
|
||||
return { type: 'u2u', data: fullData }
|
||||
}
|
||||
} else if (parts.length === 2) {
|
||||
const [prefix, data] = parts
|
||||
if (prefix === 'routing_fee_refund' || prefix === 'payment_refund') {
|
||||
return { type: prefix, data }
|
||||
} else if (BITCOIN_ADDRESS_REGEX.test(prefix)) {
|
||||
return { type: 'address', data: prefix, txHash: data }
|
||||
} else {
|
||||
return { type: 'u2u', data: prefix, serialId: +data }
|
||||
}
|
||||
}
|
||||
throw new Error("unknown data format")
|
||||
}
|
||||
|
||||
async verifyDecrementEvent(e: LoggedEvent) {
|
||||
if (this.decrementSources[e.data]) {
|
||||
throw new Error("entry decremented more that once " + e.data)
|
||||
}
|
||||
this.decrementSources[e.data] = !incrementTwiceAllowed.includes(e.data)
|
||||
this.users[e.userId] = this.checkUserEntry(e, this.users[e.userId])
|
||||
const parsed = this.parseDataField(e.data)
|
||||
switch (parsed.type) {
|
||||
case 'ban':
|
||||
return
|
||||
case 'address':
|
||||
return this.validateUserTransactionPayment({ address: parsed.data, txHash: parsed.txHash, userId: e.userId })
|
||||
case 'invoice':
|
||||
return this.validateUserInvoicePayment({ invoice: parsed.data, userId: e.userId, amt: e.amount })
|
||||
case 'u2u':
|
||||
return this.validateUser2UserPayment({ fromUser: e.userId, toUser: parsed.data, serialId: parsed.serialId })
|
||||
default:
|
||||
throw new Error("unknown decrement type " + parsed.type)
|
||||
}
|
||||
}
|
||||
|
||||
async validateUserTransactionPayment({ address, txHash, userId }: { userId: string, address: string, txHash?: string }) {
|
||||
if (!txHash) {
|
||||
throw new Error("no tx hash provided to payment for address " + address)
|
||||
}
|
||||
const entry = await this.storage.paymentStorage.GetUserTransactionPaymentOwner(address, txHash)
|
||||
if (!entry) {
|
||||
throw new Error("no payment found for tx hash " + txHash)
|
||||
}
|
||||
if (entry.user.user_id !== userId) {
|
||||
throw new Error("payment user id mismatch for tx hash " + txHash)
|
||||
}
|
||||
if (entry.paid_at_unix <= 0) {
|
||||
throw new Error("payment not paid for tx hash " + txHash)
|
||||
}
|
||||
}
|
||||
|
||||
async validateUserInvoicePayment({ invoice, userId, amt }: { userId: string, invoice: string, amt: number }) {
|
||||
const entry = await this.storage.paymentStorage.GetPaymentOwner(invoice)
|
||||
if (!entry) {
|
||||
throw new Error("no payment found for invoice " + invoice)
|
||||
}
|
||||
if (entry.user.user_id !== userId) {
|
||||
throw new Error("payment user id mismatch for invoice " + invoice)
|
||||
}
|
||||
if (entry.paid_at_unix === 0) {
|
||||
throw new Error("payment never settled for invoice " + invoice) // TODO: check if this is correct
|
||||
}
|
||||
if (entry.paid_at_unix === -1) {
|
||||
this.decrementEvents[invoice] = { userId, refund: amt, failure: true }
|
||||
} else {
|
||||
const refund = amt - (entry.paid_amount + entry.routing_fees + entry.service_fees)
|
||||
this.decrementEvents[invoice] = { userId, refund, failure: false }
|
||||
}
|
||||
if (!entry.internal) {
|
||||
const lndEntry = this.payments.find(i => i.paymentRequest === invoice)
|
||||
if (!lndEntry) {
|
||||
throw new Error("payment not found in lnd for invoice " + invoice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async validateUser2UserPayment({ fromUser, toUser, serialId }: { fromUser: string, toUser: string, serialId?: number }) {
|
||||
if (!serialId) {
|
||||
throw new Error("no serial id provided to u2u payment")
|
||||
}
|
||||
const entry = await this.storage.paymentStorage.GetUser2UserPayment(serialId)
|
||||
if (!entry) {
|
||||
throw new Error("no payment u2u found for serial id " + serialId)
|
||||
}
|
||||
if (entry.from_user.user_id !== fromUser || entry.to_user.user_id !== toUser) {
|
||||
throw new Error("u2u payment user id mismatch for serial id " + serialId)
|
||||
}
|
||||
if (entry.paid_at_unix <= 0) {
|
||||
throw new Error("payment not paid for serial id " + serialId)
|
||||
}
|
||||
}
|
||||
|
||||
async verifyIncrementEvent(e: LoggedEvent) {
|
||||
if (this.incrementSources[e.data]) {
|
||||
throw new Error("entry incremented more that once " + e.data)
|
||||
}
|
||||
this.incrementSources[e.data] = !incrementTwiceAllowed.includes(e.data)
|
||||
this.users[e.userId] = this.checkUserEntry(e, this.users[e.userId])
|
||||
const parsed = this.parseDataField(e.data)
|
||||
switch (parsed.type) {
|
||||
case 'fees':
|
||||
return
|
||||
case 'address':
|
||||
return this.validateAddressReceivingTransaction({ address: parsed.data, txHash: parsed.txHash, userId: e.userId })
|
||||
case 'invoice':
|
||||
return this.validateReceivingInvoice({ invoice: parsed.data, userId: e.userId })
|
||||
case 'u2u':
|
||||
return this.validateUser2UserPayment({ fromUser: parsed.data, toUser: e.userId, serialId: parsed.serialId })
|
||||
case 'routing_fee_refund':
|
||||
return this.validateRoutingFeeRefund({ amt: e.amount, invoice: parsed.data, userId: e.userId })
|
||||
case 'payment_refund':
|
||||
return this.validatePaymentRefund({ amt: e.amount, invoice: parsed.data, userId: e.userId })
|
||||
default:
|
||||
throw new Error("unknown increment type " + parsed.type)
|
||||
}
|
||||
}
|
||||
|
||||
async validateAddressReceivingTransaction({ userId, address, txHash }: { userId: string, address: string, txHash?: string }) {
|
||||
if (!txHash) {
|
||||
throw new Error("no tx hash provided to address " + address)
|
||||
}
|
||||
const entry = await this.storage.paymentStorage.GetAddressReceivingTransactionOwner(address, txHash)
|
||||
if (!entry) {
|
||||
throw new Error("no tx found for tx hash " + txHash)
|
||||
}
|
||||
if (entry.user_address.user.user_id !== userId) {
|
||||
throw new Error("tx user id mismatch for tx hash " + txHash)
|
||||
}
|
||||
if (entry.paid_at_unix <= 0) {
|
||||
throw new Error("tx not paid for tx hash " + txHash)
|
||||
}
|
||||
}
|
||||
|
||||
async validateReceivingInvoice({ userId, invoice }: { userId: string, invoice: string }) {
|
||||
const entry = await this.storage.paymentStorage.GetInvoiceOwner(invoice)
|
||||
if (!entry) {
|
||||
throw new Error("no invoice found for invoice " + invoice)
|
||||
}
|
||||
if (entry.user.user_id !== userId) {
|
||||
throw new Error("invoice user id mismatch for invoice " + invoice)
|
||||
}
|
||||
if (entry.paid_at_unix <= 0) {
|
||||
throw new Error("invoice not paid for invoice " + invoice)
|
||||
}
|
||||
if (!entry.internal) {
|
||||
const entry = this.invoices.find(i => i.paymentRequest === invoice)
|
||||
if (!entry) {
|
||||
throw new Error("invoice not found in lnd " + invoice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async validateRoutingFeeRefund({ amt, invoice, userId }: { userId: string, invoice: string, amt: number }) {
|
||||
const entry = this.decrementEvents[invoice]
|
||||
if (!entry) {
|
||||
throw new Error("no decrement event found for invoice routing fee refound " + invoice)
|
||||
}
|
||||
if (entry.userId !== userId) {
|
||||
throw new Error("user id mismatch for routing fee refund " + invoice)
|
||||
}
|
||||
if (entry.failure) {
|
||||
throw new Error("payment failled, should not refund routing fees " + invoice)
|
||||
}
|
||||
if (entry.refund !== amt) {
|
||||
throw new Error("refund amount mismatch for routing fee refund " + invoice)
|
||||
}
|
||||
}
|
||||
|
||||
async validatePaymentRefund({ amt, invoice, userId }: { userId: string, invoice: string, amt: number }) {
|
||||
const entry = this.decrementEvents[invoice]
|
||||
if (!entry) {
|
||||
throw new Error("no decrement event found for invoice payment refund " + invoice)
|
||||
}
|
||||
if (entry.userId !== userId) {
|
||||
throw new Error("user id mismatch for payment refund " + invoice)
|
||||
}
|
||||
if (!entry.failure) {
|
||||
throw new Error("payment did not fail, should not refund payment " + invoice)
|
||||
}
|
||||
if (entry.refund !== amt) {
|
||||
throw new Error("refund amount mismatch for payment refund " + invoice)
|
||||
}
|
||||
}
|
||||
|
||||
async VerifyEventsLog() {
|
||||
this.events = await this.storage.eventsLog.GetAllLogs()
|
||||
this.invoices = (await this.lnd.GetAllPaidInvoices(1000)).invoices
|
||||
this.payments = (await this.lnd.GetAllPayments(1000)).payments
|
||||
this.incrementSources = {}
|
||||
this.decrementSources = {}
|
||||
this.users = {}
|
||||
this.users = {}
|
||||
this.decrementEvents = {}
|
||||
for (let i = 0; i < this.events.length; i++) {
|
||||
const e = this.events[i]
|
||||
if (e.type === 'balance_decrement') {
|
||||
await this.verifyDecrementEvent(e)
|
||||
} else if (e.type === 'balance_increment') {
|
||||
await this.verifyIncrementEvent(e)
|
||||
} else {
|
||||
await this.storage.paymentStorage.VerifyDbEvent(e)
|
||||
}
|
||||
}
|
||||
await Promise.all(Object.entries(this.users).map(async ([userId, u]) => {
|
||||
const user = await this.storage.userStorage.GetUser(userId)
|
||||
if (user.balance_sats !== u.updatedBalance) {
|
||||
throw new Error("sanity check on balance failed, expected: " + u.updatedBalance + " found: " + user.balance_sats)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
checkUserEntry(e: LoggedEvent, u: { ts: number, updatedBalance: number } | undefined) {
|
||||
const newEntry = { ts: e.timestampMs, updatedBalance: e.balance + e.amount * (e.type === 'balance_decrement' ? -1 : 1) }
|
||||
if (!u) {
|
||||
this.log(e.userId, "balance starts at", e.balance, "sats and moves by", e.amount * (e.type === 'balance_decrement' ? -1 : 1), "sats, resulting in", newEntry.updatedBalance, "sats")
|
||||
return newEntry
|
||||
}
|
||||
if (e.timestampMs < u.ts) {
|
||||
throw new Error("entry out of order " + e.timestampMs + " " + u.ts)
|
||||
}
|
||||
if (e.balance !== u.updatedBalance) {
|
||||
throw new Error("inconsistent balance update got: " + e.balance + " expected " + u.updatedBalance)
|
||||
}
|
||||
this.log(e.userId, "balance updates from", e.balance, "sats and moves by", e.amount * (e.type === 'balance_decrement' ? -1 : 1), "sats, resulting in", newEntry.updatedBalance, "sats")
|
||||
return newEntry
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +1,106 @@
|
|||
import { LoadStorageSettingsFromEnv, StorageSettings } from '../storage/index.js'
|
||||
import { LndSettings, NodeSettings } from '../lnd/settings.js'
|
||||
import { LoadWatchdogSettingsFromEnv, WatchdogSettings } from './watchdog.js'
|
||||
import { LoadLndSettingsFromEnv } from '../lnd/index.js'
|
||||
import { EnvCanBeInteger, EnvMustBeInteger, EnvMustBeNonEmptyString } from '../helpers/envParser.js'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
import fs from 'fs'
|
||||
import crypto from 'crypto';
|
||||
export type MainSettings = {
|
||||
storageSettings: StorageSettings,
|
||||
lndSettings: LndSettings,
|
||||
watchDogSettings: WatchdogSettings,
|
||||
jwtSecret: string
|
||||
incomingTxFee: number
|
||||
outgoingTxFee: number
|
||||
incomingAppInvoiceFee: number
|
||||
incomingAppUserInvoiceFee: number
|
||||
outgoingAppInvoiceFee: number
|
||||
outgoingAppUserInvoiceFee: number
|
||||
userToUserFee: number
|
||||
appToUserFee: number
|
||||
serviceUrl: string
|
||||
servicePort: number
|
||||
recordPerformance: boolean
|
||||
skipSanityCheck: boolean
|
||||
disableExternalPayments: boolean
|
||||
}
|
||||
export type BitcoinCoreSettings = {
|
||||
port: number
|
||||
user: string
|
||||
pass: string
|
||||
}
|
||||
export type TestSettings = MainSettings & { lndSettings: { otherNode: NodeSettings, thirdNode: NodeSettings, fourthNode: NodeSettings }, bitcoinCoreSettings: BitcoinCoreSettings }
|
||||
export const LoadMainSettingsFromEnv = (): MainSettings => {
|
||||
return {
|
||||
watchDogSettings: LoadWatchdogSettingsFromEnv(),
|
||||
lndSettings: LoadLndSettingsFromEnv(),
|
||||
storageSettings: LoadStorageSettingsFromEnv(),
|
||||
jwtSecret: loadJwtSecret(),
|
||||
incomingTxFee: EnvCanBeInteger("INCOMING_CHAIN_FEE_ROOT_BPS", 0) / 10000,
|
||||
outgoingTxFee: EnvCanBeInteger("OUTGOING_CHAIN_FEE_ROOT_BPS", 60) / 10000,
|
||||
incomingAppInvoiceFee: EnvCanBeInteger("INCOMING_INVOICE_FEE_ROOT_BPS", 0) / 10000,
|
||||
outgoingAppInvoiceFee: EnvCanBeInteger("OUTGOING_INVOICE_FEE_ROOT_BPS", 60) / 10000,
|
||||
incomingAppUserInvoiceFee: EnvCanBeInteger("INCOMING_INVOICE_FEE_USER_BPS", 0) / 10000,
|
||||
outgoingAppUserInvoiceFee: EnvCanBeInteger("OUTGOING_INVOICE_FEE_USER_BPS", 0) / 10000,
|
||||
userToUserFee: EnvCanBeInteger("TX_FEE_INTERNAL_USER_BPS", 0) / 10000,
|
||||
appToUserFee: EnvCanBeInteger("TX_FEE_INTERNAL_ROOT_BPS", 0) / 10000,
|
||||
serviceUrl: process.env.SERVICE_URL || `http://localhost:${EnvCanBeInteger("PORT", 1776)}`,
|
||||
servicePort: EnvCanBeInteger("PORT", 1776),
|
||||
recordPerformance: process.env.RECORD_PERFORMANCE === 'true' || false,
|
||||
skipSanityCheck: process.env.SKIP_SANITY_CHECK === 'true' || false,
|
||||
disableExternalPayments: process.env.DISABLE_EXTERNAL_PAYMENTS === 'true' || false,
|
||||
}
|
||||
}
|
||||
|
||||
export const LoadTestSettingsFromEnv = (): TestSettings => {
|
||||
const eventLogPath = `logs/eventLogV2Test${Date.now()}.csv`
|
||||
const settings = LoadMainSettingsFromEnv()
|
||||
return {
|
||||
...settings,
|
||||
storageSettings: { dbSettings: { ...settings.storageSettings.dbSettings, databaseFile: ":memory:", metricsDatabaseFile: ":memory:" }, eventLogPath },
|
||||
lndSettings: {
|
||||
...settings.lndSettings,
|
||||
otherNode: {
|
||||
lndAddr: EnvMustBeNonEmptyString("LND_OTHER_ADDR"),
|
||||
lndCertPath: EnvMustBeNonEmptyString("LND_OTHER_CERT_PATH"),
|
||||
lndMacaroonPath: EnvMustBeNonEmptyString("LND_OTHER_MACAROON_PATH")
|
||||
},
|
||||
thirdNode: {
|
||||
lndAddr: EnvMustBeNonEmptyString("LND_THIRD_ADDR"),
|
||||
lndCertPath: EnvMustBeNonEmptyString("LND_THIRD_CERT_PATH"),
|
||||
lndMacaroonPath: EnvMustBeNonEmptyString("LND_THIRD_MACAROON_PATH")
|
||||
},
|
||||
fourthNode: {
|
||||
lndAddr: EnvMustBeNonEmptyString("LND_FOURTH_ADDR"),
|
||||
lndCertPath: EnvMustBeNonEmptyString("LND_FOURTH_CERT_PATH"),
|
||||
lndMacaroonPath: EnvMustBeNonEmptyString("LND_FOURTH_MACAROON_PATH")
|
||||
},
|
||||
liquidityProviderPub: ""
|
||||
},
|
||||
skipSanityCheck: true,
|
||||
bitcoinCoreSettings: {
|
||||
port: EnvMustBeInteger("BITCOIN_CORE_PORT"),
|
||||
user: EnvMustBeNonEmptyString("BITCOIN_CORE_USER"),
|
||||
pass: EnvMustBeNonEmptyString("BITCOIN_CORE_PASS")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const loadJwtSecret = (): string => {
|
||||
const secret = process.env["JWT_SECRET"]
|
||||
const log = getLogger({})
|
||||
if (secret) {
|
||||
return secret
|
||||
}
|
||||
log("JWT_SECRET not set in env, checking .jwt_secret file")
|
||||
try {
|
||||
const fileContent = fs.readFileSync(".jwt_secret", "utf-8")
|
||||
return fileContent.trim()
|
||||
} catch (e) {
|
||||
log(".jwt_secret file not found, generating random secret")
|
||||
const secret = crypto.randomBytes(32).toString('hex')
|
||||
fs.writeFileSync(".jwt_secret", secret)
|
||||
return secret
|
||||
}
|
||||
import { LoadStorageSettingsFromEnv, StorageSettings } from '../storage/index.js'
|
||||
import { LndSettings, NodeSettings } from '../lnd/settings.js'
|
||||
import { LoadWatchdogSettingsFromEnv, WatchdogSettings } from './watchdog.js'
|
||||
import { LoadLndSettingsFromEnv } from '../lnd/index.js'
|
||||
import { EnvCanBeInteger, EnvMustBeInteger, EnvMustBeNonEmptyString } from '../helpers/envParser.js'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
import fs from 'fs'
|
||||
import crypto from 'crypto';
|
||||
export type MainSettings = {
|
||||
storageSettings: StorageSettings,
|
||||
lndSettings: LndSettings,
|
||||
watchDogSettings: WatchdogSettings,
|
||||
jwtSecret: string
|
||||
incomingTxFee: number
|
||||
outgoingTxFee: number
|
||||
incomingAppInvoiceFee: number
|
||||
incomingAppUserInvoiceFee: number
|
||||
outgoingAppInvoiceFee: number
|
||||
outgoingAppUserInvoiceFee: number
|
||||
userToUserFee: number
|
||||
appToUserFee: number
|
||||
serviceUrl: string
|
||||
servicePort: number
|
||||
recordPerformance: boolean
|
||||
skipSanityCheck: boolean
|
||||
disableExternalPayments: boolean
|
||||
}
|
||||
export type BitcoinCoreSettings = {
|
||||
port: number
|
||||
user: string
|
||||
pass: string
|
||||
}
|
||||
export type TestSettings = MainSettings & { lndSettings: { otherNode: NodeSettings, thirdNode: NodeSettings, fourthNode: NodeSettings }, bitcoinCoreSettings: BitcoinCoreSettings }
|
||||
export const LoadMainSettingsFromEnv = (): MainSettings => {
|
||||
return {
|
||||
watchDogSettings: LoadWatchdogSettingsFromEnv(),
|
||||
lndSettings: LoadLndSettingsFromEnv(),
|
||||
storageSettings: LoadStorageSettingsFromEnv(),
|
||||
jwtSecret: loadJwtSecret(),
|
||||
incomingTxFee: EnvCanBeInteger("INCOMING_CHAIN_FEE_ROOT_BPS", 0) / 10000,
|
||||
outgoingTxFee: EnvCanBeInteger("OUTGOING_CHAIN_FEE_ROOT_BPS", 60) / 10000,
|
||||
incomingAppInvoiceFee: EnvCanBeInteger("INCOMING_INVOICE_FEE_ROOT_BPS", 0) / 10000,
|
||||
outgoingAppInvoiceFee: EnvCanBeInteger("OUTGOING_INVOICE_FEE_ROOT_BPS", 60) / 10000,
|
||||
incomingAppUserInvoiceFee: EnvCanBeInteger("INCOMING_INVOICE_FEE_USER_BPS", 0) / 10000,
|
||||
outgoingAppUserInvoiceFee: EnvCanBeInteger("OUTGOING_INVOICE_FEE_USER_BPS", 0) / 10000,
|
||||
userToUserFee: EnvCanBeInteger("TX_FEE_INTERNAL_USER_BPS", 0) / 10000,
|
||||
appToUserFee: EnvCanBeInteger("TX_FEE_INTERNAL_ROOT_BPS", 0) / 10000,
|
||||
serviceUrl: process.env.SERVICE_URL || `http://localhost:${EnvCanBeInteger("PORT", 1776)}`,
|
||||
servicePort: EnvCanBeInteger("PORT", 1776),
|
||||
recordPerformance: process.env.RECORD_PERFORMANCE === 'true' || false,
|
||||
skipSanityCheck: process.env.SKIP_SANITY_CHECK === 'true' || false,
|
||||
disableExternalPayments: process.env.DISABLE_EXTERNAL_PAYMENTS === 'true' || false,
|
||||
}
|
||||
}
|
||||
|
||||
export const LoadTestSettingsFromEnv = (): TestSettings => {
|
||||
const eventLogPath = `logs/eventLogV2Test${Date.now()}.csv`
|
||||
const settings = LoadMainSettingsFromEnv()
|
||||
return {
|
||||
...settings,
|
||||
storageSettings: { dbSettings: { ...settings.storageSettings.dbSettings, databaseFile: ":memory:", metricsDatabaseFile: ":memory:" }, eventLogPath },
|
||||
lndSettings: {
|
||||
...settings.lndSettings,
|
||||
otherNode: {
|
||||
lndAddr: EnvMustBeNonEmptyString("LND_OTHER_ADDR"),
|
||||
lndCertPath: EnvMustBeNonEmptyString("LND_OTHER_CERT_PATH"),
|
||||
lndMacaroonPath: EnvMustBeNonEmptyString("LND_OTHER_MACAROON_PATH")
|
||||
},
|
||||
thirdNode: {
|
||||
lndAddr: EnvMustBeNonEmptyString("LND_THIRD_ADDR"),
|
||||
lndCertPath: EnvMustBeNonEmptyString("LND_THIRD_CERT_PATH"),
|
||||
lndMacaroonPath: EnvMustBeNonEmptyString("LND_THIRD_MACAROON_PATH")
|
||||
},
|
||||
fourthNode: {
|
||||
lndAddr: EnvMustBeNonEmptyString("LND_FOURTH_ADDR"),
|
||||
lndCertPath: EnvMustBeNonEmptyString("LND_FOURTH_CERT_PATH"),
|
||||
lndMacaroonPath: EnvMustBeNonEmptyString("LND_FOURTH_MACAROON_PATH")
|
||||
},
|
||||
liquidityProviderPub: ""
|
||||
},
|
||||
skipSanityCheck: true,
|
||||
bitcoinCoreSettings: {
|
||||
port: EnvMustBeInteger("BITCOIN_CORE_PORT"),
|
||||
user: EnvMustBeNonEmptyString("BITCOIN_CORE_USER"),
|
||||
pass: EnvMustBeNonEmptyString("BITCOIN_CORE_PASS")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const loadJwtSecret = (): string => {
|
||||
const secret = process.env["JWT_SECRET"]
|
||||
const log = getLogger({})
|
||||
if (secret) {
|
||||
return secret
|
||||
}
|
||||
log("JWT_SECRET not set in env, checking .jwt_secret file")
|
||||
try {
|
||||
const fileContent = fs.readFileSync(".jwt_secret", "utf-8")
|
||||
return fileContent.trim()
|
||||
} catch (e) {
|
||||
log(".jwt_secret file not found, generating random secret")
|
||||
const secret = crypto.randomBytes(32).toString('hex')
|
||||
fs.writeFileSync(".jwt_secret", secret)
|
||||
return secret
|
||||
}
|
||||
}
|
||||
|
|
@ -1,179 +1,179 @@
|
|||
import { EnvCanBeInteger } from "../helpers/envParser.js";
|
||||
import FunctionQueue from "../helpers/functionQueue.js";
|
||||
import { getLogger } from "../helpers/logger.js";
|
||||
import LND from "../lnd/lnd.js";
|
||||
import { ChannelBalance } from "../lnd/settings.js";
|
||||
import Storage from '../storage/index.js'
|
||||
export type WatchdogSettings = {
|
||||
maxDiffSats: number
|
||||
}
|
||||
export const LoadWatchdogSettingsFromEnv = (test = false): WatchdogSettings => {
|
||||
return {
|
||||
maxDiffSats: EnvCanBeInteger("WATCHDOG_MAX_DIFF_SATS")
|
||||
}
|
||||
}
|
||||
export class Watchdog {
|
||||
queue: FunctionQueue<void>
|
||||
initialLndBalance: number;
|
||||
initialUsersBalance: number;
|
||||
startedAtUnix: number;
|
||||
latestIndexOffset: number;
|
||||
accumulatedHtlcFees: number;
|
||||
lnd: LND;
|
||||
settings: WatchdogSettings;
|
||||
storage: Storage;
|
||||
latestCheckStart = 0
|
||||
log = getLogger({ component: "watchdog" })
|
||||
ready = false
|
||||
interval: NodeJS.Timer;
|
||||
constructor(settings: WatchdogSettings, lnd: LND, storage: Storage) {
|
||||
this.lnd = lnd;
|
||||
this.settings = settings;
|
||||
this.storage = storage;
|
||||
this.queue = new FunctionQueue("watchdog_queue", () => this.StartCheck())
|
||||
}
|
||||
|
||||
Stop() {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
}
|
||||
|
||||
Start = async () => {
|
||||
this.startedAtUnix = Math.floor(Date.now() / 1000)
|
||||
const totalUsersBalance = await this.storage.paymentStorage.GetTotalUsersBalance()
|
||||
this.initialLndBalance = await this.getTotalLndBalance(totalUsersBalance)
|
||||
this.initialUsersBalance = totalUsersBalance
|
||||
const fwEvents = await this.lnd.GetForwardingHistory(0, this.startedAtUnix)
|
||||
this.latestIndexOffset = fwEvents.lastOffsetIndex
|
||||
this.accumulatedHtlcFees = 0
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
if (this.latestCheckStart + (1000 * 60) < Date.now()) {
|
||||
this.log("No balance check was made in the last minute, checking now")
|
||||
this.PaymentRequested()
|
||||
}
|
||||
}, 1000 * 60)
|
||||
|
||||
this.ready = true
|
||||
}
|
||||
|
||||
updateAccumulatedHtlcFees = async () => {
|
||||
const fwEvents = await this.lnd.GetForwardingHistory(this.latestIndexOffset, this.startedAtUnix)
|
||||
this.latestIndexOffset = fwEvents.lastOffsetIndex
|
||||
fwEvents.forwardingEvents.forEach((event) => {
|
||||
this.accumulatedHtlcFees += Number(event.fee)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
getTotalLndBalance = async (usersTotal: number) => {
|
||||
const walletBalance = await this.lnd.GetWalletBalance()
|
||||
this.log(Number(walletBalance.confirmedBalance), "sats in chain wallet")
|
||||
const channelsBalance = await this.lnd.GetChannelBalance()
|
||||
getLogger({ component: "debugLndBalancev3" })({ w: walletBalance, c: channelsBalance, u: usersTotal, f: this.accumulatedHtlcFees })
|
||||
const totalLightningBalanceMsats = (channelsBalance.localBalance?.msat || 0n) + (channelsBalance.unsettledLocalBalance?.msat || 0n)
|
||||
const totalLightningBalance = Math.ceil(Number(totalLightningBalanceMsats) / 1000)
|
||||
return Number(walletBalance.confirmedBalance) + totalLightningBalance
|
||||
}
|
||||
|
||||
checkBalanceUpdate = (deltaLnd: number, deltaUsers: number) => {
|
||||
this.log("LND balance update:", deltaLnd, "sats since app startup")
|
||||
this.log("Users balance update:", deltaUsers, "sats since app startup")
|
||||
|
||||
const result = this.checkDeltas(deltaLnd, deltaUsers)
|
||||
switch (result.type) {
|
||||
case 'mismatch':
|
||||
if (deltaLnd < 0) {
|
||||
this.log("WARNING! LND balance decreased while users balance increased creating a difference of", result.absoluteDiff, "sats")
|
||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||
this.log("Difference is too big for an update, locking outgoing operations")
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
this.log("LND balance increased while users balance decreased creating a difference of", result.absoluteDiff, "sats, could be caused by data loss, or liquidity injection")
|
||||
return false
|
||||
}
|
||||
break
|
||||
case 'negative':
|
||||
if (Math.abs(deltaLnd) > Math.abs(deltaUsers)) {
|
||||
this.log("WARNING! LND balance decreased more than users balance with a difference of", result.absoluteDiff, "sats")
|
||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||
this.log("Difference is too big for an update, locking outgoing operations")
|
||||
return true
|
||||
}
|
||||
} else if (deltaLnd === deltaUsers) {
|
||||
this.log("LND and users balance went both DOWN consistently")
|
||||
return false
|
||||
} else {
|
||||
this.log("LND balance decreased less than users balance with a difference of", result.absoluteDiff, "sats, could be caused by data loss, or liquidity injection")
|
||||
return false
|
||||
}
|
||||
break
|
||||
case 'positive':
|
||||
if (deltaLnd < deltaUsers) {
|
||||
this.log("WARNING! LND balance increased less than users balance with a difference of", result.absoluteDiff, "sats")
|
||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||
this.log("Difference is too big for an update, locking outgoing operations")
|
||||
return true
|
||||
}
|
||||
} else if (deltaLnd === deltaUsers) {
|
||||
this.log("LND and users balance went both UP consistently")
|
||||
return false
|
||||
} else {
|
||||
this.log("LND balance increased more than users balance with a difference of", result.absoluteDiff, "sats, could be caused by data loss, or liquidity injection")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
StartCheck = async () => {
|
||||
this.latestCheckStart = Date.now()
|
||||
await this.updateAccumulatedHtlcFees()
|
||||
const totalUsersBalance = await this.storage.paymentStorage.GetTotalUsersBalance()
|
||||
const totalLndBalance = await this.getTotalLndBalance(totalUsersBalance)
|
||||
const deltaLnd = totalLndBalance - (this.initialLndBalance + this.accumulatedHtlcFees)
|
||||
const deltaUsers = totalUsersBalance - this.initialUsersBalance
|
||||
const deny = this.checkBalanceUpdate(deltaLnd, deltaUsers)
|
||||
if (deny) {
|
||||
this.log("Balance mismatch detected in absolute update, locking outgoing operations")
|
||||
this.lnd.LockOutgoingOperations()
|
||||
return
|
||||
}
|
||||
this.lnd.UnlockOutgoingOperations()
|
||||
}
|
||||
|
||||
PaymentRequested = async () => {
|
||||
this.log("Payment requested, checking balance")
|
||||
if (!this.ready) {
|
||||
throw new Error("Watchdog not ready")
|
||||
}
|
||||
return new Promise<void>((res, rej) => {
|
||||
this.queue.Run({ res, rej })
|
||||
})
|
||||
}
|
||||
|
||||
checkDeltas = (deltaLnd: number, deltaUsers: number): DeltaCheckResult => {
|
||||
if (deltaLnd < 0) {
|
||||
if (deltaUsers < 0) {
|
||||
const diff = Math.abs(deltaLnd - deltaUsers)
|
||||
return { type: 'negative', absoluteDiff: diff, relativeDiff: diff / Math.max(deltaLnd, deltaUsers) }
|
||||
} else {
|
||||
const diff = Math.abs(deltaLnd) + deltaUsers
|
||||
return { type: 'mismatch', absoluteDiff: diff }
|
||||
}
|
||||
} else {
|
||||
if (deltaUsers < 0) {
|
||||
const diff = deltaLnd + Math.abs(deltaUsers)
|
||||
return { type: 'mismatch', absoluteDiff: diff }
|
||||
} else {
|
||||
const diff = Math.abs(deltaLnd - deltaUsers)
|
||||
return { type: 'positive', absoluteDiff: diff, relativeDiff: diff / Math.max(deltaLnd, deltaUsers) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import { EnvCanBeInteger } from "../helpers/envParser.js";
|
||||
import FunctionQueue from "../helpers/functionQueue.js";
|
||||
import { getLogger } from "../helpers/logger.js";
|
||||
import LND from "../lnd/lnd.js";
|
||||
import { ChannelBalance } from "../lnd/settings.js";
|
||||
import Storage from '../storage/index.js'
|
||||
export type WatchdogSettings = {
|
||||
maxDiffSats: number
|
||||
}
|
||||
export const LoadWatchdogSettingsFromEnv = (test = false): WatchdogSettings => {
|
||||
return {
|
||||
maxDiffSats: EnvCanBeInteger("WATCHDOG_MAX_DIFF_SATS")
|
||||
}
|
||||
}
|
||||
export class Watchdog {
|
||||
queue: FunctionQueue<void>
|
||||
initialLndBalance: number;
|
||||
initialUsersBalance: number;
|
||||
startedAtUnix: number;
|
||||
latestIndexOffset: number;
|
||||
accumulatedHtlcFees: number;
|
||||
lnd: LND;
|
||||
settings: WatchdogSettings;
|
||||
storage: Storage;
|
||||
latestCheckStart = 0
|
||||
log = getLogger({ component: "watchdog" })
|
||||
ready = false
|
||||
interval: NodeJS.Timer;
|
||||
constructor(settings: WatchdogSettings, lnd: LND, storage: Storage) {
|
||||
this.lnd = lnd;
|
||||
this.settings = settings;
|
||||
this.storage = storage;
|
||||
this.queue = new FunctionQueue("watchdog_queue", () => this.StartCheck())
|
||||
}
|
||||
|
||||
Stop() {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
}
|
||||
|
||||
Start = async () => {
|
||||
this.startedAtUnix = Math.floor(Date.now() / 1000)
|
||||
const totalUsersBalance = await this.storage.paymentStorage.GetTotalUsersBalance()
|
||||
this.initialLndBalance = await this.getTotalLndBalance(totalUsersBalance)
|
||||
this.initialUsersBalance = totalUsersBalance
|
||||
const fwEvents = await this.lnd.GetForwardingHistory(0, this.startedAtUnix)
|
||||
this.latestIndexOffset = fwEvents.lastOffsetIndex
|
||||
this.accumulatedHtlcFees = 0
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
if (this.latestCheckStart + (1000 * 60) < Date.now()) {
|
||||
this.log("No balance check was made in the last minute, checking now")
|
||||
this.PaymentRequested()
|
||||
}
|
||||
}, 1000 * 60)
|
||||
|
||||
this.ready = true
|
||||
}
|
||||
|
||||
updateAccumulatedHtlcFees = async () => {
|
||||
const fwEvents = await this.lnd.GetForwardingHistory(this.latestIndexOffset, this.startedAtUnix)
|
||||
this.latestIndexOffset = fwEvents.lastOffsetIndex
|
||||
fwEvents.forwardingEvents.forEach((event) => {
|
||||
this.accumulatedHtlcFees += Number(event.fee)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
getTotalLndBalance = async (usersTotal: number) => {
|
||||
const walletBalance = await this.lnd.GetWalletBalance()
|
||||
this.log(Number(walletBalance.confirmedBalance), "sats in chain wallet")
|
||||
const channelsBalance = await this.lnd.GetChannelBalance()
|
||||
getLogger({ component: "debugLndBalancev3" })({ w: walletBalance, c: channelsBalance, u: usersTotal, f: this.accumulatedHtlcFees })
|
||||
const totalLightningBalanceMsats = (channelsBalance.localBalance?.msat || 0n) + (channelsBalance.unsettledLocalBalance?.msat || 0n)
|
||||
const totalLightningBalance = Math.ceil(Number(totalLightningBalanceMsats) / 1000)
|
||||
return Number(walletBalance.confirmedBalance) + totalLightningBalance
|
||||
}
|
||||
|
||||
checkBalanceUpdate = (deltaLnd: number, deltaUsers: number) => {
|
||||
this.log("LND balance update:", deltaLnd, "sats since app startup")
|
||||
this.log("Users balance update:", deltaUsers, "sats since app startup")
|
||||
|
||||
const result = this.checkDeltas(deltaLnd, deltaUsers)
|
||||
switch (result.type) {
|
||||
case 'mismatch':
|
||||
if (deltaLnd < 0) {
|
||||
this.log("WARNING! LND balance decreased while users balance increased creating a difference of", result.absoluteDiff, "sats")
|
||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||
this.log("Difference is too big for an update, locking outgoing operations")
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
this.log("LND balance increased while users balance decreased creating a difference of", result.absoluteDiff, "sats, could be caused by data loss, or liquidity injection")
|
||||
return false
|
||||
}
|
||||
break
|
||||
case 'negative':
|
||||
if (Math.abs(deltaLnd) > Math.abs(deltaUsers)) {
|
||||
this.log("WARNING! LND balance decreased more than users balance with a difference of", result.absoluteDiff, "sats")
|
||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||
this.log("Difference is too big for an update, locking outgoing operations")
|
||||
return true
|
||||
}
|
||||
} else if (deltaLnd === deltaUsers) {
|
||||
this.log("LND and users balance went both DOWN consistently")
|
||||
return false
|
||||
} else {
|
||||
this.log("LND balance decreased less than users balance with a difference of", result.absoluteDiff, "sats, could be caused by data loss, or liquidity injection")
|
||||
return false
|
||||
}
|
||||
break
|
||||
case 'positive':
|
||||
if (deltaLnd < deltaUsers) {
|
||||
this.log("WARNING! LND balance increased less than users balance with a difference of", result.absoluteDiff, "sats")
|
||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||
this.log("Difference is too big for an update, locking outgoing operations")
|
||||
return true
|
||||
}
|
||||
} else if (deltaLnd === deltaUsers) {
|
||||
this.log("LND and users balance went both UP consistently")
|
||||
return false
|
||||
} else {
|
||||
this.log("LND balance increased more than users balance with a difference of", result.absoluteDiff, "sats, could be caused by data loss, or liquidity injection")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
StartCheck = async () => {
|
||||
this.latestCheckStart = Date.now()
|
||||
await this.updateAccumulatedHtlcFees()
|
||||
const totalUsersBalance = await this.storage.paymentStorage.GetTotalUsersBalance()
|
||||
const totalLndBalance = await this.getTotalLndBalance(totalUsersBalance)
|
||||
const deltaLnd = totalLndBalance - (this.initialLndBalance + this.accumulatedHtlcFees)
|
||||
const deltaUsers = totalUsersBalance - this.initialUsersBalance
|
||||
const deny = this.checkBalanceUpdate(deltaLnd, deltaUsers)
|
||||
if (deny) {
|
||||
this.log("Balance mismatch detected in absolute update, locking outgoing operations")
|
||||
this.lnd.LockOutgoingOperations()
|
||||
return
|
||||
}
|
||||
this.lnd.UnlockOutgoingOperations()
|
||||
}
|
||||
|
||||
PaymentRequested = async () => {
|
||||
this.log("Payment requested, checking balance")
|
||||
if (!this.ready) {
|
||||
throw new Error("Watchdog not ready")
|
||||
}
|
||||
return new Promise<void>((res, rej) => {
|
||||
this.queue.Run({ res, rej })
|
||||
})
|
||||
}
|
||||
|
||||
checkDeltas = (deltaLnd: number, deltaUsers: number): DeltaCheckResult => {
|
||||
if (deltaLnd < 0) {
|
||||
if (deltaUsers < 0) {
|
||||
const diff = Math.abs(deltaLnd - deltaUsers)
|
||||
return { type: 'negative', absoluteDiff: diff, relativeDiff: diff / Math.max(deltaLnd, deltaUsers) }
|
||||
} else {
|
||||
const diff = Math.abs(deltaLnd) + deltaUsers
|
||||
return { type: 'mismatch', absoluteDiff: diff }
|
||||
}
|
||||
} else {
|
||||
if (deltaUsers < 0) {
|
||||
const diff = deltaLnd + Math.abs(deltaUsers)
|
||||
return { type: 'mismatch', absoluteDiff: diff }
|
||||
} else {
|
||||
const diff = Math.abs(deltaLnd - deltaUsers)
|
||||
return { type: 'positive', absoluteDiff: diff, relativeDiff: diff / Math.max(deltaLnd, deltaUsers) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
type DeltaCheckResult = { type: 'negative' | 'positive', absoluteDiff: number, relativeDiff: number } | { type: 'mismatch', absoluteDiff: number }
|
||||
|
|
@ -1,132 +1,132 @@
|
|||
import Storage from '../storage/index.js'
|
||||
import { ForwardEvent, HtlcEvent, HtlcEvent_EventType } from "../../../proto/lnd/router.js";
|
||||
import { getLogger } from "../helpers/logger.js";
|
||||
type EventInfo = {
|
||||
eventType: HtlcEvent_EventType
|
||||
outgoingHtlcId: number
|
||||
incomingHtlcId: number
|
||||
outgoingChannelId: number
|
||||
incomingChannelId: number
|
||||
}
|
||||
export default class HtlcTracker {
|
||||
storage: Storage
|
||||
pendingSendHtlcs: Map<number, number> = new Map()
|
||||
pendingReceiveHtlcs: Map<number, number> = new Map()
|
||||
pendingForwardHtlcs: Map<number, number> = new Map()
|
||||
constructor(storage: Storage) {
|
||||
this.storage = storage
|
||||
}
|
||||
log = getLogger({ component: 'htlcTracker' })
|
||||
onHtlcEvent = async (htlc: HtlcEvent) => {
|
||||
getLogger({ component: 'debugHtlcs' })(htlc)
|
||||
const htlcEvent = htlc.event
|
||||
if (htlcEvent.oneofKind === 'subscribedEvent') {
|
||||
this.log("htlc subscribed")
|
||||
return
|
||||
}
|
||||
const outgoingHtlcId = Number(htlc.outgoingHtlcId)
|
||||
const incomingHtlcId = Number(htlc.incomingHtlcId)
|
||||
const outgoingChannelId = Number(htlc.outgoingChannelId)
|
||||
const incomingChannelId = Number(htlc.incomingChannelId)
|
||||
const info: EventInfo = { eventType: htlc.eventType, outgoingChannelId, incomingChannelId, outgoingHtlcId, incomingHtlcId }
|
||||
switch (htlcEvent.oneofKind) {
|
||||
case 'forwardEvent':
|
||||
return this.handleForward(htlcEvent.forwardEvent, info)
|
||||
case 'forwardFailEvent':
|
||||
return this.handleFailure(info)
|
||||
case 'linkFailEvent':
|
||||
return this.handleFailure(info)
|
||||
case 'finalHtlcEvent':
|
||||
if (!htlcEvent.finalHtlcEvent.settled) {
|
||||
return this.handleFailure(info)
|
||||
} else {
|
||||
return this.handleSuccess(info)
|
||||
}
|
||||
case 'settleEvent':
|
||||
return this.handleSuccess(info)
|
||||
default:
|
||||
this.log("unknown htlc event type")
|
||||
}
|
||||
}
|
||||
|
||||
handleForward = (fwe: ForwardEvent, { eventType, outgoingHtlcId, incomingHtlcId }: EventInfo) => {
|
||||
this.log("new forward event, currently tracked htlcs: (s,r,f)", this.pendingSendHtlcs.size, this.pendingReceiveHtlcs.size, this.pendingForwardHtlcs.size)
|
||||
const { info } = fwe
|
||||
const incomingAmtMsat = info ? Number(info.incomingAmtMsat) : 0
|
||||
const outgoingAmtMsat = info ? Number(info.outgoingAmtMsat) : 0
|
||||
if (eventType === HtlcEvent_EventType.SEND) {
|
||||
this.pendingSendHtlcs.set(outgoingHtlcId, outgoingAmtMsat - incomingAmtMsat)
|
||||
} else if (eventType === HtlcEvent_EventType.RECEIVE) {
|
||||
this.pendingReceiveHtlcs.set(incomingHtlcId, incomingAmtMsat - outgoingAmtMsat)
|
||||
} else if (eventType === HtlcEvent_EventType.FORWARD) {
|
||||
this.pendingForwardHtlcs.set(outgoingHtlcId, outgoingAmtMsat - incomingAmtMsat)
|
||||
} else {
|
||||
this.log("unknown htlc event type for forward event")
|
||||
}
|
||||
}
|
||||
|
||||
handleFailure = ({ eventType, outgoingHtlcId, incomingHtlcId, incomingChannelId, outgoingChannelId }: EventInfo) => {
|
||||
if (eventType === HtlcEvent_EventType.SEND && this.deleteMapEntry(outgoingHtlcId, this.pendingSendHtlcs) !== null) {
|
||||
return this.incrementSendFailures(outgoingChannelId)
|
||||
}
|
||||
if (eventType === HtlcEvent_EventType.RECEIVE && this.deleteMapEntry(incomingHtlcId, this.pendingReceiveHtlcs) !== null) {
|
||||
return this.incrementReceiveFailures(incomingChannelId)
|
||||
}
|
||||
if (eventType === HtlcEvent_EventType.FORWARD) {
|
||||
const amt = this.deleteMapEntry(outgoingHtlcId, this.pendingForwardHtlcs)
|
||||
if (amt !== null) {
|
||||
return this.incrementForwardFailures(incomingChannelId, outgoingChannelId, amt)
|
||||
}
|
||||
}
|
||||
if (eventType === HtlcEvent_EventType.UNKNOWN) {
|
||||
const fwdAmt = this.deleteMapEntry(outgoingHtlcId, this.pendingForwardHtlcs)
|
||||
if (fwdAmt !== null) {
|
||||
return this.incrementForwardFailures(incomingChannelId, outgoingChannelId, fwdAmt)
|
||||
}
|
||||
if (this.deleteMapEntry(outgoingHtlcId, this.pendingSendHtlcs) !== null) {
|
||||
return this.incrementSendFailures(outgoingChannelId)
|
||||
}
|
||||
if (this.deleteMapEntry(incomingHtlcId, this.pendingReceiveHtlcs) !== null) {
|
||||
return this.incrementReceiveFailures(incomingChannelId)
|
||||
}
|
||||
}
|
||||
this.log("unknown htlc event type for failure event", eventType)
|
||||
}
|
||||
|
||||
handleSuccess = ({ eventType, outgoingHtlcId, incomingHtlcId }: EventInfo) => {
|
||||
if (eventType === HtlcEvent_EventType.SEND) {
|
||||
this.deleteMapEntry(outgoingHtlcId, this.pendingSendHtlcs)
|
||||
} else if (eventType === HtlcEvent_EventType.RECEIVE) {
|
||||
this.deleteMapEntry(incomingHtlcId, this.pendingReceiveHtlcs)
|
||||
} else if (eventType === HtlcEvent_EventType.FORWARD) {
|
||||
this.deleteMapEntry(outgoingHtlcId, this.pendingForwardHtlcs)
|
||||
} else if (eventType === HtlcEvent_EventType.UNKNOWN) {
|
||||
if (this.deleteMapEntry(outgoingHtlcId, this.pendingSendHtlcs) !== null) return
|
||||
if (this.deleteMapEntry(incomingHtlcId, this.pendingReceiveHtlcs) !== null) return
|
||||
if (this.deleteMapEntry(outgoingHtlcId, this.pendingForwardHtlcs) !== null) return
|
||||
} else {
|
||||
this.log("unknown htlc event type for success event", eventType)
|
||||
}
|
||||
}
|
||||
|
||||
deleteMapEntry = (key: number, map: Map<number, number>) => {
|
||||
if (!map.has(key)) {
|
||||
return null
|
||||
}
|
||||
const v = map.get(key)
|
||||
map.delete(key)
|
||||
return v || null
|
||||
}
|
||||
|
||||
incrementSendFailures = async (outgoingChannelId: number) => {
|
||||
await this.storage.metricsStorage.IncrementChannelRouting(outgoingChannelId.toString(), { send_errors: 1 })
|
||||
}
|
||||
incrementReceiveFailures = async (incomingChannelId: number) => {
|
||||
await this.storage.metricsStorage.IncrementChannelRouting(incomingChannelId.toString(), { receive_errors: 1 })
|
||||
}
|
||||
incrementForwardFailures = async (incomingChannelId: number, outgoingChannelId: number, amt: number) => {
|
||||
await this.storage.metricsStorage.IncrementChannelRouting(incomingChannelId.toString(), { forward_errors_as_input: 1, missed_forward_fee_as_input: amt })
|
||||
await this.storage.metricsStorage.IncrementChannelRouting(outgoingChannelId.toString(), { forward_errors_as_output: 1, missed_forward_fee_as_output: amt })
|
||||
}
|
||||
}
|
||||
|
||||
import Storage from '../storage/index.js'
|
||||
import { ForwardEvent, HtlcEvent, HtlcEvent_EventType } from "../../../proto/lnd/router.js";
|
||||
import { getLogger } from "../helpers/logger.js";
|
||||
type EventInfo = {
|
||||
eventType: HtlcEvent_EventType
|
||||
outgoingHtlcId: number
|
||||
incomingHtlcId: number
|
||||
outgoingChannelId: number
|
||||
incomingChannelId: number
|
||||
}
|
||||
export default class HtlcTracker {
|
||||
storage: Storage
|
||||
pendingSendHtlcs: Map<number, number> = new Map()
|
||||
pendingReceiveHtlcs: Map<number, number> = new Map()
|
||||
pendingForwardHtlcs: Map<number, number> = new Map()
|
||||
constructor(storage: Storage) {
|
||||
this.storage = storage
|
||||
}
|
||||
log = getLogger({ component: 'htlcTracker' })
|
||||
onHtlcEvent = async (htlc: HtlcEvent) => {
|
||||
getLogger({ component: 'debugHtlcs' })(htlc)
|
||||
const htlcEvent = htlc.event
|
||||
if (htlcEvent.oneofKind === 'subscribedEvent') {
|
||||
this.log("htlc subscribed")
|
||||
return
|
||||
}
|
||||
const outgoingHtlcId = Number(htlc.outgoingHtlcId)
|
||||
const incomingHtlcId = Number(htlc.incomingHtlcId)
|
||||
const outgoingChannelId = Number(htlc.outgoingChannelId)
|
||||
const incomingChannelId = Number(htlc.incomingChannelId)
|
||||
const info: EventInfo = { eventType: htlc.eventType, outgoingChannelId, incomingChannelId, outgoingHtlcId, incomingHtlcId }
|
||||
switch (htlcEvent.oneofKind) {
|
||||
case 'forwardEvent':
|
||||
return this.handleForward(htlcEvent.forwardEvent, info)
|
||||
case 'forwardFailEvent':
|
||||
return this.handleFailure(info)
|
||||
case 'linkFailEvent':
|
||||
return this.handleFailure(info)
|
||||
case 'finalHtlcEvent':
|
||||
if (!htlcEvent.finalHtlcEvent.settled) {
|
||||
return this.handleFailure(info)
|
||||
} else {
|
||||
return this.handleSuccess(info)
|
||||
}
|
||||
case 'settleEvent':
|
||||
return this.handleSuccess(info)
|
||||
default:
|
||||
this.log("unknown htlc event type")
|
||||
}
|
||||
}
|
||||
|
||||
handleForward = (fwe: ForwardEvent, { eventType, outgoingHtlcId, incomingHtlcId }: EventInfo) => {
|
||||
this.log("new forward event, currently tracked htlcs: (s,r,f)", this.pendingSendHtlcs.size, this.pendingReceiveHtlcs.size, this.pendingForwardHtlcs.size)
|
||||
const { info } = fwe
|
||||
const incomingAmtMsat = info ? Number(info.incomingAmtMsat) : 0
|
||||
const outgoingAmtMsat = info ? Number(info.outgoingAmtMsat) : 0
|
||||
if (eventType === HtlcEvent_EventType.SEND) {
|
||||
this.pendingSendHtlcs.set(outgoingHtlcId, outgoingAmtMsat - incomingAmtMsat)
|
||||
} else if (eventType === HtlcEvent_EventType.RECEIVE) {
|
||||
this.pendingReceiveHtlcs.set(incomingHtlcId, incomingAmtMsat - outgoingAmtMsat)
|
||||
} else if (eventType === HtlcEvent_EventType.FORWARD) {
|
||||
this.pendingForwardHtlcs.set(outgoingHtlcId, outgoingAmtMsat - incomingAmtMsat)
|
||||
} else {
|
||||
this.log("unknown htlc event type for forward event")
|
||||
}
|
||||
}
|
||||
|
||||
handleFailure = ({ eventType, outgoingHtlcId, incomingHtlcId, incomingChannelId, outgoingChannelId }: EventInfo) => {
|
||||
if (eventType === HtlcEvent_EventType.SEND && this.deleteMapEntry(outgoingHtlcId, this.pendingSendHtlcs) !== null) {
|
||||
return this.incrementSendFailures(outgoingChannelId)
|
||||
}
|
||||
if (eventType === HtlcEvent_EventType.RECEIVE && this.deleteMapEntry(incomingHtlcId, this.pendingReceiveHtlcs) !== null) {
|
||||
return this.incrementReceiveFailures(incomingChannelId)
|
||||
}
|
||||
if (eventType === HtlcEvent_EventType.FORWARD) {
|
||||
const amt = this.deleteMapEntry(outgoingHtlcId, this.pendingForwardHtlcs)
|
||||
if (amt !== null) {
|
||||
return this.incrementForwardFailures(incomingChannelId, outgoingChannelId, amt)
|
||||
}
|
||||
}
|
||||
if (eventType === HtlcEvent_EventType.UNKNOWN) {
|
||||
const fwdAmt = this.deleteMapEntry(outgoingHtlcId, this.pendingForwardHtlcs)
|
||||
if (fwdAmt !== null) {
|
||||
return this.incrementForwardFailures(incomingChannelId, outgoingChannelId, fwdAmt)
|
||||
}
|
||||
if (this.deleteMapEntry(outgoingHtlcId, this.pendingSendHtlcs) !== null) {
|
||||
return this.incrementSendFailures(outgoingChannelId)
|
||||
}
|
||||
if (this.deleteMapEntry(incomingHtlcId, this.pendingReceiveHtlcs) !== null) {
|
||||
return this.incrementReceiveFailures(incomingChannelId)
|
||||
}
|
||||
}
|
||||
this.log("unknown htlc event type for failure event", eventType)
|
||||
}
|
||||
|
||||
handleSuccess = ({ eventType, outgoingHtlcId, incomingHtlcId }: EventInfo) => {
|
||||
if (eventType === HtlcEvent_EventType.SEND) {
|
||||
this.deleteMapEntry(outgoingHtlcId, this.pendingSendHtlcs)
|
||||
} else if (eventType === HtlcEvent_EventType.RECEIVE) {
|
||||
this.deleteMapEntry(incomingHtlcId, this.pendingReceiveHtlcs)
|
||||
} else if (eventType === HtlcEvent_EventType.FORWARD) {
|
||||
this.deleteMapEntry(outgoingHtlcId, this.pendingForwardHtlcs)
|
||||
} else if (eventType === HtlcEvent_EventType.UNKNOWN) {
|
||||
if (this.deleteMapEntry(outgoingHtlcId, this.pendingSendHtlcs) !== null) return
|
||||
if (this.deleteMapEntry(incomingHtlcId, this.pendingReceiveHtlcs) !== null) return
|
||||
if (this.deleteMapEntry(outgoingHtlcId, this.pendingForwardHtlcs) !== null) return
|
||||
} else {
|
||||
this.log("unknown htlc event type for success event", eventType)
|
||||
}
|
||||
}
|
||||
|
||||
deleteMapEntry = (key: number, map: Map<number, number>) => {
|
||||
if (!map.has(key)) {
|
||||
return null
|
||||
}
|
||||
const v = map.get(key)
|
||||
map.delete(key)
|
||||
return v || null
|
||||
}
|
||||
|
||||
incrementSendFailures = async (outgoingChannelId: number) => {
|
||||
await this.storage.metricsStorage.IncrementChannelRouting(outgoingChannelId.toString(), { send_errors: 1 })
|
||||
}
|
||||
incrementReceiveFailures = async (incomingChannelId: number) => {
|
||||
await this.storage.metricsStorage.IncrementChannelRouting(incomingChannelId.toString(), { receive_errors: 1 })
|
||||
}
|
||||
incrementForwardFailures = async (incomingChannelId: number, outgoingChannelId: number, amt: number) => {
|
||||
await this.storage.metricsStorage.IncrementChannelRouting(incomingChannelId.toString(), { forward_errors_as_input: 1, missed_forward_fee_as_input: amt })
|
||||
await this.storage.metricsStorage.IncrementChannelRouting(outgoingChannelId.toString(), { forward_errors_as_output: 1, missed_forward_fee_as_output: amt })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,262 +1,262 @@
|
|||
import Storage from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { Application } from '../storage/entity/Application.js'
|
||||
import { HtlcEvent, HtlcEvent_EventType } from '../../../proto/lnd/router.js'
|
||||
import { BalanceInfo } from '../lnd/settings.js'
|
||||
import { BalanceEvent } from '../storage/entity/BalanceEvent.js'
|
||||
import { ChannelBalanceEvent } from '../storage/entity/ChannelsBalanceEvent.js'
|
||||
import LND from '../lnd/lnd.js'
|
||||
import HtlcTracker from './htlcTracker.js'
|
||||
const maxEvents = 100_000
|
||||
export default class Handler {
|
||||
storage: Storage
|
||||
lnd: LND
|
||||
htlcTracker: HtlcTracker
|
||||
metrics: Types.UsageMetric[] = []
|
||||
constructor(storage: Storage, lnd: LND) {
|
||||
this.storage = storage
|
||||
this.lnd = lnd
|
||||
this.htlcTracker = new HtlcTracker(this.storage)
|
||||
}
|
||||
|
||||
async HtlcCb(htlc: HtlcEvent) {
|
||||
await this.htlcTracker.onHtlcEvent(htlc)
|
||||
}
|
||||
|
||||
async NewBlockCb(height: number, balanceInfo: BalanceInfo) {
|
||||
const balanceEvent: Partial<BalanceEvent> = {
|
||||
block_height: height,
|
||||
confirmed_chain_balance: balanceInfo.confirmedBalance,
|
||||
unconfirmed_chain_balance: balanceInfo.unconfirmedBalance,
|
||||
total_chain_balance: balanceInfo.totalBalance,
|
||||
}
|
||||
const channelsEvents: Partial<ChannelBalanceEvent>[] = balanceInfo.channelsBalance.map(c => ({
|
||||
channel_id: c.channelId,
|
||||
local_balance_sats: c.localBalanceSats,
|
||||
remote_balance_sats: c.remoteBalanceSats,
|
||||
}))
|
||||
await this.storage.metricsStorage.SaveBalanceEvents(balanceEvent, channelsEvents)
|
||||
}
|
||||
|
||||
async FetchLatestForwardingEvents() {
|
||||
const latestIndex = await this.storage.metricsStorage.GetLatestForwardingIndexOffset()
|
||||
const res = await this.lnd.GetForwardingHistory(latestIndex)
|
||||
const forwards = res.forwardingEvents.map(e => ({ fee: Number(e.fee), chanIdIn: e.chanIdIn, chanIdOut: e.chanIdOut, timestampNs: e.timestampNs.toString(), offset: res.lastOffsetIndex }))
|
||||
await Promise.all(forwards.map(async f => {
|
||||
await this.storage.metricsStorage.IncrementChannelRouting(f.chanIdIn, { forward_fee_as_input: f.fee, latest_index_offset: f.offset })
|
||||
await this.storage.metricsStorage.IncrementChannelRouting(f.chanIdOut, { forward_fee_as_output: f.fee, latest_index_offset: f.offset })
|
||||
}))
|
||||
}
|
||||
|
||||
AddMetrics(newMetrics: (Types.RequestMetric & { app_id?: string })[]) {
|
||||
const parsed: Types.UsageMetric[] = newMetrics.map(m => ({
|
||||
rpc_name: m.rpcName,
|
||||
batch: m.batch,
|
||||
nostr: m.nostr,
|
||||
batch_size: m.batchSize,
|
||||
parsed_in_nano: Number(m.parse - m.start),
|
||||
auth_in_nano: Number(m.guard - m.parse),
|
||||
validate_in_nano: Number(m.validate - m.guard),
|
||||
handle_in_nano: Number(m.handle - m.validate),
|
||||
success: !m.error,
|
||||
app_id: m.app_id ? m.app_id : "",
|
||||
processed_at_ms: m.startMs
|
||||
}))
|
||||
const len = this.metrics.push(...parsed)
|
||||
if (len > maxEvents) {
|
||||
this.metrics.splice(0, len - maxEvents)
|
||||
}
|
||||
}
|
||||
async GetUsageMetrics(): Promise<Types.UsageMetrics> {
|
||||
return {
|
||||
metrics: this.metrics
|
||||
}
|
||||
}
|
||||
async GetAppsMetrics(req: Types.AppsMetricsRequest): Promise<Types.AppsMetrics> {
|
||||
const dbApps = await this.storage.applicationStorage.GetApplications()
|
||||
const apps = await Promise.all(dbApps.map(app => this.GetAppMetrics(req, app)))
|
||||
const unlinked = await this.GetAppMetrics(req, null)
|
||||
apps.push(unlinked)
|
||||
return {
|
||||
apps
|
||||
}
|
||||
}
|
||||
|
||||
async GetAppMetrics(req: Types.AppsMetricsRequest, app: Application | null): Promise<Types.AppMetrics> {
|
||||
const totalFees = await this.storage.paymentStorage.GetTotalFeesPaidInApp(app)
|
||||
const { receivingInvoices, receivingTransactions, outgoingInvoices, outgoingTransactions, receivingAddresses, userToUser } = await this.storage.paymentStorage.GetAppOperations(app, { from: req.from_unix, to: req.to_unix })
|
||||
let totalReceived = 0
|
||||
let totalSpent = 0
|
||||
let unpaidInvoices = 0
|
||||
let feesInRange = 0
|
||||
const operations: Types.UserOperation[] = []
|
||||
receivingInvoices.forEach(i => {
|
||||
if (i.paid_at_unix > 0) {
|
||||
totalReceived += i.paid_amount
|
||||
feesInRange += i.service_fee
|
||||
if (req.include_operations) operations.push({ type: Types.UserOperationType.INCOMING_INVOICE, amount: i.paid_amount, inbound: true, paidAtUnix: i.paid_at_unix, confirmed: true, service_fee: i.service_fee, network_fee: 0, identifier: "", operationId: "", tx_hash: "", internal: i.internal })
|
||||
} else {
|
||||
unpaidInvoices++
|
||||
}
|
||||
})
|
||||
receivingTransactions.forEach(txs => {
|
||||
txs.forEach(tx => {
|
||||
if (req.include_operations) operations.push({ type: Types.UserOperationType.INCOMING_TX, amount: tx.paid_amount, inbound: true, paidAtUnix: tx.paid_at_unix, confirmed: tx.confs > 1, service_fee: tx.service_fee, network_fee: 0, identifier: "", operationId: "", tx_hash: tx.tx_hash, internal: tx.internal })
|
||||
if (tx.confs > 1) {
|
||||
feesInRange += tx.service_fee
|
||||
totalReceived += tx.paid_amount
|
||||
}
|
||||
})
|
||||
})
|
||||
outgoingInvoices.forEach(i => {
|
||||
if (req.include_operations) operations.push({ type: Types.UserOperationType.OUTGOING_INVOICE, amount: i.paid_amount, inbound: false, paidAtUnix: i.paid_at_unix, confirmed: true, service_fee: i.service_fees, network_fee: i.routing_fees, identifier: "", operationId: "", tx_hash: "", internal: i.internal })
|
||||
totalSpent += i.paid_amount
|
||||
feesInRange += i.service_fees
|
||||
})
|
||||
outgoingTransactions.forEach(tx => {
|
||||
if (req.include_operations) operations.push({ type: Types.UserOperationType.OUTGOING_TX, amount: tx.paid_amount, inbound: false, paidAtUnix: tx.paid_at_unix, confirmed: tx.confs > 1, service_fee: tx.service_fees, network_fee: tx.chain_fees, identifier: "", operationId: "", tx_hash: tx.tx_hash, internal: tx.internal })
|
||||
totalSpent += tx.paid_amount
|
||||
feesInRange += tx.service_fees
|
||||
})
|
||||
|
||||
userToUser.forEach(op => {
|
||||
if (req.include_operations) operations.push({ type: Types.UserOperationType.INCOMING_USER_TO_USER, amount: op.paid_amount, inbound: true, paidAtUnix: op.paid_at_unix, confirmed: true, service_fee: op.service_fees, network_fee: 0, identifier: "", operationId: "", tx_hash: "", internal: true })
|
||||
feesInRange += op.service_fees
|
||||
})
|
||||
|
||||
const users = await this.storage.applicationStorage.GetApplicationUsers(app, { from: req.from_unix, to: req.to_unix })
|
||||
|
||||
let totalUserWithBalance = 0
|
||||
let totalUserWithNoBalance = 0
|
||||
let totalUsersWithNegativeBalance = 0
|
||||
let totalAlwaysBeenInactive = 0
|
||||
let balanceSum = 0
|
||||
let minBalance = Number.MAX_SAFE_INTEGER
|
||||
let maxBalance = 0
|
||||
await Promise.all(users.map(async u => {
|
||||
if (u.user.balance_sats < 0) {
|
||||
totalUsersWithNegativeBalance++
|
||||
} else if (u.user.balance_sats === 0) {
|
||||
const wasActive = await this.storage.paymentStorage.UserHasOutgoingOperation(u.user.user_id)
|
||||
totalUserWithNoBalance++
|
||||
if (!wasActive) {
|
||||
totalAlwaysBeenInactive++
|
||||
}
|
||||
} else {
|
||||
balanceSum += u.user.balance_sats
|
||||
totalUserWithBalance++
|
||||
if (u.user.balance_sats < minBalance) {
|
||||
minBalance = u.user.balance_sats
|
||||
}
|
||||
if (u.user.balance_sats > maxBalance) {
|
||||
maxBalance = u.user.balance_sats
|
||||
}
|
||||
}
|
||||
}))
|
||||
return {
|
||||
app: {
|
||||
name: app ? app.name : "unlinked to app",
|
||||
id: app ? app.app_id : "unlinked",
|
||||
npub: app ? (app.nostr_public_key || "") : "",
|
||||
balance: app ? app.owner.balance_sats : 0,
|
||||
},
|
||||
users: {
|
||||
total: users.length,
|
||||
always_been_inactive: totalAlwaysBeenInactive,
|
||||
balance_avg: Math.round(balanceSum / totalUserWithBalance),
|
||||
balance_median: Math.round((maxBalance + minBalance) / 2),
|
||||
no_balance: totalUserWithNoBalance,
|
||||
negative_balance: totalUsersWithNegativeBalance,
|
||||
},
|
||||
|
||||
received: totalReceived,
|
||||
spent: totalSpent,
|
||||
available: balanceSum,
|
||||
fees: feesInRange,
|
||||
total_fees: totalFees,
|
||||
invoices: receivingInvoices.length,
|
||||
|
||||
operations
|
||||
}
|
||||
}
|
||||
|
||||
async GetChannelsInfo() {
|
||||
const { channels } = await this.lnd.ListChannels()
|
||||
let totalActive = 0
|
||||
let totalInactive = 0
|
||||
channels.forEach(c => {
|
||||
if (c.active) {
|
||||
totalActive++
|
||||
} else {
|
||||
totalInactive++
|
||||
}
|
||||
})
|
||||
return {
|
||||
totalActive, totalInactive, openChannels: channels
|
||||
}
|
||||
}
|
||||
async GetPendingChannelsInfo() {
|
||||
const { pendingForceClosingChannels, pendingOpenChannels } = await this.lnd.ListPendingChannels()
|
||||
return { totalPendingClose: pendingForceClosingChannels.length, totalPendingOpen: pendingOpenChannels.length }
|
||||
|
||||
}
|
||||
|
||||
|
||||
async GetLndMetrics(req: Types.LndMetricsRequest): Promise<Types.LndMetrics> {
|
||||
const { openChannels, totalActive, totalInactive } = await this.GetChannelsInfo()
|
||||
const { totalPendingOpen, totalPendingClose } = await this.GetPendingChannelsInfo()
|
||||
const { channels: closedChannels } = await this.lnd.ListClosedChannels()
|
||||
const rawRouting = await this.storage.metricsStorage.GetChannelRouting({ from: req.from_unix, to: req.to_unix })
|
||||
const routingMap: Record<string, Types.ChannelRouting> = {}
|
||||
rawRouting.forEach(r => {
|
||||
if (!routingMap[r.channel_id]) {
|
||||
routingMap[r.channel_id] = {
|
||||
channel_id: r.channel_id,
|
||||
send_errors: 0,
|
||||
receive_errors: 0,
|
||||
forward_errors_as_input: 0,
|
||||
forward_errors_as_output: 0,
|
||||
missed_forward_fee_as_input: 0,
|
||||
missed_forward_fee_as_output: 0,
|
||||
forward_fee_as_input: 0,
|
||||
forward_fee_as_output: 0,
|
||||
events_number: 0
|
||||
}
|
||||
}
|
||||
routingMap[r.channel_id].send_errors += r.send_errors
|
||||
routingMap[r.channel_id].receive_errors += r.receive_errors
|
||||
routingMap[r.channel_id].forward_errors_as_input += r.forward_errors_as_input
|
||||
routingMap[r.channel_id].forward_errors_as_output += r.forward_errors_as_output
|
||||
routingMap[r.channel_id].missed_forward_fee_as_input += r.missed_forward_fee_as_input
|
||||
routingMap[r.channel_id].missed_forward_fee_as_output += r.missed_forward_fee_as_output
|
||||
routingMap[r.channel_id].forward_fee_as_input += r.forward_fee_as_input
|
||||
routingMap[r.channel_id].forward_fee_as_output += r.forward_fee_as_output
|
||||
routingMap[r.channel_id].events_number++
|
||||
})
|
||||
const { channelsBalanceEvents, chainBalanceEvents } = await this.storage.metricsStorage.GetBalanceEvents({ from: req.from_unix, to: req.to_unix })
|
||||
return {
|
||||
nodes: [{
|
||||
chain_balance_events: chainBalanceEvents.map(e => ({
|
||||
block_height: e.block_height,
|
||||
confirmed_balance: e.confirmed_chain_balance,
|
||||
unconfirmed_balance: e.unconfirmed_chain_balance,
|
||||
total_balance: e.total_chain_balance
|
||||
})),
|
||||
channels_balance_events: channelsBalanceEvents.map(e => ({
|
||||
block_height: e.balance_event.block_height,
|
||||
channel_id: e.channel_id,
|
||||
local_balance_sats: e.local_balance_sats,
|
||||
remote_balance_sats: e.remote_balance_sats
|
||||
})),
|
||||
closing_channels: totalPendingClose,
|
||||
pending_channels: totalPendingOpen,
|
||||
offline_channels: totalInactive,
|
||||
online_channels: totalActive,
|
||||
closed_channels: closedChannels.map(c => ({ capacity: Number(c.capacity), channel_id: c.chanId, closed_height: c.closeHeight })),
|
||||
open_channels: openChannels.map(c => ({ active: c.active, capacity: Number(c.capacity), channel_id: c.chanId, lifetime: Number(c.lifetime), local_balance: Number(c.localBalance), remote_balance: Number(c.remoteBalance) })),
|
||||
channel_routing: Object.values(routingMap)
|
||||
}],
|
||||
|
||||
}
|
||||
}
|
||||
import Storage from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { Application } from '../storage/entity/Application.js'
|
||||
import { HtlcEvent, HtlcEvent_EventType } from '../../../proto/lnd/router.js'
|
||||
import { BalanceInfo } from '../lnd/settings.js'
|
||||
import { BalanceEvent } from '../storage/entity/BalanceEvent.js'
|
||||
import { ChannelBalanceEvent } from '../storage/entity/ChannelsBalanceEvent.js'
|
||||
import LND from '../lnd/lnd.js'
|
||||
import HtlcTracker from './htlcTracker.js'
|
||||
const maxEvents = 100_000
|
||||
export default class Handler {
|
||||
storage: Storage
|
||||
lnd: LND
|
||||
htlcTracker: HtlcTracker
|
||||
metrics: Types.UsageMetric[] = []
|
||||
constructor(storage: Storage, lnd: LND) {
|
||||
this.storage = storage
|
||||
this.lnd = lnd
|
||||
this.htlcTracker = new HtlcTracker(this.storage)
|
||||
}
|
||||
|
||||
async HtlcCb(htlc: HtlcEvent) {
|
||||
await this.htlcTracker.onHtlcEvent(htlc)
|
||||
}
|
||||
|
||||
async NewBlockCb(height: number, balanceInfo: BalanceInfo) {
|
||||
const balanceEvent: Partial<BalanceEvent> = {
|
||||
block_height: height,
|
||||
confirmed_chain_balance: balanceInfo.confirmedBalance,
|
||||
unconfirmed_chain_balance: balanceInfo.unconfirmedBalance,
|
||||
total_chain_balance: balanceInfo.totalBalance,
|
||||
}
|
||||
const channelsEvents: Partial<ChannelBalanceEvent>[] = balanceInfo.channelsBalance.map(c => ({
|
||||
channel_id: c.channelId,
|
||||
local_balance_sats: c.localBalanceSats,
|
||||
remote_balance_sats: c.remoteBalanceSats,
|
||||
}))
|
||||
await this.storage.metricsStorage.SaveBalanceEvents(balanceEvent, channelsEvents)
|
||||
}
|
||||
|
||||
async FetchLatestForwardingEvents() {
|
||||
const latestIndex = await this.storage.metricsStorage.GetLatestForwardingIndexOffset()
|
||||
const res = await this.lnd.GetForwardingHistory(latestIndex)
|
||||
const forwards = res.forwardingEvents.map(e => ({ fee: Number(e.fee), chanIdIn: e.chanIdIn, chanIdOut: e.chanIdOut, timestampNs: e.timestampNs.toString(), offset: res.lastOffsetIndex }))
|
||||
await Promise.all(forwards.map(async f => {
|
||||
await this.storage.metricsStorage.IncrementChannelRouting(f.chanIdIn, { forward_fee_as_input: f.fee, latest_index_offset: f.offset })
|
||||
await this.storage.metricsStorage.IncrementChannelRouting(f.chanIdOut, { forward_fee_as_output: f.fee, latest_index_offset: f.offset })
|
||||
}))
|
||||
}
|
||||
|
||||
AddMetrics(newMetrics: (Types.RequestMetric & { app_id?: string })[]) {
|
||||
const parsed: Types.UsageMetric[] = newMetrics.map(m => ({
|
||||
rpc_name: m.rpcName,
|
||||
batch: m.batch,
|
||||
nostr: m.nostr,
|
||||
batch_size: m.batchSize,
|
||||
parsed_in_nano: Number(m.parse - m.start),
|
||||
auth_in_nano: Number(m.guard - m.parse),
|
||||
validate_in_nano: Number(m.validate - m.guard),
|
||||
handle_in_nano: Number(m.handle - m.validate),
|
||||
success: !m.error,
|
||||
app_id: m.app_id ? m.app_id : "",
|
||||
processed_at_ms: m.startMs
|
||||
}))
|
||||
const len = this.metrics.push(...parsed)
|
||||
if (len > maxEvents) {
|
||||
this.metrics.splice(0, len - maxEvents)
|
||||
}
|
||||
}
|
||||
async GetUsageMetrics(): Promise<Types.UsageMetrics> {
|
||||
return {
|
||||
metrics: this.metrics
|
||||
}
|
||||
}
|
||||
async GetAppsMetrics(req: Types.AppsMetricsRequest): Promise<Types.AppsMetrics> {
|
||||
const dbApps = await this.storage.applicationStorage.GetApplications()
|
||||
const apps = await Promise.all(dbApps.map(app => this.GetAppMetrics(req, app)))
|
||||
const unlinked = await this.GetAppMetrics(req, null)
|
||||
apps.push(unlinked)
|
||||
return {
|
||||
apps
|
||||
}
|
||||
}
|
||||
|
||||
async GetAppMetrics(req: Types.AppsMetricsRequest, app: Application | null): Promise<Types.AppMetrics> {
|
||||
const totalFees = await this.storage.paymentStorage.GetTotalFeesPaidInApp(app)
|
||||
const { receivingInvoices, receivingTransactions, outgoingInvoices, outgoingTransactions, receivingAddresses, userToUser } = await this.storage.paymentStorage.GetAppOperations(app, { from: req.from_unix, to: req.to_unix })
|
||||
let totalReceived = 0
|
||||
let totalSpent = 0
|
||||
let unpaidInvoices = 0
|
||||
let feesInRange = 0
|
||||
const operations: Types.UserOperation[] = []
|
||||
receivingInvoices.forEach(i => {
|
||||
if (i.paid_at_unix > 0) {
|
||||
totalReceived += i.paid_amount
|
||||
feesInRange += i.service_fee
|
||||
if (req.include_operations) operations.push({ type: Types.UserOperationType.INCOMING_INVOICE, amount: i.paid_amount, inbound: true, paidAtUnix: i.paid_at_unix, confirmed: true, service_fee: i.service_fee, network_fee: 0, identifier: "", operationId: "", tx_hash: "", internal: i.internal })
|
||||
} else {
|
||||
unpaidInvoices++
|
||||
}
|
||||
})
|
||||
receivingTransactions.forEach(txs => {
|
||||
txs.forEach(tx => {
|
||||
if (req.include_operations) operations.push({ type: Types.UserOperationType.INCOMING_TX, amount: tx.paid_amount, inbound: true, paidAtUnix: tx.paid_at_unix, confirmed: tx.confs > 1, service_fee: tx.service_fee, network_fee: 0, identifier: "", operationId: "", tx_hash: tx.tx_hash, internal: tx.internal })
|
||||
if (tx.confs > 1) {
|
||||
feesInRange += tx.service_fee
|
||||
totalReceived += tx.paid_amount
|
||||
}
|
||||
})
|
||||
})
|
||||
outgoingInvoices.forEach(i => {
|
||||
if (req.include_operations) operations.push({ type: Types.UserOperationType.OUTGOING_INVOICE, amount: i.paid_amount, inbound: false, paidAtUnix: i.paid_at_unix, confirmed: true, service_fee: i.service_fees, network_fee: i.routing_fees, identifier: "", operationId: "", tx_hash: "", internal: i.internal })
|
||||
totalSpent += i.paid_amount
|
||||
feesInRange += i.service_fees
|
||||
})
|
||||
outgoingTransactions.forEach(tx => {
|
||||
if (req.include_operations) operations.push({ type: Types.UserOperationType.OUTGOING_TX, amount: tx.paid_amount, inbound: false, paidAtUnix: tx.paid_at_unix, confirmed: tx.confs > 1, service_fee: tx.service_fees, network_fee: tx.chain_fees, identifier: "", operationId: "", tx_hash: tx.tx_hash, internal: tx.internal })
|
||||
totalSpent += tx.paid_amount
|
||||
feesInRange += tx.service_fees
|
||||
})
|
||||
|
||||
userToUser.forEach(op => {
|
||||
if (req.include_operations) operations.push({ type: Types.UserOperationType.INCOMING_USER_TO_USER, amount: op.paid_amount, inbound: true, paidAtUnix: op.paid_at_unix, confirmed: true, service_fee: op.service_fees, network_fee: 0, identifier: "", operationId: "", tx_hash: "", internal: true })
|
||||
feesInRange += op.service_fees
|
||||
})
|
||||
|
||||
const users = await this.storage.applicationStorage.GetApplicationUsers(app, { from: req.from_unix, to: req.to_unix })
|
||||
|
||||
let totalUserWithBalance = 0
|
||||
let totalUserWithNoBalance = 0
|
||||
let totalUsersWithNegativeBalance = 0
|
||||
let totalAlwaysBeenInactive = 0
|
||||
let balanceSum = 0
|
||||
let minBalance = Number.MAX_SAFE_INTEGER
|
||||
let maxBalance = 0
|
||||
await Promise.all(users.map(async u => {
|
||||
if (u.user.balance_sats < 0) {
|
||||
totalUsersWithNegativeBalance++
|
||||
} else if (u.user.balance_sats === 0) {
|
||||
const wasActive = await this.storage.paymentStorage.UserHasOutgoingOperation(u.user.user_id)
|
||||
totalUserWithNoBalance++
|
||||
if (!wasActive) {
|
||||
totalAlwaysBeenInactive++
|
||||
}
|
||||
} else {
|
||||
balanceSum += u.user.balance_sats
|
||||
totalUserWithBalance++
|
||||
if (u.user.balance_sats < minBalance) {
|
||||
minBalance = u.user.balance_sats
|
||||
}
|
||||
if (u.user.balance_sats > maxBalance) {
|
||||
maxBalance = u.user.balance_sats
|
||||
}
|
||||
}
|
||||
}))
|
||||
return {
|
||||
app: {
|
||||
name: app ? app.name : "unlinked to app",
|
||||
id: app ? app.app_id : "unlinked",
|
||||
npub: app ? (app.nostr_public_key || "") : "",
|
||||
balance: app ? app.owner.balance_sats : 0,
|
||||
},
|
||||
users: {
|
||||
total: users.length,
|
||||
always_been_inactive: totalAlwaysBeenInactive,
|
||||
balance_avg: Math.round(balanceSum / totalUserWithBalance),
|
||||
balance_median: Math.round((maxBalance + minBalance) / 2),
|
||||
no_balance: totalUserWithNoBalance,
|
||||
negative_balance: totalUsersWithNegativeBalance,
|
||||
},
|
||||
|
||||
received: totalReceived,
|
||||
spent: totalSpent,
|
||||
available: balanceSum,
|
||||
fees: feesInRange,
|
||||
total_fees: totalFees,
|
||||
invoices: receivingInvoices.length,
|
||||
|
||||
operations
|
||||
}
|
||||
}
|
||||
|
||||
async GetChannelsInfo() {
|
||||
const { channels } = await this.lnd.ListChannels()
|
||||
let totalActive = 0
|
||||
let totalInactive = 0
|
||||
channels.forEach(c => {
|
||||
if (c.active) {
|
||||
totalActive++
|
||||
} else {
|
||||
totalInactive++
|
||||
}
|
||||
})
|
||||
return {
|
||||
totalActive, totalInactive, openChannels: channels
|
||||
}
|
||||
}
|
||||
async GetPendingChannelsInfo() {
|
||||
const { pendingForceClosingChannels, pendingOpenChannels } = await this.lnd.ListPendingChannels()
|
||||
return { totalPendingClose: pendingForceClosingChannels.length, totalPendingOpen: pendingOpenChannels.length }
|
||||
|
||||
}
|
||||
|
||||
|
||||
async GetLndMetrics(req: Types.LndMetricsRequest): Promise<Types.LndMetrics> {
|
||||
const { openChannels, totalActive, totalInactive } = await this.GetChannelsInfo()
|
||||
const { totalPendingOpen, totalPendingClose } = await this.GetPendingChannelsInfo()
|
||||
const { channels: closedChannels } = await this.lnd.ListClosedChannels()
|
||||
const rawRouting = await this.storage.metricsStorage.GetChannelRouting({ from: req.from_unix, to: req.to_unix })
|
||||
const routingMap: Record<string, Types.ChannelRouting> = {}
|
||||
rawRouting.forEach(r => {
|
||||
if (!routingMap[r.channel_id]) {
|
||||
routingMap[r.channel_id] = {
|
||||
channel_id: r.channel_id,
|
||||
send_errors: 0,
|
||||
receive_errors: 0,
|
||||
forward_errors_as_input: 0,
|
||||
forward_errors_as_output: 0,
|
||||
missed_forward_fee_as_input: 0,
|
||||
missed_forward_fee_as_output: 0,
|
||||
forward_fee_as_input: 0,
|
||||
forward_fee_as_output: 0,
|
||||
events_number: 0
|
||||
}
|
||||
}
|
||||
routingMap[r.channel_id].send_errors += r.send_errors
|
||||
routingMap[r.channel_id].receive_errors += r.receive_errors
|
||||
routingMap[r.channel_id].forward_errors_as_input += r.forward_errors_as_input
|
||||
routingMap[r.channel_id].forward_errors_as_output += r.forward_errors_as_output
|
||||
routingMap[r.channel_id].missed_forward_fee_as_input += r.missed_forward_fee_as_input
|
||||
routingMap[r.channel_id].missed_forward_fee_as_output += r.missed_forward_fee_as_output
|
||||
routingMap[r.channel_id].forward_fee_as_input += r.forward_fee_as_input
|
||||
routingMap[r.channel_id].forward_fee_as_output += r.forward_fee_as_output
|
||||
routingMap[r.channel_id].events_number++
|
||||
})
|
||||
const { channelsBalanceEvents, chainBalanceEvents } = await this.storage.metricsStorage.GetBalanceEvents({ from: req.from_unix, to: req.to_unix })
|
||||
return {
|
||||
nodes: [{
|
||||
chain_balance_events: chainBalanceEvents.map(e => ({
|
||||
block_height: e.block_height,
|
||||
confirmed_balance: e.confirmed_chain_balance,
|
||||
unconfirmed_balance: e.unconfirmed_chain_balance,
|
||||
total_balance: e.total_chain_balance
|
||||
})),
|
||||
channels_balance_events: channelsBalanceEvents.map(e => ({
|
||||
block_height: e.balance_event.block_height,
|
||||
channel_id: e.channel_id,
|
||||
local_balance_sats: e.local_balance_sats,
|
||||
remote_balance_sats: e.remote_balance_sats
|
||||
})),
|
||||
closing_channels: totalPendingClose,
|
||||
pending_channels: totalPendingOpen,
|
||||
offline_channels: totalInactive,
|
||||
online_channels: totalActive,
|
||||
closed_channels: closedChannels.map(c => ({ capacity: Number(c.capacity), channel_id: c.chanId, closed_height: c.closeHeight })),
|
||||
open_channels: openChannels.map(c => ({ active: c.active, capacity: Number(c.capacity), channel_id: c.chanId, lifetime: Number(c.lifetime), local_balance: Number(c.localBalance), remote_balance: Number(c.remoteBalance) })),
|
||||
channel_routing: Object.values(routingMap)
|
||||
}],
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,226 +1,226 @@
|
|||
//import { SimplePool, Sub, Event, UnsignedEvent, getEventHash, signEvent } from 'nostr-tools'
|
||||
import { SimplePool, Sub, Event, UnsignedEvent, getEventHash, finishEvent, relayInit } from './tools/index.js'
|
||||
import { encryptData, decryptData, getSharedSecret, decodePayload, encodePayload } from './nip44.js'
|
||||
import { ERROR, getLogger } from '../helpers/logger.js'
|
||||
import { encodeNprofile } from '../../custom-nip19.js'
|
||||
const handledEvents: string[] = [] // TODO: - big memory leak here, add TTL
|
||||
type AppInfo = { appId: string, publicKey: string, privateKey: string, name: string }
|
||||
type ClientInfo = { clientId: string, publicKey: string, privateKey: string, name: string }
|
||||
export type SendData = { type: "content", content: string, pub: string } | { type: "event", event: UnsignedEvent }
|
||||
export type SendInitiator = { type: 'app', appId: string } | { type: 'client', clientId: string }
|
||||
export type NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => void
|
||||
|
||||
export type NostrSettings = {
|
||||
apps: AppInfo[]
|
||||
relays: string[]
|
||||
clients: ClientInfo[]
|
||||
}
|
||||
export type NostrEvent = {
|
||||
id: string
|
||||
pub: string
|
||||
content: string
|
||||
appId: string
|
||||
startAtNano: string
|
||||
startAtMs: number
|
||||
}
|
||||
|
||||
type SettingsRequest = {
|
||||
type: 'settings'
|
||||
settings: NostrSettings
|
||||
}
|
||||
|
||||
type SendRequest = {
|
||||
type: 'send'
|
||||
initiator: SendInitiator
|
||||
data: SendData
|
||||
relays?: string[]
|
||||
}
|
||||
type ReadyResponse = {
|
||||
type: 'ready'
|
||||
}
|
||||
type EventResponse = {
|
||||
type: 'event'
|
||||
event: NostrEvent
|
||||
}
|
||||
|
||||
export type ChildProcessRequest = SettingsRequest | SendRequest
|
||||
export type ChildProcessResponse = ReadyResponse | EventResponse
|
||||
const send = (message: ChildProcessResponse) => {
|
||||
if (process.send) {
|
||||
process.send(message)
|
||||
}
|
||||
}
|
||||
let subProcessHandler: Handler | undefined
|
||||
process.on("message", (message: ChildProcessRequest) => {
|
||||
switch (message.type) {
|
||||
case 'settings':
|
||||
initSubprocessHandler(message.settings)
|
||||
break
|
||||
case 'send':
|
||||
sendToNostr(message.initiator, message.data, message.relays)
|
||||
break
|
||||
default:
|
||||
getLogger({ component: "nostrMiddleware" })(ERROR, "unknown nostr request", message)
|
||||
break
|
||||
}
|
||||
})
|
||||
const initSubprocessHandler = (settings: NostrSettings) => {
|
||||
if (subProcessHandler) {
|
||||
getLogger({ component: "nostrMiddleware" })(ERROR, "nostr settings ignored since handler already exists")
|
||||
return
|
||||
}
|
||||
subProcessHandler = new Handler(settings, event => {
|
||||
send({
|
||||
type: 'event',
|
||||
event: event
|
||||
})
|
||||
})
|
||||
}
|
||||
const sendToNostr: NostrSend = (initiator, data, relays) => {
|
||||
if (!subProcessHandler) {
|
||||
getLogger({ component: "nostrMiddleware" })(ERROR, "nostr was not initialized")
|
||||
return
|
||||
}
|
||||
subProcessHandler.Send(initiator, data, relays)
|
||||
}
|
||||
send({ type: 'ready' })
|
||||
|
||||
export default class Handler {
|
||||
pool = new SimplePool()
|
||||
settings: NostrSettings
|
||||
subs: Sub[] = []
|
||||
apps: Record<string, AppInfo> = {}
|
||||
eventCallback: (event: NostrEvent) => void
|
||||
log = getLogger({ component: "nostrMiddleware" })
|
||||
constructor(settings: NostrSettings, eventCallback: (event: NostrEvent) => void) {
|
||||
this.settings = settings
|
||||
this.log(
|
||||
{
|
||||
...settings,
|
||||
apps: settings.apps.map(app => {
|
||||
const { privateKey, ...rest } = app;
|
||||
return {
|
||||
...rest,
|
||||
nprofile: encodeNprofile({ pubkey: rest.publicKey, relays: settings.relays })
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
this.eventCallback = eventCallback
|
||||
this.settings.apps.forEach(app => {
|
||||
this.apps[app.publicKey] = app
|
||||
})
|
||||
this.Connect()
|
||||
}
|
||||
|
||||
async Connect() {
|
||||
const log = getLogger({})
|
||||
log("conneting to relay...", this.settings.relays[0])
|
||||
const relay = relayInit(this.settings.relays[0]) // TODO: create multiple conns for multiple relays
|
||||
try {
|
||||
await relay.connect()
|
||||
} catch (err) {
|
||||
log("failed to connect to relay, will try again in 2 seconds")
|
||||
setTimeout(() => {
|
||||
this.Connect()
|
||||
}, 2000)
|
||||
return
|
||||
}
|
||||
log("connected, subbing...")
|
||||
relay.on('disconnect', () => {
|
||||
log("relay disconnected, will try to reconnect")
|
||||
relay.close()
|
||||
this.Connect()
|
||||
})
|
||||
const sub = relay.sub([
|
||||
{
|
||||
since: Math.ceil(Date.now() / 1000),
|
||||
kinds: [21000],
|
||||
'#p': Object.keys(this.apps),
|
||||
}
|
||||
])
|
||||
sub.on('eose', () => {
|
||||
log("up to date with nostr events")
|
||||
})
|
||||
sub.on('event', async (e) => {
|
||||
if (e.kind !== 21000 || !e.pubkey) {
|
||||
return
|
||||
}
|
||||
const pubTags = e.tags.find(tags => tags && tags.length > 1 && tags[0] === 'p')
|
||||
if (!pubTags) {
|
||||
return
|
||||
}
|
||||
const app = this.apps[pubTags[1]]
|
||||
if (app) {
|
||||
await this.processEvent(e, app)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async processEvent(e: Event<21000>, app: AppInfo) {
|
||||
const eventId = e.id
|
||||
if (handledEvents.includes(eventId)) {
|
||||
this.log("event already handled")
|
||||
return
|
||||
}
|
||||
handledEvents.push(eventId)
|
||||
const startAtMs = Date.now()
|
||||
const startAtNano = process.hrtime.bigint().toString()
|
||||
const decoded = decodePayload(e.content)
|
||||
const content = await decryptData(decoded, getSharedSecret(app.privateKey, e.pubkey))
|
||||
this.eventCallback({ id: eventId, content, pub: e.pubkey, appId: app.appId, startAtNano, startAtMs })
|
||||
}
|
||||
|
||||
async Send(initiator: SendInitiator, data: SendData, relays?: string[]) {
|
||||
const keys = this.GetSendKeys(initiator)
|
||||
let toSign: UnsignedEvent
|
||||
if (data.type === 'content') {
|
||||
const decoded = await encryptData(data.content, getSharedSecret(keys.privateKey, data.pub))
|
||||
const content = encodePayload(decoded)
|
||||
toSign = {
|
||||
content,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 21000,
|
||||
pubkey: keys.publicKey,
|
||||
tags: [['p', data.pub]],
|
||||
}
|
||||
} else {
|
||||
toSign = data.event
|
||||
}
|
||||
|
||||
const signed = finishEvent(toSign, keys.privateKey)
|
||||
let sent = false
|
||||
const log = getLogger({ appName: keys.name })
|
||||
await Promise.all(this.pool.publish(relays || this.settings.relays, signed).map(async p => {
|
||||
try {
|
||||
await p
|
||||
sent = true
|
||||
} catch (e: any) {
|
||||
log(e)
|
||||
}
|
||||
}))
|
||||
if (!sent) {
|
||||
log("failed to send event")
|
||||
}
|
||||
}
|
||||
|
||||
GetSendKeys(initiator: SendInitiator) {
|
||||
if (initiator.type === 'app') {
|
||||
const { appId } = initiator
|
||||
const found = this.settings.apps.find((info: AppInfo) => info.appId === appId)
|
||||
if (!found) {
|
||||
throw new Error("unkown app")
|
||||
}
|
||||
return found
|
||||
} else if (initiator.type === 'client') {
|
||||
const { clientId } = initiator
|
||||
const found = this.settings.clients.find((info: ClientInfo) => info.clientId === clientId)
|
||||
if (!found) {
|
||||
throw new Error("unkown client")
|
||||
}
|
||||
return found
|
||||
}
|
||||
throw new Error("unkown initiator type")
|
||||
}
|
||||
//import { SimplePool, Sub, Event, UnsignedEvent, getEventHash, signEvent } from 'nostr-tools'
|
||||
import { SimplePool, Sub, Event, UnsignedEvent, getEventHash, finishEvent, relayInit } from './tools/index.js'
|
||||
import { encryptData, decryptData, getSharedSecret, decodePayload, encodePayload } from './nip44.js'
|
||||
import { ERROR, getLogger } from '../helpers/logger.js'
|
||||
import { encodeNprofile } from '../../custom-nip19.js'
|
||||
const handledEvents: string[] = [] // TODO: - big memory leak here, add TTL
|
||||
type AppInfo = { appId: string, publicKey: string, privateKey: string, name: string }
|
||||
type ClientInfo = { clientId: string, publicKey: string, privateKey: string, name: string }
|
||||
export type SendData = { type: "content", content: string, pub: string } | { type: "event", event: UnsignedEvent }
|
||||
export type SendInitiator = { type: 'app', appId: string } | { type: 'client', clientId: string }
|
||||
export type NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => void
|
||||
|
||||
export type NostrSettings = {
|
||||
apps: AppInfo[]
|
||||
relays: string[]
|
||||
clients: ClientInfo[]
|
||||
}
|
||||
export type NostrEvent = {
|
||||
id: string
|
||||
pub: string
|
||||
content: string
|
||||
appId: string
|
||||
startAtNano: string
|
||||
startAtMs: number
|
||||
}
|
||||
|
||||
type SettingsRequest = {
|
||||
type: 'settings'
|
||||
settings: NostrSettings
|
||||
}
|
||||
|
||||
type SendRequest = {
|
||||
type: 'send'
|
||||
initiator: SendInitiator
|
||||
data: SendData
|
||||
relays?: string[]
|
||||
}
|
||||
type ReadyResponse = {
|
||||
type: 'ready'
|
||||
}
|
||||
type EventResponse = {
|
||||
type: 'event'
|
||||
event: NostrEvent
|
||||
}
|
||||
|
||||
export type ChildProcessRequest = SettingsRequest | SendRequest
|
||||
export type ChildProcessResponse = ReadyResponse | EventResponse
|
||||
const send = (message: ChildProcessResponse) => {
|
||||
if (process.send) {
|
||||
process.send(message)
|
||||
}
|
||||
}
|
||||
let subProcessHandler: Handler | undefined
|
||||
process.on("message", (message: ChildProcessRequest) => {
|
||||
switch (message.type) {
|
||||
case 'settings':
|
||||
initSubprocessHandler(message.settings)
|
||||
break
|
||||
case 'send':
|
||||
sendToNostr(message.initiator, message.data, message.relays)
|
||||
break
|
||||
default:
|
||||
getLogger({ component: "nostrMiddleware" })(ERROR, "unknown nostr request", message)
|
||||
break
|
||||
}
|
||||
})
|
||||
const initSubprocessHandler = (settings: NostrSettings) => {
|
||||
if (subProcessHandler) {
|
||||
getLogger({ component: "nostrMiddleware" })(ERROR, "nostr settings ignored since handler already exists")
|
||||
return
|
||||
}
|
||||
subProcessHandler = new Handler(settings, event => {
|
||||
send({
|
||||
type: 'event',
|
||||
event: event
|
||||
})
|
||||
})
|
||||
}
|
||||
const sendToNostr: NostrSend = (initiator, data, relays) => {
|
||||
if (!subProcessHandler) {
|
||||
getLogger({ component: "nostrMiddleware" })(ERROR, "nostr was not initialized")
|
||||
return
|
||||
}
|
||||
subProcessHandler.Send(initiator, data, relays)
|
||||
}
|
||||
send({ type: 'ready' })
|
||||
|
||||
export default class Handler {
|
||||
pool = new SimplePool()
|
||||
settings: NostrSettings
|
||||
subs: Sub[] = []
|
||||
apps: Record<string, AppInfo> = {}
|
||||
eventCallback: (event: NostrEvent) => void
|
||||
log = getLogger({ component: "nostrMiddleware" })
|
||||
constructor(settings: NostrSettings, eventCallback: (event: NostrEvent) => void) {
|
||||
this.settings = settings
|
||||
this.log(
|
||||
{
|
||||
...settings,
|
||||
apps: settings.apps.map(app => {
|
||||
const { privateKey, ...rest } = app;
|
||||
return {
|
||||
...rest,
|
||||
nprofile: encodeNprofile({ pubkey: rest.publicKey, relays: settings.relays })
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
this.eventCallback = eventCallback
|
||||
this.settings.apps.forEach(app => {
|
||||
this.apps[app.publicKey] = app
|
||||
})
|
||||
this.Connect()
|
||||
}
|
||||
|
||||
async Connect() {
|
||||
const log = getLogger({})
|
||||
log("conneting to relay...", this.settings.relays[0])
|
||||
const relay = relayInit(this.settings.relays[0]) // TODO: create multiple conns for multiple relays
|
||||
try {
|
||||
await relay.connect()
|
||||
} catch (err) {
|
||||
log("failed to connect to relay, will try again in 2 seconds")
|
||||
setTimeout(() => {
|
||||
this.Connect()
|
||||
}, 2000)
|
||||
return
|
||||
}
|
||||
log("connected, subbing...")
|
||||
relay.on('disconnect', () => {
|
||||
log("relay disconnected, will try to reconnect")
|
||||
relay.close()
|
||||
this.Connect()
|
||||
})
|
||||
const sub = relay.sub([
|
||||
{
|
||||
since: Math.ceil(Date.now() / 1000),
|
||||
kinds: [21000],
|
||||
'#p': Object.keys(this.apps),
|
||||
}
|
||||
])
|
||||
sub.on('eose', () => {
|
||||
log("up to date with nostr events")
|
||||
})
|
||||
sub.on('event', async (e) => {
|
||||
if (e.kind !== 21000 || !e.pubkey) {
|
||||
return
|
||||
}
|
||||
const pubTags = e.tags.find(tags => tags && tags.length > 1 && tags[0] === 'p')
|
||||
if (!pubTags) {
|
||||
return
|
||||
}
|
||||
const app = this.apps[pubTags[1]]
|
||||
if (app) {
|
||||
await this.processEvent(e, app)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async processEvent(e: Event<21000>, app: AppInfo) {
|
||||
const eventId = e.id
|
||||
if (handledEvents.includes(eventId)) {
|
||||
this.log("event already handled")
|
||||
return
|
||||
}
|
||||
handledEvents.push(eventId)
|
||||
const startAtMs = Date.now()
|
||||
const startAtNano = process.hrtime.bigint().toString()
|
||||
const decoded = decodePayload(e.content)
|
||||
const content = await decryptData(decoded, getSharedSecret(app.privateKey, e.pubkey))
|
||||
this.eventCallback({ id: eventId, content, pub: e.pubkey, appId: app.appId, startAtNano, startAtMs })
|
||||
}
|
||||
|
||||
async Send(initiator: SendInitiator, data: SendData, relays?: string[]) {
|
||||
const keys = this.GetSendKeys(initiator)
|
||||
let toSign: UnsignedEvent
|
||||
if (data.type === 'content') {
|
||||
const decoded = await encryptData(data.content, getSharedSecret(keys.privateKey, data.pub))
|
||||
const content = encodePayload(decoded)
|
||||
toSign = {
|
||||
content,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 21000,
|
||||
pubkey: keys.publicKey,
|
||||
tags: [['p', data.pub]],
|
||||
}
|
||||
} else {
|
||||
toSign = data.event
|
||||
}
|
||||
|
||||
const signed = finishEvent(toSign, keys.privateKey)
|
||||
let sent = false
|
||||
const log = getLogger({ appName: keys.name })
|
||||
await Promise.all(this.pool.publish(relays || this.settings.relays, signed).map(async p => {
|
||||
try {
|
||||
await p
|
||||
sent = true
|
||||
} catch (e: any) {
|
||||
log(e)
|
||||
}
|
||||
}))
|
||||
if (!sent) {
|
||||
log("failed to send event")
|
||||
}
|
||||
}
|
||||
|
||||
GetSendKeys(initiator: SendInitiator) {
|
||||
if (initiator.type === 'app') {
|
||||
const { appId } = initiator
|
||||
const found = this.settings.apps.find((info: AppInfo) => info.appId === appId)
|
||||
if (!found) {
|
||||
throw new Error("unkown app")
|
||||
}
|
||||
return found
|
||||
} else if (initiator.type === 'client') {
|
||||
const { clientId } = initiator
|
||||
const found = this.settings.clients.find((info: ClientInfo) => info.clientId === clientId)
|
||||
if (!found) {
|
||||
throw new Error("unkown client")
|
||||
}
|
||||
return found
|
||||
}
|
||||
throw new Error("unkown initiator type")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +1,47 @@
|
|||
import { ChildProcess, fork } from 'child_process'
|
||||
import { EnvMustBeNonEmptyString } from "../helpers/envParser.js"
|
||||
import { NostrSettings, NostrEvent, ChildProcessRequest, ChildProcessResponse, SendData, SendInitiator } from "./handler.js"
|
||||
type EventCallback = (event: NostrEvent) => void
|
||||
|
||||
const getEnvOrDefault = (name: string, defaultValue: string): string => {
|
||||
return process.env[name] || defaultValue;
|
||||
}
|
||||
|
||||
export const LoadNosrtSettingsFromEnv = (test = false) => {
|
||||
const relaysEnv = getEnvOrDefault("NOSTR_RELAYS", "wss://strfry.shock.network");
|
||||
return {
|
||||
relays: relaysEnv.split(' ')
|
||||
}
|
||||
}
|
||||
|
||||
export default class NostrSubprocess {
|
||||
settings: NostrSettings
|
||||
childProcess: ChildProcess
|
||||
constructor(settings: NostrSettings, eventCallback: EventCallback) {
|
||||
this.childProcess = fork("./build/src/services/nostr/handler")
|
||||
this.childProcess.on("error", console.error)
|
||||
this.childProcess.on("message", (message: ChildProcessResponse) => {
|
||||
switch (message.type) {
|
||||
case 'ready':
|
||||
this.sendToChildProcess({ type: 'settings', settings: settings })
|
||||
break;
|
||||
case 'event':
|
||||
eventCallback(message.event)
|
||||
break
|
||||
default:
|
||||
console.error("unknown nostr event response", message)
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
sendToChildProcess(message: ChildProcessRequest) {
|
||||
this.childProcess.send(message)
|
||||
}
|
||||
|
||||
Send(initiator: SendInitiator, data: SendData, relays?: string[]) {
|
||||
this.sendToChildProcess({ type: 'send', data, initiator, relays })
|
||||
}
|
||||
Stop() {
|
||||
this.childProcess.kill()
|
||||
}
|
||||
}
|
||||
import { ChildProcess, fork } from 'child_process'
|
||||
import { EnvMustBeNonEmptyString } from "../helpers/envParser.js"
|
||||
import { NostrSettings, NostrEvent, ChildProcessRequest, ChildProcessResponse, SendData, SendInitiator } from "./handler.js"
|
||||
type EventCallback = (event: NostrEvent) => void
|
||||
|
||||
const getEnvOrDefault = (name: string, defaultValue: string): string => {
|
||||
return process.env[name] || defaultValue;
|
||||
}
|
||||
|
||||
export const LoadNosrtSettingsFromEnv = (test = false) => {
|
||||
const relaysEnv = getEnvOrDefault("NOSTR_RELAYS", "wss://strfry.shock.network");
|
||||
return {
|
||||
relays: relaysEnv.split(' ')
|
||||
}
|
||||
}
|
||||
|
||||
export default class NostrSubprocess {
|
||||
settings: NostrSettings
|
||||
childProcess: ChildProcess
|
||||
constructor(settings: NostrSettings, eventCallback: EventCallback) {
|
||||
this.childProcess = fork("./build/src/services/nostr/handler")
|
||||
this.childProcess.on("error", console.error)
|
||||
this.childProcess.on("message", (message: ChildProcessResponse) => {
|
||||
switch (message.type) {
|
||||
case 'ready':
|
||||
this.sendToChildProcess({ type: 'settings', settings: settings })
|
||||
break;
|
||||
case 'event':
|
||||
eventCallback(message.event)
|
||||
break
|
||||
default:
|
||||
console.error("unknown nostr event response", message)
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
sendToChildProcess(message: ChildProcessRequest) {
|
||||
this.childProcess.send(message)
|
||||
}
|
||||
|
||||
Send(initiator: SendInitiator, data: SendData, relays?: string[]) {
|
||||
this.sendToChildProcess({ type: 'send', data, initiator, relays })
|
||||
}
|
||||
Stop() {
|
||||
this.childProcess.kill()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,55 @@
|
|||
import { base64 } from "@scure/base";
|
||||
import { randomBytes } from "@noble/hashes/utils";
|
||||
import { streamXOR as xchacha20 } from "@stablelib/xchacha20";
|
||||
import { secp256k1 } from "@noble/curves/secp256k1";
|
||||
import { sha256 } from "@noble/hashes/sha256";
|
||||
type EncryptedData = {
|
||||
ciphertext: Uint8Array;
|
||||
nonce: Uint8Array;
|
||||
}
|
||||
export const getSharedSecret = (privateKey: string, publicKey: string) => {
|
||||
const key = secp256k1.getSharedSecret(privateKey, "02" + publicKey);
|
||||
return sha256(key.slice(1, 33));
|
||||
}
|
||||
|
||||
export const encryptData = (content: string, sharedSecret: Uint8Array) => {
|
||||
const nonce = randomBytes(24);
|
||||
const plaintext = new TextEncoder().encode(content);
|
||||
const ciphertext = xchacha20(sharedSecret, nonce, plaintext, plaintext);
|
||||
return {
|
||||
ciphertext: Uint8Array.from(ciphertext),
|
||||
nonce: nonce,
|
||||
} as EncryptedData;
|
||||
}
|
||||
|
||||
export const decryptData = (payload: EncryptedData, sharedSecret: Uint8Array) => {
|
||||
const dst = xchacha20(sharedSecret, payload.nonce, payload.ciphertext, payload.ciphertext);
|
||||
const decoded = new TextDecoder().decode(dst);
|
||||
return decoded;
|
||||
}
|
||||
const xchacha20EncryptionVersion = 1
|
||||
export const decodePayload = (p: string) => {
|
||||
if (p.startsWith("{") && p.endsWith("}")) {
|
||||
const pj = JSON.parse(p) as { v: number; nonce: string; ciphertext: string };
|
||||
if (pj.v !== xchacha20EncryptionVersion) {
|
||||
throw new Error("Encryption version unsupported")
|
||||
}
|
||||
return {
|
||||
nonce: base64.decode(pj.nonce),
|
||||
ciphertext: base64.decode(pj.ciphertext),
|
||||
} as EncryptedData;
|
||||
} else {
|
||||
const buf = base64.decode(p);
|
||||
if (buf[0] !== xchacha20EncryptionVersion) {
|
||||
throw new Error("Encryption version unsupported")
|
||||
}
|
||||
return {
|
||||
nonce: buf.subarray(1, 25),
|
||||
ciphertext: buf.subarray(25),
|
||||
} as EncryptedData;
|
||||
}
|
||||
}
|
||||
|
||||
export const encodePayload = (p: EncryptedData) => {
|
||||
return base64.encode(new Uint8Array([xchacha20EncryptionVersion, ...p.nonce, ...p.ciphertext]));
|
||||
import { base64 } from "@scure/base";
|
||||
import { randomBytes } from "@noble/hashes/utils";
|
||||
import { streamXOR as xchacha20 } from "@stablelib/xchacha20";
|
||||
import { secp256k1 } from "@noble/curves/secp256k1";
|
||||
import { sha256 } from "@noble/hashes/sha256";
|
||||
type EncryptedData = {
|
||||
ciphertext: Uint8Array;
|
||||
nonce: Uint8Array;
|
||||
}
|
||||
export const getSharedSecret = (privateKey: string, publicKey: string) => {
|
||||
const key = secp256k1.getSharedSecret(privateKey, "02" + publicKey);
|
||||
return sha256(key.slice(1, 33));
|
||||
}
|
||||
|
||||
export const encryptData = (content: string, sharedSecret: Uint8Array) => {
|
||||
const nonce = randomBytes(24);
|
||||
const plaintext = new TextEncoder().encode(content);
|
||||
const ciphertext = xchacha20(sharedSecret, nonce, plaintext, plaintext);
|
||||
return {
|
||||
ciphertext: Uint8Array.from(ciphertext),
|
||||
nonce: nonce,
|
||||
} as EncryptedData;
|
||||
}
|
||||
|
||||
export const decryptData = (payload: EncryptedData, sharedSecret: Uint8Array) => {
|
||||
const dst = xchacha20(sharedSecret, payload.nonce, payload.ciphertext, payload.ciphertext);
|
||||
const decoded = new TextDecoder().decode(dst);
|
||||
return decoded;
|
||||
}
|
||||
const xchacha20EncryptionVersion = 1
|
||||
export const decodePayload = (p: string) => {
|
||||
if (p.startsWith("{") && p.endsWith("}")) {
|
||||
const pj = JSON.parse(p) as { v: number; nonce: string; ciphertext: string };
|
||||
if (pj.v !== xchacha20EncryptionVersion) {
|
||||
throw new Error("Encryption version unsupported")
|
||||
}
|
||||
return {
|
||||
nonce: base64.decode(pj.nonce),
|
||||
ciphertext: base64.decode(pj.ciphertext),
|
||||
} as EncryptedData;
|
||||
} else {
|
||||
const buf = base64.decode(p);
|
||||
if (buf[0] !== xchacha20EncryptionVersion) {
|
||||
throw new Error("Encryption version unsupported")
|
||||
}
|
||||
return {
|
||||
nonce: buf.subarray(1, 25),
|
||||
ciphertext: buf.subarray(25),
|
||||
} as EncryptedData;
|
||||
}
|
||||
}
|
||||
|
||||
export const encodePayload = (p: EncryptedData) => {
|
||||
return base64.encode(new Uint8Array([xchacha20EncryptionVersion, ...p.nonce, ...p.ciphertext]));
|
||||
}
|
||||
|
|
@ -1,144 +1,144 @@
|
|||
import { schnorr } from '@noble/curves/secp256k1'
|
||||
import { sha256 } from '@noble/hashes/sha256'
|
||||
import { bytesToHex } from '@noble/hashes/utils'
|
||||
|
||||
import { getPublicKey } from './keys.js'
|
||||
import { utf8Encoder } from './utils.js'
|
||||
|
||||
/** Designates a verified event signature. */
|
||||
export const verifiedSymbol = Symbol('verified')
|
||||
|
||||
/** @deprecated Use numbers instead. */
|
||||
/* eslint-disable no-unused-vars */
|
||||
export enum Kind {
|
||||
Metadata = 0,
|
||||
Text = 1,
|
||||
RecommendRelay = 2,
|
||||
Contacts = 3,
|
||||
EncryptedDirectMessage = 4,
|
||||
EventDeletion = 5,
|
||||
Repost = 6,
|
||||
Reaction = 7,
|
||||
BadgeAward = 8,
|
||||
ChannelCreation = 40,
|
||||
ChannelMetadata = 41,
|
||||
ChannelMessage = 42,
|
||||
ChannelHideMessage = 43,
|
||||
ChannelMuteUser = 44,
|
||||
Blank = 255,
|
||||
Report = 1984,
|
||||
ZapRequest = 9734,
|
||||
Zap = 9735,
|
||||
RelayList = 10002,
|
||||
ClientAuth = 22242,
|
||||
HttpAuth = 27235,
|
||||
ProfileBadge = 30008,
|
||||
BadgeDefinition = 30009,
|
||||
Article = 30023,
|
||||
FileMetadata = 1063,
|
||||
}
|
||||
|
||||
export interface Event<K extends number = number> {
|
||||
kind: K
|
||||
tags: string[][]
|
||||
content: string
|
||||
created_at: number
|
||||
pubkey: string
|
||||
id: string
|
||||
sig: string
|
||||
[verifiedSymbol]?: boolean
|
||||
}
|
||||
|
||||
export type EventTemplate<K extends number = number> = Pick<Event<K>, 'kind' | 'tags' | 'content' | 'created_at'>
|
||||
export type UnsignedEvent<K extends number = number> = Pick<
|
||||
Event<K>,
|
||||
'kind' | 'tags' | 'content' | 'created_at' | 'pubkey'
|
||||
>
|
||||
|
||||
/** An event whose signature has been verified. */
|
||||
export interface VerifiedEvent<K extends number = number> extends Event<K> {
|
||||
[verifiedSymbol]: true
|
||||
}
|
||||
|
||||
export function getBlankEvent(): EventTemplate<Kind.Blank>
|
||||
export function getBlankEvent<K extends number>(kind: K): EventTemplate<K>
|
||||
export function getBlankEvent<K>(kind: K | Kind.Blank = Kind.Blank) {
|
||||
return {
|
||||
kind,
|
||||
content: '',
|
||||
tags: [],
|
||||
created_at: 0,
|
||||
}
|
||||
}
|
||||
|
||||
export function finishEvent<K extends number = number>(t: EventTemplate<K>, privateKey: string): VerifiedEvent<K> {
|
||||
const event = t as VerifiedEvent<K>
|
||||
event.pubkey = getPublicKey(privateKey)
|
||||
event.id = getEventHash(event)
|
||||
event.sig = getSignature(event, privateKey)
|
||||
event[verifiedSymbol] = true
|
||||
return event
|
||||
}
|
||||
|
||||
export function serializeEvent(evt: UnsignedEvent<number>): string {
|
||||
if (!validateEvent(evt)) throw new Error("can't serialize event with wrong or missing properties")
|
||||
|
||||
return JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content])
|
||||
}
|
||||
|
||||
export function getEventHash(event: UnsignedEvent<number>): string {
|
||||
let eventHash = sha256(utf8Encoder.encode(serializeEvent(event)))
|
||||
return bytesToHex(eventHash)
|
||||
}
|
||||
|
||||
const isRecord = (obj: unknown): obj is Record<string, unknown> => obj instanceof Object
|
||||
|
||||
export function validateEvent<T>(event: T): event is T & UnsignedEvent<number> {
|
||||
if (!isRecord(event)) return false
|
||||
if (typeof event.kind !== 'number') return false
|
||||
if (typeof event.content !== 'string') return false
|
||||
if (typeof event.created_at !== 'number') return false
|
||||
if (typeof event.pubkey !== 'string') return false
|
||||
if (!event.pubkey.match(/^[a-f0-9]{64}$/)) return false
|
||||
|
||||
if (!Array.isArray(event.tags)) return false
|
||||
for (let i = 0; i < event.tags.length; i++) {
|
||||
let tag = event.tags[i]
|
||||
if (!Array.isArray(tag)) return false
|
||||
for (let j = 0; j < tag.length; j++) {
|
||||
if (typeof tag[j] === 'object') return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/** Verify the event's signature. This function mutates the event with a `verified` symbol, making it idempotent. */
|
||||
export function verifySignature<K extends number>(event: Event<K>): event is VerifiedEvent<K> {
|
||||
//@ts-ignore
|
||||
if (typeof event[verifiedSymbol] === 'boolean') return event[verifiedSymbol]
|
||||
|
||||
const hash = getEventHash(event)
|
||||
if (hash !== event.id) {
|
||||
return (event[verifiedSymbol] = false)
|
||||
}
|
||||
|
||||
try {
|
||||
return (event[verifiedSymbol] = schnorr.verify(event.sig, hash, event.pubkey))
|
||||
} catch (err) {
|
||||
return (event[verifiedSymbol] = false)
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Use `getSignature` instead. */
|
||||
export function signEvent(event: UnsignedEvent<number>, key: string): string {
|
||||
console.warn(
|
||||
'nostr-tools: `signEvent` is deprecated and will be removed or changed in the future. Please use `getSignature` instead.',
|
||||
)
|
||||
return getSignature(event, key)
|
||||
}
|
||||
|
||||
/** Calculate the signature for an event. */
|
||||
export function getSignature(event: UnsignedEvent<number>, key: string): string {
|
||||
return bytesToHex(schnorr.sign(getEventHash(event), key))
|
||||
import { schnorr } from '@noble/curves/secp256k1'
|
||||
import { sha256 } from '@noble/hashes/sha256'
|
||||
import { bytesToHex } from '@noble/hashes/utils'
|
||||
|
||||
import { getPublicKey } from './keys.js'
|
||||
import { utf8Encoder } from './utils.js'
|
||||
|
||||
/** Designates a verified event signature. */
|
||||
export const verifiedSymbol = Symbol('verified')
|
||||
|
||||
/** @deprecated Use numbers instead. */
|
||||
/* eslint-disable no-unused-vars */
|
||||
export enum Kind {
|
||||
Metadata = 0,
|
||||
Text = 1,
|
||||
RecommendRelay = 2,
|
||||
Contacts = 3,
|
||||
EncryptedDirectMessage = 4,
|
||||
EventDeletion = 5,
|
||||
Repost = 6,
|
||||
Reaction = 7,
|
||||
BadgeAward = 8,
|
||||
ChannelCreation = 40,
|
||||
ChannelMetadata = 41,
|
||||
ChannelMessage = 42,
|
||||
ChannelHideMessage = 43,
|
||||
ChannelMuteUser = 44,
|
||||
Blank = 255,
|
||||
Report = 1984,
|
||||
ZapRequest = 9734,
|
||||
Zap = 9735,
|
||||
RelayList = 10002,
|
||||
ClientAuth = 22242,
|
||||
HttpAuth = 27235,
|
||||
ProfileBadge = 30008,
|
||||
BadgeDefinition = 30009,
|
||||
Article = 30023,
|
||||
FileMetadata = 1063,
|
||||
}
|
||||
|
||||
export interface Event<K extends number = number> {
|
||||
kind: K
|
||||
tags: string[][]
|
||||
content: string
|
||||
created_at: number
|
||||
pubkey: string
|
||||
id: string
|
||||
sig: string
|
||||
[verifiedSymbol]?: boolean
|
||||
}
|
||||
|
||||
export type EventTemplate<K extends number = number> = Pick<Event<K>, 'kind' | 'tags' | 'content' | 'created_at'>
|
||||
export type UnsignedEvent<K extends number = number> = Pick<
|
||||
Event<K>,
|
||||
'kind' | 'tags' | 'content' | 'created_at' | 'pubkey'
|
||||
>
|
||||
|
||||
/** An event whose signature has been verified. */
|
||||
export interface VerifiedEvent<K extends number = number> extends Event<K> {
|
||||
[verifiedSymbol]: true
|
||||
}
|
||||
|
||||
export function getBlankEvent(): EventTemplate<Kind.Blank>
|
||||
export function getBlankEvent<K extends number>(kind: K): EventTemplate<K>
|
||||
export function getBlankEvent<K>(kind: K | Kind.Blank = Kind.Blank) {
|
||||
return {
|
||||
kind,
|
||||
content: '',
|
||||
tags: [],
|
||||
created_at: 0,
|
||||
}
|
||||
}
|
||||
|
||||
export function finishEvent<K extends number = number>(t: EventTemplate<K>, privateKey: string): VerifiedEvent<K> {
|
||||
const event = t as VerifiedEvent<K>
|
||||
event.pubkey = getPublicKey(privateKey)
|
||||
event.id = getEventHash(event)
|
||||
event.sig = getSignature(event, privateKey)
|
||||
event[verifiedSymbol] = true
|
||||
return event
|
||||
}
|
||||
|
||||
export function serializeEvent(evt: UnsignedEvent<number>): string {
|
||||
if (!validateEvent(evt)) throw new Error("can't serialize event with wrong or missing properties")
|
||||
|
||||
return JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content])
|
||||
}
|
||||
|
||||
export function getEventHash(event: UnsignedEvent<number>): string {
|
||||
let eventHash = sha256(utf8Encoder.encode(serializeEvent(event)))
|
||||
return bytesToHex(eventHash)
|
||||
}
|
||||
|
||||
const isRecord = (obj: unknown): obj is Record<string, unknown> => obj instanceof Object
|
||||
|
||||
export function validateEvent<T>(event: T): event is T & UnsignedEvent<number> {
|
||||
if (!isRecord(event)) return false
|
||||
if (typeof event.kind !== 'number') return false
|
||||
if (typeof event.content !== 'string') return false
|
||||
if (typeof event.created_at !== 'number') return false
|
||||
if (typeof event.pubkey !== 'string') return false
|
||||
if (!event.pubkey.match(/^[a-f0-9]{64}$/)) return false
|
||||
|
||||
if (!Array.isArray(event.tags)) return false
|
||||
for (let i = 0; i < event.tags.length; i++) {
|
||||
let tag = event.tags[i]
|
||||
if (!Array.isArray(tag)) return false
|
||||
for (let j = 0; j < tag.length; j++) {
|
||||
if (typeof tag[j] === 'object') return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/** Verify the event's signature. This function mutates the event with a `verified` symbol, making it idempotent. */
|
||||
export function verifySignature<K extends number>(event: Event<K>): event is VerifiedEvent<K> {
|
||||
//@ts-ignore
|
||||
if (typeof event[verifiedSymbol] === 'boolean') return event[verifiedSymbol]
|
||||
|
||||
const hash = getEventHash(event)
|
||||
if (hash !== event.id) {
|
||||
return (event[verifiedSymbol] = false)
|
||||
}
|
||||
|
||||
try {
|
||||
return (event[verifiedSymbol] = schnorr.verify(event.sig, hash, event.pubkey))
|
||||
} catch (err) {
|
||||
return (event[verifiedSymbol] = false)
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Use `getSignature` instead. */
|
||||
export function signEvent(event: UnsignedEvent<number>, key: string): string {
|
||||
console.warn(
|
||||
'nostr-tools: `signEvent` is deprecated and will be removed or changed in the future. Please use `getSignature` instead.',
|
||||
)
|
||||
return getSignature(event, key)
|
||||
}
|
||||
|
||||
/** Calculate the signature for an event. */
|
||||
export function getSignature(event: UnsignedEvent<number>, key: string): string {
|
||||
return bytesToHex(schnorr.sign(getEventHash(event), key))
|
||||
}
|
||||
|
|
@ -1,41 +1,41 @@
|
|||
export function getHex64(json: string, field: string): string {
|
||||
let len = field.length + 3
|
||||
let idx = json.indexOf(`"${field}":`) + len
|
||||
let s = json.slice(idx).indexOf(`"`) + idx + 1
|
||||
return json.slice(s, s + 64)
|
||||
}
|
||||
|
||||
export function getInt(json: string, field: string): number {
|
||||
let len = field.length
|
||||
let idx = json.indexOf(`"${field}":`) + len + 3
|
||||
let sliced = json.slice(idx)
|
||||
let end = Math.min(sliced.indexOf(','), sliced.indexOf('}'))
|
||||
return parseInt(sliced.slice(0, end), 10)
|
||||
}
|
||||
|
||||
export function getSubscriptionId(json: string): string | null {
|
||||
let idx = json.slice(0, 22).indexOf(`"EVENT"`)
|
||||
if (idx === -1) return null
|
||||
|
||||
let pstart = json.slice(idx + 7 + 1).indexOf(`"`)
|
||||
if (pstart === -1) return null
|
||||
let start = idx + 7 + 1 + pstart
|
||||
|
||||
let pend = json.slice(start + 1, 80).indexOf(`"`)
|
||||
if (pend === -1) return null
|
||||
let end = start + 1 + pend
|
||||
|
||||
return json.slice(start + 1, end)
|
||||
}
|
||||
|
||||
export function matchEventId(json: string, id: string): boolean {
|
||||
return id === getHex64(json, 'id')
|
||||
}
|
||||
|
||||
export function matchEventPubkey(json: string, pubkey: string): boolean {
|
||||
return pubkey === getHex64(json, 'pubkey')
|
||||
}
|
||||
|
||||
export function matchEventKind(json: string, kind: number): boolean {
|
||||
return kind === getInt(json, 'kind')
|
||||
export function getHex64(json: string, field: string): string {
|
||||
let len = field.length + 3
|
||||
let idx = json.indexOf(`"${field}":`) + len
|
||||
let s = json.slice(idx).indexOf(`"`) + idx + 1
|
||||
return json.slice(s, s + 64)
|
||||
}
|
||||
|
||||
export function getInt(json: string, field: string): number {
|
||||
let len = field.length
|
||||
let idx = json.indexOf(`"${field}":`) + len + 3
|
||||
let sliced = json.slice(idx)
|
||||
let end = Math.min(sliced.indexOf(','), sliced.indexOf('}'))
|
||||
return parseInt(sliced.slice(0, end), 10)
|
||||
}
|
||||
|
||||
export function getSubscriptionId(json: string): string | null {
|
||||
let idx = json.slice(0, 22).indexOf(`"EVENT"`)
|
||||
if (idx === -1) return null
|
||||
|
||||
let pstart = json.slice(idx + 7 + 1).indexOf(`"`)
|
||||
if (pstart === -1) return null
|
||||
let start = idx + 7 + 1 + pstart
|
||||
|
||||
let pend = json.slice(start + 1, 80).indexOf(`"`)
|
||||
if (pend === -1) return null
|
||||
let end = start + 1 + pend
|
||||
|
||||
return json.slice(start + 1, end)
|
||||
}
|
||||
|
||||
export function matchEventId(json: string, id: string): boolean {
|
||||
return id === getHex64(json, 'id')
|
||||
}
|
||||
|
||||
export function matchEventPubkey(json: string, pubkey: string): boolean {
|
||||
return pubkey === getHex64(json, 'pubkey')
|
||||
}
|
||||
|
||||
export function matchEventKind(json: string, kind: number): boolean {
|
||||
return kind === getInt(json, 'kind')
|
||||
}
|
||||
|
|
@ -1,72 +1,72 @@
|
|||
import { Event } from './event.js'
|
||||
|
||||
export type Filter<K extends number = number> = {
|
||||
ids?: string[]
|
||||
kinds?: K[]
|
||||
authors?: string[]
|
||||
since?: number
|
||||
until?: number
|
||||
limit?: number
|
||||
search?: string
|
||||
[key: `#${string}`]: string[] | undefined
|
||||
}
|
||||
|
||||
export function matchFilter(filter: Filter<number>, event: Event<number>): boolean {
|
||||
if (filter.ids && filter.ids.indexOf(event.id) === -1) {
|
||||
if (!filter.ids.some(prefix => event.id.startsWith(prefix))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (filter.kinds && filter.kinds.indexOf(event.kind) === -1) return false
|
||||
if (filter.authors && filter.authors.indexOf(event.pubkey) === -1) {
|
||||
if (!filter.authors.some(prefix => event.pubkey.startsWith(prefix))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for (let f in filter) {
|
||||
if (f[0] === '#') {
|
||||
let tagName = f.slice(1)
|
||||
let values = filter[`#${tagName}`]
|
||||
if (values && !event.tags.find(([t, v]) => t === f.slice(1) && values!.indexOf(v) !== -1)) return false
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.since && event.created_at < filter.since) return false
|
||||
if (filter.until && event.created_at > filter.until) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export function matchFilters(filters: Filter<number>[], event: Event<number>): boolean {
|
||||
for (let i = 0; i < filters.length; i++) {
|
||||
if (matchFilter(filters[i], event)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function mergeFilters(...filters: Filter<number>[]): Filter<number> {
|
||||
let result: Filter<number> = {}
|
||||
for (let i = 0; i < filters.length; i++) {
|
||||
let filter = filters[i]
|
||||
Object.entries(filter).forEach(([property, values]) => {
|
||||
if (property === 'kinds' || property === 'ids' || property === 'authors' || property[0] === '#') {
|
||||
// @ts-ignore
|
||||
result[property] = result[property] || []
|
||||
// @ts-ignore
|
||||
for (let v = 0; v < values.length; v++) {
|
||||
// @ts-ignore
|
||||
let value = values[v]
|
||||
// @ts-ignore
|
||||
if (!result[property].includes(value)) result[property].push(value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (filter.limit && (!result.limit || filter.limit > result.limit)) result.limit = filter.limit
|
||||
if (filter.until && (!result.until || filter.until > result.until)) result.until = filter.until
|
||||
if (filter.since && (!result.since || filter.since < result.since)) result.since = filter.since
|
||||
}
|
||||
|
||||
return result
|
||||
import { Event } from './event.js'
|
||||
|
||||
export type Filter<K extends number = number> = {
|
||||
ids?: string[]
|
||||
kinds?: K[]
|
||||
authors?: string[]
|
||||
since?: number
|
||||
until?: number
|
||||
limit?: number
|
||||
search?: string
|
||||
[key: `#${string}`]: string[] | undefined
|
||||
}
|
||||
|
||||
export function matchFilter(filter: Filter<number>, event: Event<number>): boolean {
|
||||
if (filter.ids && filter.ids.indexOf(event.id) === -1) {
|
||||
if (!filter.ids.some(prefix => event.id.startsWith(prefix))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (filter.kinds && filter.kinds.indexOf(event.kind) === -1) return false
|
||||
if (filter.authors && filter.authors.indexOf(event.pubkey) === -1) {
|
||||
if (!filter.authors.some(prefix => event.pubkey.startsWith(prefix))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for (let f in filter) {
|
||||
if (f[0] === '#') {
|
||||
let tagName = f.slice(1)
|
||||
let values = filter[`#${tagName}`]
|
||||
if (values && !event.tags.find(([t, v]) => t === f.slice(1) && values!.indexOf(v) !== -1)) return false
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.since && event.created_at < filter.since) return false
|
||||
if (filter.until && event.created_at > filter.until) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export function matchFilters(filters: Filter<number>[], event: Event<number>): boolean {
|
||||
for (let i = 0; i < filters.length; i++) {
|
||||
if (matchFilter(filters[i], event)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function mergeFilters(...filters: Filter<number>[]): Filter<number> {
|
||||
let result: Filter<number> = {}
|
||||
for (let i = 0; i < filters.length; i++) {
|
||||
let filter = filters[i]
|
||||
Object.entries(filter).forEach(([property, values]) => {
|
||||
if (property === 'kinds' || property === 'ids' || property === 'authors' || property[0] === '#') {
|
||||
// @ts-ignore
|
||||
result[property] = result[property] || []
|
||||
// @ts-ignore
|
||||
for (let v = 0; v < values.length; v++) {
|
||||
// @ts-ignore
|
||||
let value = values[v]
|
||||
// @ts-ignore
|
||||
if (!result[property].includes(value)) result[property].push(value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (filter.limit && (!result.limit || filter.limit > result.limit)) result.limit = filter.limit
|
||||
if (filter.until && (!result.until || filter.until > result.until)) result.until = filter.until
|
||||
if (filter.since && (!result.since || filter.since < result.since)) result.since = filter.since
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
export * from './event.js'
|
||||
export * from './fakejson.js'
|
||||
export * from './filter.js'
|
||||
export * from './keys.js'
|
||||
export * from './pool.js'
|
||||
export * from './relay.js'
|
||||
export * from './utils.js'
|
||||
export * from './event.js'
|
||||
export * from './fakejson.js'
|
||||
export * from './filter.js'
|
||||
export * from './keys.js'
|
||||
export * from './pool.js'
|
||||
export * from './relay.js'
|
||||
export * from './utils.js'
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { schnorr } from '@noble/curves/secp256k1'
|
||||
import { bytesToHex } from '@noble/hashes/utils'
|
||||
|
||||
export function generatePrivateKey(): string {
|
||||
return bytesToHex(schnorr.utils.randomPrivateKey())
|
||||
}
|
||||
|
||||
export function getPublicKey(privateKey: string): string {
|
||||
return bytesToHex(schnorr.getPublicKey(privateKey))
|
||||
import { schnorr } from '@noble/curves/secp256k1'
|
||||
import { bytesToHex } from '@noble/hashes/utils'
|
||||
|
||||
export function generatePrivateKey(): string {
|
||||
return bytesToHex(schnorr.utils.randomPrivateKey())
|
||||
}
|
||||
|
||||
export function getPublicKey(privateKey: string): string {
|
||||
return bytesToHex(schnorr.getPublicKey(privateKey))
|
||||
}
|
||||
|
|
@ -1,249 +1,249 @@
|
|||
import { relayInit, eventsGenerator, type Relay, type Sub, type SubscriptionOptions } from './relay.js'
|
||||
import { normalizeURL } from './utils.js'
|
||||
|
||||
import type { Event } from './event.js'
|
||||
import { matchFilters, type Filter } from './filter.js'
|
||||
|
||||
type BatchedRequest = {
|
||||
filters: Filter<any>[]
|
||||
relays: string[]
|
||||
resolve: (events: Event<any>[]) => void
|
||||
events: Event<any>[]
|
||||
}
|
||||
|
||||
export class SimplePool {
|
||||
private _conn: { [url: string]: Relay }
|
||||
private _seenOn: { [id: string]: Set<string> } = {} // a map of all events we've seen in each relay
|
||||
private batchedByKey: { [batchKey: string]: BatchedRequest[] } = {}
|
||||
|
||||
private eoseSubTimeout: number
|
||||
private getTimeout: number
|
||||
private seenOnEnabled: boolean = true
|
||||
private batchInterval: number = 100
|
||||
|
||||
constructor(
|
||||
options: {
|
||||
eoseSubTimeout?: number
|
||||
getTimeout?: number
|
||||
seenOnEnabled?: boolean
|
||||
batchInterval?: number
|
||||
} = {},
|
||||
) {
|
||||
this._conn = {}
|
||||
this.eoseSubTimeout = options.eoseSubTimeout || 3400
|
||||
this.getTimeout = options.getTimeout || 3400
|
||||
this.seenOnEnabled = options.seenOnEnabled !== false
|
||||
this.batchInterval = options.batchInterval || 100
|
||||
}
|
||||
|
||||
close(relays: string[]): void {
|
||||
relays.forEach(url => {
|
||||
let relay = this._conn[normalizeURL(url)]
|
||||
if (relay) relay.close()
|
||||
})
|
||||
}
|
||||
|
||||
async ensureRelay(url: string): Promise<Relay> {
|
||||
const nm = normalizeURL(url)
|
||||
|
||||
if (!this._conn[nm]) {
|
||||
this._conn[nm] = relayInit(nm, {
|
||||
getTimeout: this.getTimeout * 0.9,
|
||||
listTimeout: this.getTimeout * 0.9,
|
||||
})
|
||||
}
|
||||
|
||||
const relay = this._conn[nm]
|
||||
await relay.connect()
|
||||
return relay
|
||||
}
|
||||
|
||||
sub<K extends number = number>(relays: string[], filters: Filter<K>[], opts?: SubscriptionOptions): Sub<K> {
|
||||
let _knownIds: Set<string> = new Set()
|
||||
let modifiedOpts = { ...(opts || {}) }
|
||||
modifiedOpts.alreadyHaveEvent = (id, url) => {
|
||||
if (opts?.alreadyHaveEvent?.(id, url)) {
|
||||
return true
|
||||
}
|
||||
if (this.seenOnEnabled) {
|
||||
let set = this._seenOn[id] || new Set()
|
||||
set.add(url)
|
||||
this._seenOn[id] = set
|
||||
}
|
||||
return _knownIds.has(id)
|
||||
}
|
||||
|
||||
let subs: Sub[] = []
|
||||
let eventListeners: Set<any> = new Set()
|
||||
let eoseListeners: Set<() => void> = new Set()
|
||||
let eosesMissing = relays.length
|
||||
|
||||
let eoseSent = false
|
||||
let eoseTimeout = setTimeout(
|
||||
() => {
|
||||
eoseSent = true
|
||||
for (let cb of eoseListeners.values()) cb()
|
||||
},
|
||||
opts?.eoseSubTimeout || this.eoseSubTimeout,
|
||||
)
|
||||
|
||||
relays
|
||||
.filter((r, i, a) => a.indexOf(r) === i)
|
||||
.forEach(async relay => {
|
||||
let r
|
||||
try {
|
||||
r = await this.ensureRelay(relay)
|
||||
} catch (err) {
|
||||
handleEose()
|
||||
return
|
||||
}
|
||||
if (!r) return
|
||||
let s = r.sub(filters, modifiedOpts)
|
||||
s.on('event', event => {
|
||||
_knownIds.add(event.id as string)
|
||||
for (let cb of eventListeners.values()) cb(event)
|
||||
})
|
||||
s.on('eose', () => {
|
||||
if (eoseSent) return
|
||||
handleEose()
|
||||
})
|
||||
subs.push(s)
|
||||
|
||||
function handleEose() {
|
||||
eosesMissing--
|
||||
if (eosesMissing === 0) {
|
||||
clearTimeout(eoseTimeout)
|
||||
for (let cb of eoseListeners.values()) cb()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let greaterSub: Sub<K> = {
|
||||
sub(filters, opts) {
|
||||
subs.forEach(sub => sub.sub(filters, opts))
|
||||
return greaterSub as any
|
||||
},
|
||||
unsub() {
|
||||
subs.forEach(sub => sub.unsub())
|
||||
},
|
||||
on(type, cb) {
|
||||
if (type === 'event') {
|
||||
eventListeners.add(cb)
|
||||
} else if (type === 'eose') {
|
||||
eoseListeners.add(cb as () => void | Promise<void>)
|
||||
}
|
||||
},
|
||||
off(type, cb) {
|
||||
if (type === 'event') {
|
||||
eventListeners.delete(cb)
|
||||
} else if (type === 'eose') eoseListeners.delete(cb as () => void | Promise<void>)
|
||||
},
|
||||
get events() {
|
||||
return eventsGenerator(greaterSub)
|
||||
},
|
||||
}
|
||||
|
||||
return greaterSub
|
||||
}
|
||||
|
||||
get<K extends number = number>(
|
||||
relays: string[],
|
||||
filter: Filter<K>,
|
||||
opts?: SubscriptionOptions,
|
||||
): Promise<Event<K> | null> {
|
||||
return new Promise(resolve => {
|
||||
let sub = this.sub(relays, [filter], opts)
|
||||
let timeout = setTimeout(() => {
|
||||
sub.unsub()
|
||||
resolve(null)
|
||||
}, this.getTimeout)
|
||||
sub.on('event', event => {
|
||||
resolve(event)
|
||||
clearTimeout(timeout)
|
||||
sub.unsub()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
list<K extends number = number>(
|
||||
relays: string[],
|
||||
filters: Filter<K>[],
|
||||
opts?: SubscriptionOptions,
|
||||
): Promise<Event<K>[]> {
|
||||
return new Promise(resolve => {
|
||||
let events: Event<K>[] = []
|
||||
let sub = this.sub(relays, filters, opts)
|
||||
|
||||
sub.on('event', event => {
|
||||
events.push(event)
|
||||
})
|
||||
|
||||
// we can rely on an eose being emitted here because pool.sub() will fake one
|
||||
sub.on('eose', () => {
|
||||
sub.unsub()
|
||||
resolve(events)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
batchedList<K extends number = number>(
|
||||
batchKey: string,
|
||||
relays: string[],
|
||||
filters: Filter<K>[],
|
||||
): Promise<Event<K>[]> {
|
||||
return new Promise(resolve => {
|
||||
if (!this.batchedByKey[batchKey]) {
|
||||
this.batchedByKey[batchKey] = [
|
||||
{
|
||||
filters,
|
||||
relays,
|
||||
resolve,
|
||||
events: [],
|
||||
},
|
||||
]
|
||||
|
||||
setTimeout(() => {
|
||||
Object.keys(this.batchedByKey).forEach(async batchKey => {
|
||||
const batchedRequests = this.batchedByKey[batchKey]
|
||||
|
||||
const filters = [] as Filter[]
|
||||
const relays = [] as string[]
|
||||
batchedRequests.forEach(br => {
|
||||
filters.push(...br.filters)
|
||||
relays.push(...br.relays)
|
||||
})
|
||||
|
||||
const sub = this.sub(relays, filters)
|
||||
sub.on('event', event => {
|
||||
batchedRequests.forEach(br => matchFilters(br.filters, event) && br.events.push(event))
|
||||
})
|
||||
sub.on('eose', () => {
|
||||
sub.unsub()
|
||||
batchedRequests.forEach(br => br.resolve(br.events))
|
||||
})
|
||||
|
||||
delete this.batchedByKey[batchKey]
|
||||
})
|
||||
}, this.batchInterval)
|
||||
} else {
|
||||
this.batchedByKey[batchKey].push({
|
||||
filters,
|
||||
relays,
|
||||
resolve,
|
||||
events: [],
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
publish(relays: string[], event: Event<number>): Promise<void>[] {
|
||||
return relays.map(async relay => {
|
||||
let r = await this.ensureRelay(relay)
|
||||
return r.publish(event)
|
||||
})
|
||||
}
|
||||
|
||||
seenOn(id: string): string[] {
|
||||
return Array.from(this._seenOn[id]?.values?.() || [])
|
||||
}
|
||||
import { relayInit, eventsGenerator, type Relay, type Sub, type SubscriptionOptions } from './relay.js'
|
||||
import { normalizeURL } from './utils.js'
|
||||
|
||||
import type { Event } from './event.js'
|
||||
import { matchFilters, type Filter } from './filter.js'
|
||||
|
||||
type BatchedRequest = {
|
||||
filters: Filter<any>[]
|
||||
relays: string[]
|
||||
resolve: (events: Event<any>[]) => void
|
||||
events: Event<any>[]
|
||||
}
|
||||
|
||||
export class SimplePool {
|
||||
private _conn: { [url: string]: Relay }
|
||||
private _seenOn: { [id: string]: Set<string> } = {} // a map of all events we've seen in each relay
|
||||
private batchedByKey: { [batchKey: string]: BatchedRequest[] } = {}
|
||||
|
||||
private eoseSubTimeout: number
|
||||
private getTimeout: number
|
||||
private seenOnEnabled: boolean = true
|
||||
private batchInterval: number = 100
|
||||
|
||||
constructor(
|
||||
options: {
|
||||
eoseSubTimeout?: number
|
||||
getTimeout?: number
|
||||
seenOnEnabled?: boolean
|
||||
batchInterval?: number
|
||||
} = {},
|
||||
) {
|
||||
this._conn = {}
|
||||
this.eoseSubTimeout = options.eoseSubTimeout || 3400
|
||||
this.getTimeout = options.getTimeout || 3400
|
||||
this.seenOnEnabled = options.seenOnEnabled !== false
|
||||
this.batchInterval = options.batchInterval || 100
|
||||
}
|
||||
|
||||
close(relays: string[]): void {
|
||||
relays.forEach(url => {
|
||||
let relay = this._conn[normalizeURL(url)]
|
||||
if (relay) relay.close()
|
||||
})
|
||||
}
|
||||
|
||||
async ensureRelay(url: string): Promise<Relay> {
|
||||
const nm = normalizeURL(url)
|
||||
|
||||
if (!this._conn[nm]) {
|
||||
this._conn[nm] = relayInit(nm, {
|
||||
getTimeout: this.getTimeout * 0.9,
|
||||
listTimeout: this.getTimeout * 0.9,
|
||||
})
|
||||
}
|
||||
|
||||
const relay = this._conn[nm]
|
||||
await relay.connect()
|
||||
return relay
|
||||
}
|
||||
|
||||
sub<K extends number = number>(relays: string[], filters: Filter<K>[], opts?: SubscriptionOptions): Sub<K> {
|
||||
let _knownIds: Set<string> = new Set()
|
||||
let modifiedOpts = { ...(opts || {}) }
|
||||
modifiedOpts.alreadyHaveEvent = (id, url) => {
|
||||
if (opts?.alreadyHaveEvent?.(id, url)) {
|
||||
return true
|
||||
}
|
||||
if (this.seenOnEnabled) {
|
||||
let set = this._seenOn[id] || new Set()
|
||||
set.add(url)
|
||||
this._seenOn[id] = set
|
||||
}
|
||||
return _knownIds.has(id)
|
||||
}
|
||||
|
||||
let subs: Sub[] = []
|
||||
let eventListeners: Set<any> = new Set()
|
||||
let eoseListeners: Set<() => void> = new Set()
|
||||
let eosesMissing = relays.length
|
||||
|
||||
let eoseSent = false
|
||||
let eoseTimeout = setTimeout(
|
||||
() => {
|
||||
eoseSent = true
|
||||
for (let cb of eoseListeners.values()) cb()
|
||||
},
|
||||
opts?.eoseSubTimeout || this.eoseSubTimeout,
|
||||
)
|
||||
|
||||
relays
|
||||
.filter((r, i, a) => a.indexOf(r) === i)
|
||||
.forEach(async relay => {
|
||||
let r
|
||||
try {
|
||||
r = await this.ensureRelay(relay)
|
||||
} catch (err) {
|
||||
handleEose()
|
||||
return
|
||||
}
|
||||
if (!r) return
|
||||
let s = r.sub(filters, modifiedOpts)
|
||||
s.on('event', event => {
|
||||
_knownIds.add(event.id as string)
|
||||
for (let cb of eventListeners.values()) cb(event)
|
||||
})
|
||||
s.on('eose', () => {
|
||||
if (eoseSent) return
|
||||
handleEose()
|
||||
})
|
||||
subs.push(s)
|
||||
|
||||
function handleEose() {
|
||||
eosesMissing--
|
||||
if (eosesMissing === 0) {
|
||||
clearTimeout(eoseTimeout)
|
||||
for (let cb of eoseListeners.values()) cb()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let greaterSub: Sub<K> = {
|
||||
sub(filters, opts) {
|
||||
subs.forEach(sub => sub.sub(filters, opts))
|
||||
return greaterSub as any
|
||||
},
|
||||
unsub() {
|
||||
subs.forEach(sub => sub.unsub())
|
||||
},
|
||||
on(type, cb) {
|
||||
if (type === 'event') {
|
||||
eventListeners.add(cb)
|
||||
} else if (type === 'eose') {
|
||||
eoseListeners.add(cb as () => void | Promise<void>)
|
||||
}
|
||||
},
|
||||
off(type, cb) {
|
||||
if (type === 'event') {
|
||||
eventListeners.delete(cb)
|
||||
} else if (type === 'eose') eoseListeners.delete(cb as () => void | Promise<void>)
|
||||
},
|
||||
get events() {
|
||||
return eventsGenerator(greaterSub)
|
||||
},
|
||||
}
|
||||
|
||||
return greaterSub
|
||||
}
|
||||
|
||||
get<K extends number = number>(
|
||||
relays: string[],
|
||||
filter: Filter<K>,
|
||||
opts?: SubscriptionOptions,
|
||||
): Promise<Event<K> | null> {
|
||||
return new Promise(resolve => {
|
||||
let sub = this.sub(relays, [filter], opts)
|
||||
let timeout = setTimeout(() => {
|
||||
sub.unsub()
|
||||
resolve(null)
|
||||
}, this.getTimeout)
|
||||
sub.on('event', event => {
|
||||
resolve(event)
|
||||
clearTimeout(timeout)
|
||||
sub.unsub()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
list<K extends number = number>(
|
||||
relays: string[],
|
||||
filters: Filter<K>[],
|
||||
opts?: SubscriptionOptions,
|
||||
): Promise<Event<K>[]> {
|
||||
return new Promise(resolve => {
|
||||
let events: Event<K>[] = []
|
||||
let sub = this.sub(relays, filters, opts)
|
||||
|
||||
sub.on('event', event => {
|
||||
events.push(event)
|
||||
})
|
||||
|
||||
// we can rely on an eose being emitted here because pool.sub() will fake one
|
||||
sub.on('eose', () => {
|
||||
sub.unsub()
|
||||
resolve(events)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
batchedList<K extends number = number>(
|
||||
batchKey: string,
|
||||
relays: string[],
|
||||
filters: Filter<K>[],
|
||||
): Promise<Event<K>[]> {
|
||||
return new Promise(resolve => {
|
||||
if (!this.batchedByKey[batchKey]) {
|
||||
this.batchedByKey[batchKey] = [
|
||||
{
|
||||
filters,
|
||||
relays,
|
||||
resolve,
|
||||
events: [],
|
||||
},
|
||||
]
|
||||
|
||||
setTimeout(() => {
|
||||
Object.keys(this.batchedByKey).forEach(async batchKey => {
|
||||
const batchedRequests = this.batchedByKey[batchKey]
|
||||
|
||||
const filters = [] as Filter[]
|
||||
const relays = [] as string[]
|
||||
batchedRequests.forEach(br => {
|
||||
filters.push(...br.filters)
|
||||
relays.push(...br.relays)
|
||||
})
|
||||
|
||||
const sub = this.sub(relays, filters)
|
||||
sub.on('event', event => {
|
||||
batchedRequests.forEach(br => matchFilters(br.filters, event) && br.events.push(event))
|
||||
})
|
||||
sub.on('eose', () => {
|
||||
sub.unsub()
|
||||
batchedRequests.forEach(br => br.resolve(br.events))
|
||||
})
|
||||
|
||||
delete this.batchedByKey[batchKey]
|
||||
})
|
||||
}, this.batchInterval)
|
||||
} else {
|
||||
this.batchedByKey[batchKey].push({
|
||||
filters,
|
||||
relays,
|
||||
resolve,
|
||||
events: [],
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
publish(relays: string[], event: Event<number>): Promise<void>[] {
|
||||
return relays.map(async relay => {
|
||||
let r = await this.ensureRelay(relay)
|
||||
return r.publish(event)
|
||||
})
|
||||
}
|
||||
|
||||
seenOn(id: string): string[] {
|
||||
return Array.from(this._seenOn[id]?.values?.() || [])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,402 +1,402 @@
|
|||
/* global WebSocket */
|
||||
import "websocket-polyfill"
|
||||
import { verifySignature, validateEvent, type Event } from './event.js'
|
||||
import { matchFilters, type Filter } from './filter.js'
|
||||
import { getHex64, getSubscriptionId } from './fakejson.js'
|
||||
import { MessageQueue } from './utils.js'
|
||||
|
||||
type RelayEvent = {
|
||||
connect: () => void | Promise<void>
|
||||
disconnect: () => void | Promise<void>
|
||||
error: () => void | Promise<void>
|
||||
notice: (msg: string) => void | Promise<void>
|
||||
auth: (challenge: string) => void | Promise<void>
|
||||
}
|
||||
export type CountPayload = {
|
||||
count: number
|
||||
}
|
||||
export type SubEvent<K extends number> = {
|
||||
event: (event: Event<K>) => void | Promise<void>
|
||||
count: (payload: CountPayload) => void | Promise<void>
|
||||
eose: () => void | Promise<void>
|
||||
}
|
||||
export type Relay = {
|
||||
url: string
|
||||
status: number
|
||||
connect: () => Promise<void>
|
||||
close: () => void
|
||||
sub: <K extends number = number>(filters: Filter<K>[], opts?: SubscriptionOptions) => Sub<K>
|
||||
list: <K extends number = number>(filters: Filter<K>[], opts?: SubscriptionOptions) => Promise<Event<K>[]>
|
||||
get: <K extends number = number>(filter: Filter<K>, opts?: SubscriptionOptions) => Promise<Event<K> | null>
|
||||
count: (filters: Filter[], opts?: SubscriptionOptions) => Promise<CountPayload | null>
|
||||
publish: (event: Event<number>) => Promise<void>
|
||||
auth: (event: Event<number>) => Promise<void>
|
||||
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(event: T, listener: U) => void
|
||||
on: <T extends keyof RelayEvent, U extends RelayEvent[T]>(event: T, listener: U) => void
|
||||
}
|
||||
export type Sub<K extends number = number> = {
|
||||
sub: <K extends number = number>(filters: Filter<K>[], opts: SubscriptionOptions) => Sub<K>
|
||||
unsub: () => void
|
||||
on: <T extends keyof SubEvent<K>, U extends SubEvent<K>[T]>(event: T, listener: U) => void
|
||||
off: <T extends keyof SubEvent<K>, U extends SubEvent<K>[T]>(event: T, listener: U) => void
|
||||
events: AsyncGenerator<Event<K>, void, unknown>
|
||||
}
|
||||
|
||||
export type SubscriptionOptions = {
|
||||
id?: string
|
||||
verb?: 'REQ' | 'COUNT'
|
||||
skipVerification?: boolean
|
||||
alreadyHaveEvent?: null | ((id: string, relay: string) => boolean)
|
||||
eoseSubTimeout?: number
|
||||
}
|
||||
|
||||
const newListeners = (): { [TK in keyof RelayEvent]: RelayEvent[TK][] } => ({
|
||||
connect: [],
|
||||
disconnect: [],
|
||||
error: [],
|
||||
notice: [],
|
||||
auth: [],
|
||||
})
|
||||
|
||||
export function relayInit(
|
||||
url: string,
|
||||
options: {
|
||||
getTimeout?: number
|
||||
listTimeout?: number
|
||||
countTimeout?: number
|
||||
} = {},
|
||||
): Relay {
|
||||
let { listTimeout = 3000, getTimeout = 3000, countTimeout = 3000 } = options
|
||||
|
||||
var ws: WebSocket
|
||||
var openSubs: { [id: string]: { filters: Filter[] } & SubscriptionOptions } = {}
|
||||
var listeners = newListeners()
|
||||
var subListeners: {
|
||||
[subid: string]: { [TK in keyof SubEvent<any>]: SubEvent<any>[TK][] }
|
||||
} = {}
|
||||
var pubListeners: {
|
||||
[eventid: string]: {
|
||||
resolve: (_: unknown) => void
|
||||
reject: (err: Error) => void
|
||||
}
|
||||
} = {}
|
||||
|
||||
var connectionPromise: Promise<void> | undefined
|
||||
async function connectRelay(): Promise<void> {
|
||||
if (connectionPromise) return connectionPromise
|
||||
connectionPromise = new Promise((resolve, reject) => {
|
||||
try {
|
||||
ws = new WebSocket(url)
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
listeners.connect.forEach(cb => cb())
|
||||
resolve()
|
||||
}
|
||||
ws.onerror = () => {
|
||||
connectionPromise = undefined
|
||||
listeners.error.forEach(cb => cb())
|
||||
reject()
|
||||
}
|
||||
ws.onclose = async () => {
|
||||
connectionPromise = undefined
|
||||
listeners.disconnect.forEach(cb => cb())
|
||||
}
|
||||
|
||||
let incomingMessageQueue: MessageQueue = new MessageQueue()
|
||||
let handleNextInterval: any
|
||||
|
||||
ws.onmessage = e => {
|
||||
incomingMessageQueue.enqueue(e.data)
|
||||
if (!handleNextInterval) {
|
||||
handleNextInterval = setInterval(handleNext, 0)
|
||||
}
|
||||
}
|
||||
|
||||
function handleNext() {
|
||||
if (incomingMessageQueue.size === 0) {
|
||||
clearInterval(handleNextInterval)
|
||||
handleNextInterval = null
|
||||
return
|
||||
}
|
||||
|
||||
var json = incomingMessageQueue.dequeue()
|
||||
if (!json) return
|
||||
|
||||
let subid = getSubscriptionId(json)
|
||||
if (subid) {
|
||||
let so = openSubs[subid]
|
||||
if (so && so.alreadyHaveEvent && so.alreadyHaveEvent(getHex64(json, 'id'), url)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let data = JSON.parse(json)
|
||||
|
||||
// we won't do any checks against the data since all failures (i.e. invalid messages from relays)
|
||||
// will naturally be caught by the encompassing try..catch block
|
||||
|
||||
switch (data[0]) {
|
||||
case 'EVENT': {
|
||||
let id = data[1]
|
||||
let event = data[2]
|
||||
if (
|
||||
validateEvent(event) &&
|
||||
openSubs[id] &&
|
||||
(openSubs[id].skipVerification || verifySignature(event)) &&
|
||||
matchFilters(openSubs[id].filters, event)
|
||||
) {
|
||||
openSubs[id]
|
||||
; (subListeners[id]?.event || []).forEach(cb => cb(event))
|
||||
}
|
||||
return
|
||||
}
|
||||
case 'COUNT':
|
||||
let id = data[1]
|
||||
let payload = data[2]
|
||||
if (openSubs[id]) {
|
||||
; (subListeners[id]?.count || []).forEach(cb => cb(payload))
|
||||
}
|
||||
return
|
||||
case 'EOSE': {
|
||||
let id = data[1]
|
||||
if (id in subListeners) {
|
||||
subListeners[id].eose.forEach(cb => cb())
|
||||
subListeners[id].eose = [] // 'eose' only happens once per sub, so stop listeners here
|
||||
}
|
||||
return
|
||||
}
|
||||
case 'OK': {
|
||||
let id: string = data[1]
|
||||
let ok: boolean = data[2]
|
||||
let reason: string = data[3] || ''
|
||||
if (id in pubListeners) {
|
||||
let { resolve, reject } = pubListeners[id]
|
||||
if (ok) resolve(null)
|
||||
else reject(new Error(reason))
|
||||
}
|
||||
return
|
||||
}
|
||||
case 'NOTICE':
|
||||
let notice = data[1]
|
||||
listeners.notice.forEach(cb => cb(notice))
|
||||
return
|
||||
case 'AUTH': {
|
||||
let challenge = data[1]
|
||||
listeners.auth?.forEach(cb => cb(challenge))
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return connectionPromise
|
||||
}
|
||||
|
||||
function connected() {
|
||||
return ws?.readyState === 1
|
||||
}
|
||||
|
||||
async function connect(): Promise<void> {
|
||||
if (connected()) return // ws already open
|
||||
await connectRelay()
|
||||
}
|
||||
|
||||
async function trySend(params: [string, ...any]) {
|
||||
let msg = JSON.stringify(params)
|
||||
if (!connected()) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
if (!connected()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
try {
|
||||
ws.send(msg)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
const sub = <K extends number = number>(
|
||||
filters: Filter<K>[],
|
||||
{
|
||||
verb = 'REQ',
|
||||
skipVerification = false,
|
||||
alreadyHaveEvent = null,
|
||||
id = Math.random().toString().slice(2),
|
||||
}: SubscriptionOptions = {},
|
||||
): Sub<K> => {
|
||||
let subid = id
|
||||
|
||||
openSubs[subid] = {
|
||||
id: subid,
|
||||
filters,
|
||||
skipVerification,
|
||||
alreadyHaveEvent,
|
||||
}
|
||||
trySend([verb, subid, ...filters])
|
||||
|
||||
let subscription: Sub<K> = {
|
||||
sub: (newFilters, newOpts = {}) =>
|
||||
sub(newFilters || filters, {
|
||||
skipVerification: newOpts.skipVerification || skipVerification,
|
||||
alreadyHaveEvent: newOpts.alreadyHaveEvent || alreadyHaveEvent,
|
||||
id: subid,
|
||||
}),
|
||||
unsub: () => {
|
||||
delete openSubs[subid]
|
||||
delete subListeners[subid]
|
||||
trySend(['CLOSE', subid])
|
||||
},
|
||||
on: (type, cb) => {
|
||||
subListeners[subid] = subListeners[subid] || {
|
||||
event: [],
|
||||
count: [],
|
||||
eose: [],
|
||||
}
|
||||
//@ts-ignore
|
||||
subListeners[subid][type].push(cb)
|
||||
},
|
||||
off: (type, cb): void => {
|
||||
let listeners = subListeners[subid]
|
||||
//@ts-ignore
|
||||
let idx = listeners[type].indexOf(cb)
|
||||
if (idx >= 0) listeners[type].splice(idx, 1)
|
||||
},
|
||||
get events() {
|
||||
return eventsGenerator(subscription)
|
||||
},
|
||||
}
|
||||
|
||||
return subscription
|
||||
}
|
||||
|
||||
function _publishEvent(event: Event<number>, type: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!event.id) {
|
||||
reject(new Error(`event ${event} has no id`))
|
||||
return
|
||||
}
|
||||
|
||||
let id = event.id
|
||||
trySend([type, event])
|
||||
pubListeners[id] = { resolve, reject }
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
url,
|
||||
sub,
|
||||
on: <T extends keyof RelayEvent, U extends RelayEvent[T]>(type: T, cb: U): void => {
|
||||
//@ts-ignore
|
||||
listeners[type].push(cb)
|
||||
if (type === 'connect' && ws?.readyState === 1) {
|
||||
// i would love to know why we need this
|
||||
; (cb as () => void)()
|
||||
}
|
||||
},
|
||||
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(type: T, cb: U): void => {
|
||||
//@ts-ignore
|
||||
let index = listeners[type].indexOf(cb)
|
||||
if (index !== -1) listeners[type].splice(index, 1)
|
||||
},
|
||||
list: (filters, opts?: SubscriptionOptions) =>
|
||||
new Promise(resolve => {
|
||||
let s = sub(filters, opts)
|
||||
let events: Event<any>[] = []
|
||||
let timeout = setTimeout(() => {
|
||||
s.unsub()
|
||||
resolve(events)
|
||||
}, listTimeout)
|
||||
s.on('eose', () => {
|
||||
s.unsub()
|
||||
clearTimeout(timeout)
|
||||
resolve(events)
|
||||
})
|
||||
s.on('event', event => {
|
||||
events.push(event)
|
||||
})
|
||||
}),
|
||||
get: (filter, opts?: SubscriptionOptions) =>
|
||||
new Promise(resolve => {
|
||||
let s = sub([filter], opts)
|
||||
let timeout = setTimeout(() => {
|
||||
s.unsub()
|
||||
resolve(null)
|
||||
}, getTimeout)
|
||||
s.on('event', event => {
|
||||
s.unsub()
|
||||
clearTimeout(timeout)
|
||||
resolve(event)
|
||||
})
|
||||
}),
|
||||
count: (filters: Filter[]): Promise<CountPayload | null> =>
|
||||
new Promise(resolve => {
|
||||
let s = sub(filters, { ...sub, verb: 'COUNT' })
|
||||
let timeout = setTimeout(() => {
|
||||
s.unsub()
|
||||
resolve(null)
|
||||
}, countTimeout)
|
||||
s.on('count', (event: CountPayload) => {
|
||||
s.unsub()
|
||||
clearTimeout(timeout)
|
||||
resolve(event)
|
||||
})
|
||||
}),
|
||||
async publish(event): Promise<void> {
|
||||
await _publishEvent(event, 'EVENT')
|
||||
},
|
||||
async auth(event): Promise<void> {
|
||||
await _publishEvent(event, 'AUTH')
|
||||
},
|
||||
connect,
|
||||
close(): void {
|
||||
listeners = newListeners()
|
||||
subListeners = {}
|
||||
pubListeners = {}
|
||||
if (ws?.readyState === WebSocket.OPEN) {
|
||||
ws.close()
|
||||
}
|
||||
},
|
||||
get status() {
|
||||
return ws?.readyState ?? 3
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function* eventsGenerator<K extends number>(sub: Sub<K>): AsyncGenerator<Event<K>, void, unknown> {
|
||||
let nextResolve: ((event: Event<K>) => void) | undefined
|
||||
const eventQueue: Event<K>[] = []
|
||||
|
||||
const pushToQueue = (event: Event<K>) => {
|
||||
if (nextResolve) {
|
||||
nextResolve(event)
|
||||
nextResolve = undefined
|
||||
} else {
|
||||
eventQueue.push(event)
|
||||
}
|
||||
}
|
||||
|
||||
sub.on('event', pushToQueue)
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
if (eventQueue.length > 0) {
|
||||
yield eventQueue.shift()!
|
||||
} else {
|
||||
const event = await new Promise<Event<K>>(resolve => {
|
||||
nextResolve = resolve
|
||||
})
|
||||
yield event
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
sub.off('event', pushToQueue)
|
||||
}
|
||||
/* global WebSocket */
|
||||
import "websocket-polyfill"
|
||||
import { verifySignature, validateEvent, type Event } from './event.js'
|
||||
import { matchFilters, type Filter } from './filter.js'
|
||||
import { getHex64, getSubscriptionId } from './fakejson.js'
|
||||
import { MessageQueue } from './utils.js'
|
||||
|
||||
type RelayEvent = {
|
||||
connect: () => void | Promise<void>
|
||||
disconnect: () => void | Promise<void>
|
||||
error: () => void | Promise<void>
|
||||
notice: (msg: string) => void | Promise<void>
|
||||
auth: (challenge: string) => void | Promise<void>
|
||||
}
|
||||
export type CountPayload = {
|
||||
count: number
|
||||
}
|
||||
export type SubEvent<K extends number> = {
|
||||
event: (event: Event<K>) => void | Promise<void>
|
||||
count: (payload: CountPayload) => void | Promise<void>
|
||||
eose: () => void | Promise<void>
|
||||
}
|
||||
export type Relay = {
|
||||
url: string
|
||||
status: number
|
||||
connect: () => Promise<void>
|
||||
close: () => void
|
||||
sub: <K extends number = number>(filters: Filter<K>[], opts?: SubscriptionOptions) => Sub<K>
|
||||
list: <K extends number = number>(filters: Filter<K>[], opts?: SubscriptionOptions) => Promise<Event<K>[]>
|
||||
get: <K extends number = number>(filter: Filter<K>, opts?: SubscriptionOptions) => Promise<Event<K> | null>
|
||||
count: (filters: Filter[], opts?: SubscriptionOptions) => Promise<CountPayload | null>
|
||||
publish: (event: Event<number>) => Promise<void>
|
||||
auth: (event: Event<number>) => Promise<void>
|
||||
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(event: T, listener: U) => void
|
||||
on: <T extends keyof RelayEvent, U extends RelayEvent[T]>(event: T, listener: U) => void
|
||||
}
|
||||
export type Sub<K extends number = number> = {
|
||||
sub: <K extends number = number>(filters: Filter<K>[], opts: SubscriptionOptions) => Sub<K>
|
||||
unsub: () => void
|
||||
on: <T extends keyof SubEvent<K>, U extends SubEvent<K>[T]>(event: T, listener: U) => void
|
||||
off: <T extends keyof SubEvent<K>, U extends SubEvent<K>[T]>(event: T, listener: U) => void
|
||||
events: AsyncGenerator<Event<K>, void, unknown>
|
||||
}
|
||||
|
||||
export type SubscriptionOptions = {
|
||||
id?: string
|
||||
verb?: 'REQ' | 'COUNT'
|
||||
skipVerification?: boolean
|
||||
alreadyHaveEvent?: null | ((id: string, relay: string) => boolean)
|
||||
eoseSubTimeout?: number
|
||||
}
|
||||
|
||||
const newListeners = (): { [TK in keyof RelayEvent]: RelayEvent[TK][] } => ({
|
||||
connect: [],
|
||||
disconnect: [],
|
||||
error: [],
|
||||
notice: [],
|
||||
auth: [],
|
||||
})
|
||||
|
||||
export function relayInit(
|
||||
url: string,
|
||||
options: {
|
||||
getTimeout?: number
|
||||
listTimeout?: number
|
||||
countTimeout?: number
|
||||
} = {},
|
||||
): Relay {
|
||||
let { listTimeout = 3000, getTimeout = 3000, countTimeout = 3000 } = options
|
||||
|
||||
var ws: WebSocket
|
||||
var openSubs: { [id: string]: { filters: Filter[] } & SubscriptionOptions } = {}
|
||||
var listeners = newListeners()
|
||||
var subListeners: {
|
||||
[subid: string]: { [TK in keyof SubEvent<any>]: SubEvent<any>[TK][] }
|
||||
} = {}
|
||||
var pubListeners: {
|
||||
[eventid: string]: {
|
||||
resolve: (_: unknown) => void
|
||||
reject: (err: Error) => void
|
||||
}
|
||||
} = {}
|
||||
|
||||
var connectionPromise: Promise<void> | undefined
|
||||
async function connectRelay(): Promise<void> {
|
||||
if (connectionPromise) return connectionPromise
|
||||
connectionPromise = new Promise((resolve, reject) => {
|
||||
try {
|
||||
ws = new WebSocket(url)
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
listeners.connect.forEach(cb => cb())
|
||||
resolve()
|
||||
}
|
||||
ws.onerror = () => {
|
||||
connectionPromise = undefined
|
||||
listeners.error.forEach(cb => cb())
|
||||
reject()
|
||||
}
|
||||
ws.onclose = async () => {
|
||||
connectionPromise = undefined
|
||||
listeners.disconnect.forEach(cb => cb())
|
||||
}
|
||||
|
||||
let incomingMessageQueue: MessageQueue = new MessageQueue()
|
||||
let handleNextInterval: any
|
||||
|
||||
ws.onmessage = e => {
|
||||
incomingMessageQueue.enqueue(e.data)
|
||||
if (!handleNextInterval) {
|
||||
handleNextInterval = setInterval(handleNext, 0)
|
||||
}
|
||||
}
|
||||
|
||||
function handleNext() {
|
||||
if (incomingMessageQueue.size === 0) {
|
||||
clearInterval(handleNextInterval)
|
||||
handleNextInterval = null
|
||||
return
|
||||
}
|
||||
|
||||
var json = incomingMessageQueue.dequeue()
|
||||
if (!json) return
|
||||
|
||||
let subid = getSubscriptionId(json)
|
||||
if (subid) {
|
||||
let so = openSubs[subid]
|
||||
if (so && so.alreadyHaveEvent && so.alreadyHaveEvent(getHex64(json, 'id'), url)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let data = JSON.parse(json)
|
||||
|
||||
// we won't do any checks against the data since all failures (i.e. invalid messages from relays)
|
||||
// will naturally be caught by the encompassing try..catch block
|
||||
|
||||
switch (data[0]) {
|
||||
case 'EVENT': {
|
||||
let id = data[1]
|
||||
let event = data[2]
|
||||
if (
|
||||
validateEvent(event) &&
|
||||
openSubs[id] &&
|
||||
(openSubs[id].skipVerification || verifySignature(event)) &&
|
||||
matchFilters(openSubs[id].filters, event)
|
||||
) {
|
||||
openSubs[id]
|
||||
; (subListeners[id]?.event || []).forEach(cb => cb(event))
|
||||
}
|
||||
return
|
||||
}
|
||||
case 'COUNT':
|
||||
let id = data[1]
|
||||
let payload = data[2]
|
||||
if (openSubs[id]) {
|
||||
; (subListeners[id]?.count || []).forEach(cb => cb(payload))
|
||||
}
|
||||
return
|
||||
case 'EOSE': {
|
||||
let id = data[1]
|
||||
if (id in subListeners) {
|
||||
subListeners[id].eose.forEach(cb => cb())
|
||||
subListeners[id].eose = [] // 'eose' only happens once per sub, so stop listeners here
|
||||
}
|
||||
return
|
||||
}
|
||||
case 'OK': {
|
||||
let id: string = data[1]
|
||||
let ok: boolean = data[2]
|
||||
let reason: string = data[3] || ''
|
||||
if (id in pubListeners) {
|
||||
let { resolve, reject } = pubListeners[id]
|
||||
if (ok) resolve(null)
|
||||
else reject(new Error(reason))
|
||||
}
|
||||
return
|
||||
}
|
||||
case 'NOTICE':
|
||||
let notice = data[1]
|
||||
listeners.notice.forEach(cb => cb(notice))
|
||||
return
|
||||
case 'AUTH': {
|
||||
let challenge = data[1]
|
||||
listeners.auth?.forEach(cb => cb(challenge))
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return connectionPromise
|
||||
}
|
||||
|
||||
function connected() {
|
||||
return ws?.readyState === 1
|
||||
}
|
||||
|
||||
async function connect(): Promise<void> {
|
||||
if (connected()) return // ws already open
|
||||
await connectRelay()
|
||||
}
|
||||
|
||||
async function trySend(params: [string, ...any]) {
|
||||
let msg = JSON.stringify(params)
|
||||
if (!connected()) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
if (!connected()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
try {
|
||||
ws.send(msg)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
const sub = <K extends number = number>(
|
||||
filters: Filter<K>[],
|
||||
{
|
||||
verb = 'REQ',
|
||||
skipVerification = false,
|
||||
alreadyHaveEvent = null,
|
||||
id = Math.random().toString().slice(2),
|
||||
}: SubscriptionOptions = {},
|
||||
): Sub<K> => {
|
||||
let subid = id
|
||||
|
||||
openSubs[subid] = {
|
||||
id: subid,
|
||||
filters,
|
||||
skipVerification,
|
||||
alreadyHaveEvent,
|
||||
}
|
||||
trySend([verb, subid, ...filters])
|
||||
|
||||
let subscription: Sub<K> = {
|
||||
sub: (newFilters, newOpts = {}) =>
|
||||
sub(newFilters || filters, {
|
||||
skipVerification: newOpts.skipVerification || skipVerification,
|
||||
alreadyHaveEvent: newOpts.alreadyHaveEvent || alreadyHaveEvent,
|
||||
id: subid,
|
||||
}),
|
||||
unsub: () => {
|
||||
delete openSubs[subid]
|
||||
delete subListeners[subid]
|
||||
trySend(['CLOSE', subid])
|
||||
},
|
||||
on: (type, cb) => {
|
||||
subListeners[subid] = subListeners[subid] || {
|
||||
event: [],
|
||||
count: [],
|
||||
eose: [],
|
||||
}
|
||||
//@ts-ignore
|
||||
subListeners[subid][type].push(cb)
|
||||
},
|
||||
off: (type, cb): void => {
|
||||
let listeners = subListeners[subid]
|
||||
//@ts-ignore
|
||||
let idx = listeners[type].indexOf(cb)
|
||||
if (idx >= 0) listeners[type].splice(idx, 1)
|
||||
},
|
||||
get events() {
|
||||
return eventsGenerator(subscription)
|
||||
},
|
||||
}
|
||||
|
||||
return subscription
|
||||
}
|
||||
|
||||
function _publishEvent(event: Event<number>, type: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!event.id) {
|
||||
reject(new Error(`event ${event} has no id`))
|
||||
return
|
||||
}
|
||||
|
||||
let id = event.id
|
||||
trySend([type, event])
|
||||
pubListeners[id] = { resolve, reject }
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
url,
|
||||
sub,
|
||||
on: <T extends keyof RelayEvent, U extends RelayEvent[T]>(type: T, cb: U): void => {
|
||||
//@ts-ignore
|
||||
listeners[type].push(cb)
|
||||
if (type === 'connect' && ws?.readyState === 1) {
|
||||
// i would love to know why we need this
|
||||
; (cb as () => void)()
|
||||
}
|
||||
},
|
||||
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(type: T, cb: U): void => {
|
||||
//@ts-ignore
|
||||
let index = listeners[type].indexOf(cb)
|
||||
if (index !== -1) listeners[type].splice(index, 1)
|
||||
},
|
||||
list: (filters, opts?: SubscriptionOptions) =>
|
||||
new Promise(resolve => {
|
||||
let s = sub(filters, opts)
|
||||
let events: Event<any>[] = []
|
||||
let timeout = setTimeout(() => {
|
||||
s.unsub()
|
||||
resolve(events)
|
||||
}, listTimeout)
|
||||
s.on('eose', () => {
|
||||
s.unsub()
|
||||
clearTimeout(timeout)
|
||||
resolve(events)
|
||||
})
|
||||
s.on('event', event => {
|
||||
events.push(event)
|
||||
})
|
||||
}),
|
||||
get: (filter, opts?: SubscriptionOptions) =>
|
||||
new Promise(resolve => {
|
||||
let s = sub([filter], opts)
|
||||
let timeout = setTimeout(() => {
|
||||
s.unsub()
|
||||
resolve(null)
|
||||
}, getTimeout)
|
||||
s.on('event', event => {
|
||||
s.unsub()
|
||||
clearTimeout(timeout)
|
||||
resolve(event)
|
||||
})
|
||||
}),
|
||||
count: (filters: Filter[]): Promise<CountPayload | null> =>
|
||||
new Promise(resolve => {
|
||||
let s = sub(filters, { ...sub, verb: 'COUNT' })
|
||||
let timeout = setTimeout(() => {
|
||||
s.unsub()
|
||||
resolve(null)
|
||||
}, countTimeout)
|
||||
s.on('count', (event: CountPayload) => {
|
||||
s.unsub()
|
||||
clearTimeout(timeout)
|
||||
resolve(event)
|
||||
})
|
||||
}),
|
||||
async publish(event): Promise<void> {
|
||||
await _publishEvent(event, 'EVENT')
|
||||
},
|
||||
async auth(event): Promise<void> {
|
||||
await _publishEvent(event, 'AUTH')
|
||||
},
|
||||
connect,
|
||||
close(): void {
|
||||
listeners = newListeners()
|
||||
subListeners = {}
|
||||
pubListeners = {}
|
||||
if (ws?.readyState === WebSocket.OPEN) {
|
||||
ws.close()
|
||||
}
|
||||
},
|
||||
get status() {
|
||||
return ws?.readyState ?? 3
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function* eventsGenerator<K extends number>(sub: Sub<K>): AsyncGenerator<Event<K>, void, unknown> {
|
||||
let nextResolve: ((event: Event<K>) => void) | undefined
|
||||
const eventQueue: Event<K>[] = []
|
||||
|
||||
const pushToQueue = (event: Event<K>) => {
|
||||
if (nextResolve) {
|
||||
nextResolve(event)
|
||||
nextResolve = undefined
|
||||
} else {
|
||||
eventQueue.push(event)
|
||||
}
|
||||
}
|
||||
|
||||
sub.on('event', pushToQueue)
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
if (eventQueue.length > 0) {
|
||||
yield eventQueue.shift()!
|
||||
} else {
|
||||
const event = await new Promise<Event<K>>(resolve => {
|
||||
nextResolve = resolve
|
||||
})
|
||||
yield event
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
sub.off('event', pushToQueue)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,169 +1,169 @@
|
|||
import type { Event } from './event.js'
|
||||
|
||||
export const utf8Decoder = new TextDecoder('utf-8')
|
||||
export const utf8Encoder = new TextEncoder()
|
||||
|
||||
export function normalizeURL(url: string): string {
|
||||
let p = new URL(url)
|
||||
p.pathname = p.pathname.replace(/\/+/g, '/')
|
||||
if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)
|
||||
if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''
|
||||
p.searchParams.sort()
|
||||
p.hash = ''
|
||||
return p.toString()
|
||||
}
|
||||
|
||||
//
|
||||
// fast insert-into-sorted-array functions adapted from https://github.com/terrymorse58/fast-sorted-array
|
||||
//
|
||||
export function insertEventIntoDescendingList(sortedArray: Event<number>[], event: Event<number>) {
|
||||
let start = 0
|
||||
let end = sortedArray.length - 1
|
||||
let midPoint
|
||||
let position = start
|
||||
|
||||
if (end < 0) {
|
||||
position = 0
|
||||
} else if (event.created_at < sortedArray[end].created_at) {
|
||||
position = end + 1
|
||||
} else if (event.created_at >= sortedArray[start].created_at) {
|
||||
position = start
|
||||
} else
|
||||
while (true) {
|
||||
if (end <= start + 1) {
|
||||
position = end
|
||||
break
|
||||
}
|
||||
midPoint = Math.floor(start + (end - start) / 2)
|
||||
if (sortedArray[midPoint].created_at > event.created_at) {
|
||||
start = midPoint
|
||||
} else if (sortedArray[midPoint].created_at < event.created_at) {
|
||||
end = midPoint
|
||||
} else {
|
||||
// aMidPoint === num
|
||||
position = midPoint
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// insert when num is NOT already in (no duplicates)
|
||||
if (sortedArray[position]?.id !== event.id) {
|
||||
return [...sortedArray.slice(0, position), event, ...sortedArray.slice(position)]
|
||||
}
|
||||
|
||||
return sortedArray
|
||||
}
|
||||
|
||||
export function insertEventIntoAscendingList(sortedArray: Event<number>[], event: Event<number>) {
|
||||
let start = 0
|
||||
let end = sortedArray.length - 1
|
||||
let midPoint
|
||||
let position = start
|
||||
|
||||
if (end < 0) {
|
||||
position = 0
|
||||
} else if (event.created_at > sortedArray[end].created_at) {
|
||||
position = end + 1
|
||||
} else if (event.created_at <= sortedArray[start].created_at) {
|
||||
position = start
|
||||
} else
|
||||
while (true) {
|
||||
if (end <= start + 1) {
|
||||
position = end
|
||||
break
|
||||
}
|
||||
midPoint = Math.floor(start + (end - start) / 2)
|
||||
if (sortedArray[midPoint].created_at < event.created_at) {
|
||||
start = midPoint
|
||||
} else if (sortedArray[midPoint].created_at > event.created_at) {
|
||||
end = midPoint
|
||||
} else {
|
||||
// aMidPoint === num
|
||||
position = midPoint
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// insert when num is NOT already in (no duplicates)
|
||||
if (sortedArray[position]?.id !== event.id) {
|
||||
return [...sortedArray.slice(0, position), event, ...sortedArray.slice(position)]
|
||||
}
|
||||
|
||||
return sortedArray
|
||||
}
|
||||
|
||||
export class MessageNode {
|
||||
private _value: string
|
||||
private _next: MessageNode | null
|
||||
|
||||
public get value(): string {
|
||||
return this._value
|
||||
}
|
||||
public set value(message: string) {
|
||||
this._value = message
|
||||
}
|
||||
public get next(): MessageNode | null {
|
||||
return this._next
|
||||
}
|
||||
public set next(node: MessageNode | null) {
|
||||
this._next = node
|
||||
}
|
||||
|
||||
constructor(message: string) {
|
||||
this._value = message
|
||||
this._next = null
|
||||
}
|
||||
}
|
||||
|
||||
export class MessageQueue {
|
||||
private _first: MessageNode | null
|
||||
private _last: MessageNode | null
|
||||
|
||||
public get first(): MessageNode | null {
|
||||
return this._first
|
||||
}
|
||||
public set first(messageNode: MessageNode | null) {
|
||||
this._first = messageNode
|
||||
}
|
||||
public get last(): MessageNode | null {
|
||||
return this._last
|
||||
}
|
||||
public set last(messageNode: MessageNode | null) {
|
||||
this._last = messageNode
|
||||
}
|
||||
private _size: number
|
||||
public get size(): number {
|
||||
return this._size
|
||||
}
|
||||
public set size(v: number) {
|
||||
this._size = v
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._first = null
|
||||
this._last = null
|
||||
this._size = 0
|
||||
}
|
||||
enqueue(message: string): boolean {
|
||||
const newNode = new MessageNode(message)
|
||||
if (this._size === 0 || !this._last) {
|
||||
this._first = newNode
|
||||
this._last = newNode
|
||||
} else {
|
||||
this._last.next = newNode
|
||||
this._last = newNode
|
||||
}
|
||||
this._size++
|
||||
return true
|
||||
}
|
||||
dequeue(): string | null {
|
||||
if (this._size === 0 || !this._first) return null
|
||||
|
||||
let prev = this._first
|
||||
this._first = prev.next
|
||||
prev.next = null
|
||||
|
||||
this._size--
|
||||
return prev.value
|
||||
}
|
||||
import type { Event } from './event.js'
|
||||
|
||||
export const utf8Decoder = new TextDecoder('utf-8')
|
||||
export const utf8Encoder = new TextEncoder()
|
||||
|
||||
export function normalizeURL(url: string): string {
|
||||
let p = new URL(url)
|
||||
p.pathname = p.pathname.replace(/\/+/g, '/')
|
||||
if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)
|
||||
if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''
|
||||
p.searchParams.sort()
|
||||
p.hash = ''
|
||||
return p.toString()
|
||||
}
|
||||
|
||||
//
|
||||
// fast insert-into-sorted-array functions adapted from https://github.com/terrymorse58/fast-sorted-array
|
||||
//
|
||||
export function insertEventIntoDescendingList(sortedArray: Event<number>[], event: Event<number>) {
|
||||
let start = 0
|
||||
let end = sortedArray.length - 1
|
||||
let midPoint
|
||||
let position = start
|
||||
|
||||
if (end < 0) {
|
||||
position = 0
|
||||
} else if (event.created_at < sortedArray[end].created_at) {
|
||||
position = end + 1
|
||||
} else if (event.created_at >= sortedArray[start].created_at) {
|
||||
position = start
|
||||
} else
|
||||
while (true) {
|
||||
if (end <= start + 1) {
|
||||
position = end
|
||||
break
|
||||
}
|
||||
midPoint = Math.floor(start + (end - start) / 2)
|
||||
if (sortedArray[midPoint].created_at > event.created_at) {
|
||||
start = midPoint
|
||||
} else if (sortedArray[midPoint].created_at < event.created_at) {
|
||||
end = midPoint
|
||||
} else {
|
||||
// aMidPoint === num
|
||||
position = midPoint
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// insert when num is NOT already in (no duplicates)
|
||||
if (sortedArray[position]?.id !== event.id) {
|
||||
return [...sortedArray.slice(0, position), event, ...sortedArray.slice(position)]
|
||||
}
|
||||
|
||||
return sortedArray
|
||||
}
|
||||
|
||||
export function insertEventIntoAscendingList(sortedArray: Event<number>[], event: Event<number>) {
|
||||
let start = 0
|
||||
let end = sortedArray.length - 1
|
||||
let midPoint
|
||||
let position = start
|
||||
|
||||
if (end < 0) {
|
||||
position = 0
|
||||
} else if (event.created_at > sortedArray[end].created_at) {
|
||||
position = end + 1
|
||||
} else if (event.created_at <= sortedArray[start].created_at) {
|
||||
position = start
|
||||
} else
|
||||
while (true) {
|
||||
if (end <= start + 1) {
|
||||
position = end
|
||||
break
|
||||
}
|
||||
midPoint = Math.floor(start + (end - start) / 2)
|
||||
if (sortedArray[midPoint].created_at < event.created_at) {
|
||||
start = midPoint
|
||||
} else if (sortedArray[midPoint].created_at > event.created_at) {
|
||||
end = midPoint
|
||||
} else {
|
||||
// aMidPoint === num
|
||||
position = midPoint
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// insert when num is NOT already in (no duplicates)
|
||||
if (sortedArray[position]?.id !== event.id) {
|
||||
return [...sortedArray.slice(0, position), event, ...sortedArray.slice(position)]
|
||||
}
|
||||
|
||||
return sortedArray
|
||||
}
|
||||
|
||||
export class MessageNode {
|
||||
private _value: string
|
||||
private _next: MessageNode | null
|
||||
|
||||
public get value(): string {
|
||||
return this._value
|
||||
}
|
||||
public set value(message: string) {
|
||||
this._value = message
|
||||
}
|
||||
public get next(): MessageNode | null {
|
||||
return this._next
|
||||
}
|
||||
public set next(node: MessageNode | null) {
|
||||
this._next = node
|
||||
}
|
||||
|
||||
constructor(message: string) {
|
||||
this._value = message
|
||||
this._next = null
|
||||
}
|
||||
}
|
||||
|
||||
export class MessageQueue {
|
||||
private _first: MessageNode | null
|
||||
private _last: MessageNode | null
|
||||
|
||||
public get first(): MessageNode | null {
|
||||
return this._first
|
||||
}
|
||||
public set first(messageNode: MessageNode | null) {
|
||||
this._first = messageNode
|
||||
}
|
||||
public get last(): MessageNode | null {
|
||||
return this._last
|
||||
}
|
||||
public set last(messageNode: MessageNode | null) {
|
||||
this._last = messageNode
|
||||
}
|
||||
private _size: number
|
||||
public get size(): number {
|
||||
return this._size
|
||||
}
|
||||
public set size(v: number) {
|
||||
this._size = v
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._first = null
|
||||
this._last = null
|
||||
this._size = 0
|
||||
}
|
||||
enqueue(message: string): boolean {
|
||||
const newNode = new MessageNode(message)
|
||||
if (this._size === 0 || !this._last) {
|
||||
this._first = newNode
|
||||
this._last = newNode
|
||||
} else {
|
||||
this._last.next = newNode
|
||||
this._last = newNode
|
||||
}
|
||||
this._size++
|
||||
return true
|
||||
}
|
||||
dequeue(): string | null {
|
||||
if (this._size === 0 || !this._first) return null
|
||||
|
||||
let prev = this._first
|
||||
this._first = prev.next
|
||||
prev.next = null
|
||||
|
||||
this._size--
|
||||
return prev.value
|
||||
}
|
||||
}
|
||||
|
|
@ -1,219 +1,219 @@
|
|||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
import main from '../main/index.js'
|
||||
import Main from '../main/index.js'
|
||||
export default (mainHandler: Main): Types.ServerMethods => {
|
||||
return {
|
||||
GetUsageMetrics: async ({ ctx }) => {
|
||||
return mainHandler.metricsManager.GetUsageMetrics()
|
||||
},
|
||||
GetAppsMetrics: async ({ ctx, req }) => {
|
||||
return mainHandler.metricsManager.GetAppsMetrics(req)
|
||||
},
|
||||
GetLndMetrics: async ({ ctx, req }) => {
|
||||
return mainHandler.metricsManager.GetLndMetrics(req)
|
||||
},
|
||||
EncryptionExchange: async () => { },
|
||||
Health: async () => { await mainHandler.lnd.Health() },
|
||||
LndGetInfo: async ({ ctx }) => {
|
||||
const info = await mainHandler.lnd.GetInfo()
|
||||
return { alias: info.alias }
|
||||
},
|
||||
BanUser: async ({ ctx, req }) => {
|
||||
const err = Types.BanUserRequestValidate(req, {
|
||||
user_id_CustomCheck: id => id !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.appUserManager.BanUser(req.user_id)
|
||||
},
|
||||
SetMockInvoiceAsPaid: async ({ ctx, req }) => {
|
||||
const err = Types.SetMockInvoiceAsPaidRequestValidate(req, {
|
||||
invoice_CustomCheck: invoice => invoice !== '',
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
await mainHandler.paymentManager.SetMockInvoiceAsPaid(req)
|
||||
},
|
||||
UserHealth: async () => { },
|
||||
GetUserInfo: ({ ctx }) => mainHandler.appUserManager.GetUserInfo(ctx),
|
||||
GetUserOperations: async ({ ctx, req }) => {
|
||||
return mainHandler.paymentManager.GetUserOperations(ctx.user_id, req)
|
||||
},
|
||||
OpenChannel: async ({ ctx, req }) => {
|
||||
const err = Types.OpenChannelRequestValidate(req, {
|
||||
fundingAmount_CustomCheck: amt => amt > 0,
|
||||
pushAmount_CustomCheck: amt => amt > 0,
|
||||
destination_CustomCheck: dest => dest !== ""
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.paymentManager.OpenChannel(ctx.user_id, req)
|
||||
},
|
||||
NewAddress: ({ ctx, req }) => mainHandler.paymentManager.NewAddress(ctx, req),
|
||||
PayAddress: async ({ ctx, req }) => {
|
||||
const err = Types.PayAddressRequestValidate(req, {
|
||||
address_CustomCheck: addr => addr !== '',
|
||||
amoutSats_CustomCheck: amt => amt > 0,
|
||||
satsPerVByte_CustomCheck: spb => spb > 0
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.paymentManager.PayAddress(ctx, req)
|
||||
},
|
||||
NewInvoice: ({ ctx, req }) => mainHandler.appUserManager.NewInvoice(ctx, req),
|
||||
DecodeInvoice: async ({ ctx, 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.appUserManager.PayInvoice(ctx, req)
|
||||
},
|
||||
GetLnurlWithdrawLink: ({ ctx }) => mainHandler.paymentManager.GetLnurlWithdrawLink(ctx),
|
||||
GetLnurlWithdrawInfo: async ({ ctx, query }) => {
|
||||
if (!query.k1) {
|
||||
throw new Error("invalid lnurl withdraw to get info")
|
||||
}
|
||||
return mainHandler.paymentManager.GetLnurlWithdrawInfo(query.k1)
|
||||
},
|
||||
HandleLnurlWithdraw: async ({ query }) => {
|
||||
if (!query.k1 || !query.pr) {
|
||||
throw new Error("invalid lnurl withdraw to handle")
|
||||
}
|
||||
return mainHandler.paymentManager.HandleLnurlWithdraw(query.k1, query.pr)
|
||||
},
|
||||
GetLnurlPayLink: ({ ctx }) => mainHandler.paymentManager.GetLnurlPayLink(ctx),
|
||||
GetLnurlPayInfo: async ({ ctx, query }) => {
|
||||
if (!query.k1) {
|
||||
throw new Error("invalid lnurl pay to get info")
|
||||
}
|
||||
return mainHandler.paymentManager.GetLnurlPayInfo(query.k1)
|
||||
},
|
||||
HandleLnurlPay: async ({ ctx, query }) => {
|
||||
return mainHandler.paymentManager.HandleLnurlPay(query)
|
||||
},
|
||||
HandleLnurlAddress: async ({ ctx, params }) => {
|
||||
if (!params.address_name) {
|
||||
throw new Error("invalid address_name to lnurl address")
|
||||
}
|
||||
return mainHandler.paymentManager.HandleLnurlAddress(params.address_name)
|
||||
},
|
||||
AddProduct: async ({ ctx, req }) => {
|
||||
return mainHandler.productManager.AddProduct(ctx.user_id, req)
|
||||
},
|
||||
NewProductInvoice: async ({ query }) => {
|
||||
if (!query.id) {
|
||||
throw new Error("product id must be non empty")
|
||||
}
|
||||
return mainHandler.productManager.NewProductInvoice(query.id)
|
||||
},
|
||||
GetLNURLChannelLink: async ({ ctx }) => {
|
||||
throw new Error("unimplemented")
|
||||
},
|
||||
AddApp: async ({ ctx, req }) => {
|
||||
const err = Types.AuthAppRequestValidate(req, {
|
||||
name_CustomCheck: name => name !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.AddApp(req)
|
||||
},
|
||||
AuthApp: async ({ ctx, req }) => {
|
||||
const err = Types.AuthAppRequestValidate(req, {
|
||||
name_CustomCheck: name => name !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.AuthApp(req)
|
||||
},
|
||||
GetApp: async ({ ctx }) => {
|
||||
return mainHandler.applicationManager.GetApp(ctx.app_id)
|
||||
},
|
||||
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)
|
||||
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)
|
||||
await 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)
|
||||
await mainHandler.applicationManager.SendAppUserToAppPayment(ctx.app_id, req)
|
||||
},
|
||||
GetAppUserLNURLInfo: async ({ ctx, req }) => {
|
||||
const err = Types.GetAppUserLNURLInfoRequestValidate(req, {
|
||||
user_identifier_CustomCheck: id => id !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.GetAppUserLNURLInfo(ctx.app_id, req)
|
||||
},
|
||||
SetMockAppUserBalance: async ({ ctx, req }) => {
|
||||
const err = Types.SetMockAppUserBalanceRequestValidate(req, {
|
||||
user_identifier_CustomCheck: id => id !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
await mainHandler.applicationManager.SetMockAppUserBalance(ctx.app_id, req)
|
||||
},
|
||||
SetMockAppBalance: async ({ ctx, req }) => {
|
||||
await mainHandler.applicationManager.SetMockAppBalance(ctx.app_id, req)
|
||||
},
|
||||
GetLiveUserOperations: async ({ ctx, cb }) => {
|
||||
},
|
||||
GetMigrationUpdate: async ({ ctx, cb }) => {
|
||||
},
|
||||
RequestNPubLinkingToken: async ({ ctx, req }) => {
|
||||
const err = Types.RequestNPubLinkingTokenRequestValidate(req, {
|
||||
user_identifier_CustomCheck: userIdentifier => userIdentifier !== '',
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.RequestNPubLinkingToken(ctx.app_id, req)
|
||||
},
|
||||
LinkNPubThroughToken: async ({ ctx, req }) => {
|
||||
const err = Types.LinkNPubThroughTokenRequestValidate(req, {
|
||||
nostr_pub_CustomCheck: nostrPub => nostrPub !== '',
|
||||
token_CustomCheck: token => token !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.LinkNpubThroughToken(ctx, req)
|
||||
}
|
||||
}
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
import main from '../main/index.js'
|
||||
import Main from '../main/index.js'
|
||||
export default (mainHandler: Main): Types.ServerMethods => {
|
||||
return {
|
||||
GetUsageMetrics: async ({ ctx }) => {
|
||||
return mainHandler.metricsManager.GetUsageMetrics()
|
||||
},
|
||||
GetAppsMetrics: async ({ ctx, req }) => {
|
||||
return mainHandler.metricsManager.GetAppsMetrics(req)
|
||||
},
|
||||
GetLndMetrics: async ({ ctx, req }) => {
|
||||
return mainHandler.metricsManager.GetLndMetrics(req)
|
||||
},
|
||||
EncryptionExchange: async () => { },
|
||||
Health: async () => { await mainHandler.lnd.Health() },
|
||||
LndGetInfo: async ({ ctx }) => {
|
||||
const info = await mainHandler.lnd.GetInfo()
|
||||
return { alias: info.alias }
|
||||
},
|
||||
BanUser: async ({ ctx, req }) => {
|
||||
const err = Types.BanUserRequestValidate(req, {
|
||||
user_id_CustomCheck: id => id !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.appUserManager.BanUser(req.user_id)
|
||||
},
|
||||
SetMockInvoiceAsPaid: async ({ ctx, req }) => {
|
||||
const err = Types.SetMockInvoiceAsPaidRequestValidate(req, {
|
||||
invoice_CustomCheck: invoice => invoice !== '',
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
await mainHandler.paymentManager.SetMockInvoiceAsPaid(req)
|
||||
},
|
||||
UserHealth: async () => { },
|
||||
GetUserInfo: ({ ctx }) => mainHandler.appUserManager.GetUserInfo(ctx),
|
||||
GetUserOperations: async ({ ctx, req }) => {
|
||||
return mainHandler.paymentManager.GetUserOperations(ctx.user_id, req)
|
||||
},
|
||||
OpenChannel: async ({ ctx, req }) => {
|
||||
const err = Types.OpenChannelRequestValidate(req, {
|
||||
fundingAmount_CustomCheck: amt => amt > 0,
|
||||
pushAmount_CustomCheck: amt => amt > 0,
|
||||
destination_CustomCheck: dest => dest !== ""
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.paymentManager.OpenChannel(ctx.user_id, req)
|
||||
},
|
||||
NewAddress: ({ ctx, req }) => mainHandler.paymentManager.NewAddress(ctx, req),
|
||||
PayAddress: async ({ ctx, req }) => {
|
||||
const err = Types.PayAddressRequestValidate(req, {
|
||||
address_CustomCheck: addr => addr !== '',
|
||||
amoutSats_CustomCheck: amt => amt > 0,
|
||||
satsPerVByte_CustomCheck: spb => spb > 0
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.paymentManager.PayAddress(ctx, req)
|
||||
},
|
||||
NewInvoice: ({ ctx, req }) => mainHandler.appUserManager.NewInvoice(ctx, req),
|
||||
DecodeInvoice: async ({ ctx, 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.appUserManager.PayInvoice(ctx, req)
|
||||
},
|
||||
GetLnurlWithdrawLink: ({ ctx }) => mainHandler.paymentManager.GetLnurlWithdrawLink(ctx),
|
||||
GetLnurlWithdrawInfo: async ({ ctx, query }) => {
|
||||
if (!query.k1) {
|
||||
throw new Error("invalid lnurl withdraw to get info")
|
||||
}
|
||||
return mainHandler.paymentManager.GetLnurlWithdrawInfo(query.k1)
|
||||
},
|
||||
HandleLnurlWithdraw: async ({ query }) => {
|
||||
if (!query.k1 || !query.pr) {
|
||||
throw new Error("invalid lnurl withdraw to handle")
|
||||
}
|
||||
return mainHandler.paymentManager.HandleLnurlWithdraw(query.k1, query.pr)
|
||||
},
|
||||
GetLnurlPayLink: ({ ctx }) => mainHandler.paymentManager.GetLnurlPayLink(ctx),
|
||||
GetLnurlPayInfo: async ({ ctx, query }) => {
|
||||
if (!query.k1) {
|
||||
throw new Error("invalid lnurl pay to get info")
|
||||
}
|
||||
return mainHandler.paymentManager.GetLnurlPayInfo(query.k1)
|
||||
},
|
||||
HandleLnurlPay: async ({ ctx, query }) => {
|
||||
return mainHandler.paymentManager.HandleLnurlPay(query)
|
||||
},
|
||||
HandleLnurlAddress: async ({ ctx, params }) => {
|
||||
if (!params.address_name) {
|
||||
throw new Error("invalid address_name to lnurl address")
|
||||
}
|
||||
return mainHandler.paymentManager.HandleLnurlAddress(params.address_name)
|
||||
},
|
||||
AddProduct: async ({ ctx, req }) => {
|
||||
return mainHandler.productManager.AddProduct(ctx.user_id, req)
|
||||
},
|
||||
NewProductInvoice: async ({ query }) => {
|
||||
if (!query.id) {
|
||||
throw new Error("product id must be non empty")
|
||||
}
|
||||
return mainHandler.productManager.NewProductInvoice(query.id)
|
||||
},
|
||||
GetLNURLChannelLink: async ({ ctx }) => {
|
||||
throw new Error("unimplemented")
|
||||
},
|
||||
AddApp: async ({ ctx, req }) => {
|
||||
const err = Types.AuthAppRequestValidate(req, {
|
||||
name_CustomCheck: name => name !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.AddApp(req)
|
||||
},
|
||||
AuthApp: async ({ ctx, req }) => {
|
||||
const err = Types.AuthAppRequestValidate(req, {
|
||||
name_CustomCheck: name => name !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.AuthApp(req)
|
||||
},
|
||||
GetApp: async ({ ctx }) => {
|
||||
return mainHandler.applicationManager.GetApp(ctx.app_id)
|
||||
},
|
||||
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)
|
||||
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)
|
||||
await 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)
|
||||
await mainHandler.applicationManager.SendAppUserToAppPayment(ctx.app_id, req)
|
||||
},
|
||||
GetAppUserLNURLInfo: async ({ ctx, req }) => {
|
||||
const err = Types.GetAppUserLNURLInfoRequestValidate(req, {
|
||||
user_identifier_CustomCheck: id => id !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.GetAppUserLNURLInfo(ctx.app_id, req)
|
||||
},
|
||||
SetMockAppUserBalance: async ({ ctx, req }) => {
|
||||
const err = Types.SetMockAppUserBalanceRequestValidate(req, {
|
||||
user_identifier_CustomCheck: id => id !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
await mainHandler.applicationManager.SetMockAppUserBalance(ctx.app_id, req)
|
||||
},
|
||||
SetMockAppBalance: async ({ ctx, req }) => {
|
||||
await mainHandler.applicationManager.SetMockAppBalance(ctx.app_id, req)
|
||||
},
|
||||
GetLiveUserOperations: async ({ ctx, cb }) => {
|
||||
},
|
||||
GetMigrationUpdate: async ({ ctx, cb }) => {
|
||||
},
|
||||
RequestNPubLinkingToken: async ({ ctx, req }) => {
|
||||
const err = Types.RequestNPubLinkingTokenRequestValidate(req, {
|
||||
user_identifier_CustomCheck: userIdentifier => userIdentifier !== '',
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.RequestNPubLinkingToken(ctx.app_id, req)
|
||||
},
|
||||
LinkNPubThroughToken: async ({ ctx, req }) => {
|
||||
const err = Types.LinkNPubThroughTokenRequestValidate(req, {
|
||||
nostr_pub_CustomCheck: nostrPub => nostrPub !== '',
|
||||
token_CustomCheck: token => token !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.LinkNpubThroughToken(ctx, req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,172 +1,172 @@
|
|||
import crypto from 'crypto';
|
||||
import { Between, DataSource, EntityManager, FindOperator, IsNull, LessThanOrEqual, MoreThanOrEqual } from "typeorm"
|
||||
import { generatePrivateKey, getPublicKey } from 'nostr-tools';
|
||||
import { Application } from "./entity/Application.js"
|
||||
import UserStorage from './userStorage.js';
|
||||
import { ApplicationUser } from './entity/ApplicationUser.js';
|
||||
import { getLogger } from '../helpers/logger.js';
|
||||
import TransactionsQueue, { TX } from "./transactionsQueue.js";
|
||||
import { User } from './entity/User.js';
|
||||
export default class {
|
||||
DB: DataSource | EntityManager
|
||||
userStorage: UserStorage
|
||||
txQueue: TransactionsQueue
|
||||
constructor(DB: DataSource | EntityManager, userStorage: UserStorage, txQueue: TransactionsQueue) {
|
||||
this.DB = DB
|
||||
this.userStorage = userStorage
|
||||
this.txQueue = txQueue
|
||||
}
|
||||
|
||||
async AddApplication(name: string, allowUserCreation: boolean): Promise<Application> {
|
||||
return this.DB.transaction(async tx => {
|
||||
const owner = await this.userStorage.AddUser(0, tx)
|
||||
const repo = this.DB.getRepository(Application)
|
||||
const newApplication = repo.create({
|
||||
app_id: crypto.randomBytes(32).toString('hex'),
|
||||
name,
|
||||
owner,
|
||||
allow_user_creation: allowUserCreation
|
||||
})
|
||||
return tx.getRepository(Application).save(newApplication)
|
||||
})
|
||||
}
|
||||
|
||||
async GetApplicationByName(name: string, entityManager = this.DB) {
|
||||
const found = await entityManager.getRepository(Application).findOne({
|
||||
where: {
|
||||
name
|
||||
}
|
||||
})
|
||||
if (!found) {
|
||||
throw new Error(`application ${name} not found`)
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
async GetApplications(entityManager = this.DB): Promise<Application[]> {
|
||||
return entityManager.getRepository(Application).find()
|
||||
}
|
||||
async GetApplication(appId: string, entityManager = this.DB): Promise<Application> {
|
||||
if (!appId) {
|
||||
throw new Error("invalid app id provided")
|
||||
}
|
||||
const found = await entityManager.getRepository(Application).findOne({
|
||||
where: {
|
||||
app_id: appId
|
||||
}
|
||||
})
|
||||
if (!found) {
|
||||
throw new Error(`application ${appId} not found`)
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
async UpdateApplication(app: Application, update: Partial<Application>, entityManager = this.DB) {
|
||||
await entityManager.getRepository(Application).update(app.serial_id, update)
|
||||
}
|
||||
|
||||
async GenerateApplicationKeys(app: Application) {
|
||||
const priv = generatePrivateKey()
|
||||
const pub = getPublicKey(priv)
|
||||
await this.UpdateApplication(app, { nostr_private_key: priv, nostr_public_key: pub })
|
||||
return { privateKey: priv, publicKey: pub, appId: app.app_id, name: app.name }
|
||||
}
|
||||
|
||||
async AddApplicationUser(application: Application, userIdentifier: string, balance: number, nostrPub?: string) {
|
||||
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,
|
||||
identifier: userIdentifier,
|
||||
nostr_public_key: nostrPub
|
||||
})
|
||||
return repo.save(appUser)
|
||||
})
|
||||
}
|
||||
|
||||
async GetApplicationUserIfExists(application: Application, userIdentifier: string, entityManager = this.DB): Promise<ApplicationUser | null> {
|
||||
return entityManager.getRepository(ApplicationUser).findOne({ where: { identifier: userIdentifier, application: { serial_id: application.serial_id } } })
|
||||
}
|
||||
|
||||
async GetOrCreateNostrAppUser(application: Application, nostrPub: string, entityManager = this.DB): Promise<ApplicationUser> {
|
||||
if (!nostrPub) {
|
||||
throw new Error("no nostrPub provided")
|
||||
}
|
||||
const user = await entityManager.getRepository(ApplicationUser).findOne({ where: { nostr_public_key: nostrPub } })
|
||||
if (user) {
|
||||
//if (user.application.app_id !== application.app_id) {
|
||||
// throw new Error("tried to access a user of application:" + user.application.app_id + "from application:" + application.app_id)
|
||||
//}
|
||||
return user
|
||||
}
|
||||
if (!application.allow_user_creation) {
|
||||
throw new Error("user creation by client is not allowed in this app")
|
||||
}
|
||||
return this.AddApplicationUser(application, crypto.randomBytes(32).toString('hex'), 0, nostrPub)
|
||||
}
|
||||
|
||||
async FindNostrAppUser(nostrPub: string, entityManager = this.DB) {
|
||||
return entityManager.getRepository(ApplicationUser).findOne({ where: { nostr_public_key: nostrPub } })
|
||||
}
|
||||
|
||||
async GetOrCreateApplicationUser(application: Application, userIdentifier: string, balance: number, entityManager = this.DB): Promise<{ user: ApplicationUser, created: boolean }> {
|
||||
const user = await this.GetApplicationUserIfExists(application, userIdentifier, entityManager)
|
||||
if (user) {
|
||||
return { user, created: false }
|
||||
}
|
||||
return { user: await this.AddApplicationUser(application, userIdentifier, balance), created: true }
|
||||
}
|
||||
|
||||
async GetApplicationUser(application: Application, userIdentifier: string, entityManager = this.DB): Promise<ApplicationUser> {
|
||||
const found = await this.GetApplicationUserIfExists(application, userIdentifier, entityManager)
|
||||
if (!found) {
|
||||
getLogger({ appName: application.name })("user", userIdentifier, "not found", application.name)
|
||||
throw new Error(`application user not found`)
|
||||
}
|
||||
|
||||
if (found.application.app_id !== application.app_id) {
|
||||
throw new Error("requested user does not belong to requestor application")
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
async GetApplicationUsers(application: Application | null, { from, to }: { from?: number, to?: number }, entityManager = this.DB) {
|
||||
const q = application ? { app_id: application.app_id } : IsNull()
|
||||
let time: { created_at?: FindOperator<Date> } = {}
|
||||
if (!!from && !!to) {
|
||||
time.created_at = Between<Date>(new Date(from * 1000), new Date(to * 1000))
|
||||
} else if (!!from) {
|
||||
time.created_at = MoreThanOrEqual<Date>(new Date(from * 1000))
|
||||
} else if (!!to) {
|
||||
time.created_at = LessThanOrEqual<Date>(new Date(to * 1000))
|
||||
}
|
||||
return entityManager.getRepository(ApplicationUser).find({ where: { application: q, ...time } })
|
||||
}
|
||||
|
||||
async GetAppUserFromUser(application: Application, userId: string, entityManager = this.DB): Promise<ApplicationUser | null> {
|
||||
return entityManager.getRepository(ApplicationUser).findOne({ where: { user: { user_id: userId }, application: { app_id: application.app_id } } })
|
||||
}
|
||||
|
||||
async GetAllAppUsersFromUser(userId: string, entityManager = this.DB): Promise<ApplicationUser[]> {
|
||||
return entityManager.getRepository(ApplicationUser).find({ where: { user: { user_id: userId } } })
|
||||
}
|
||||
|
||||
async IsApplicationOwner(userId: string, entityManager = this.DB) {
|
||||
return entityManager.getRepository(Application).findOne({ where: { owner: { user_id: userId } } })
|
||||
}
|
||||
|
||||
|
||||
async AddNPubToApplicationUser(serialId: number, nPub: string, entityManager = this.DB) {
|
||||
return entityManager.getRepository(ApplicationUser).update(serialId, { nostr_public_key: nPub })
|
||||
|
||||
}
|
||||
|
||||
|
||||
async RemoveApplicationUserAndBaseUser(appUser: ApplicationUser, entityManager = this.DB) {
|
||||
const baseUser = appUser.user;
|
||||
await entityManager.getRepository(ApplicationUser).remove(appUser);
|
||||
await entityManager.getRepository(User).remove(baseUser);
|
||||
}
|
||||
import crypto from 'crypto';
|
||||
import { Between, DataSource, EntityManager, FindOperator, IsNull, LessThanOrEqual, MoreThanOrEqual } from "typeorm"
|
||||
import { generatePrivateKey, getPublicKey } from 'nostr-tools';
|
||||
import { Application } from "./entity/Application.js"
|
||||
import UserStorage from './userStorage.js';
|
||||
import { ApplicationUser } from './entity/ApplicationUser.js';
|
||||
import { getLogger } from '../helpers/logger.js';
|
||||
import TransactionsQueue, { TX } from "./transactionsQueue.js";
|
||||
import { User } from './entity/User.js';
|
||||
export default class {
|
||||
DB: DataSource | EntityManager
|
||||
userStorage: UserStorage
|
||||
txQueue: TransactionsQueue
|
||||
constructor(DB: DataSource | EntityManager, userStorage: UserStorage, txQueue: TransactionsQueue) {
|
||||
this.DB = DB
|
||||
this.userStorage = userStorage
|
||||
this.txQueue = txQueue
|
||||
}
|
||||
|
||||
async AddApplication(name: string, allowUserCreation: boolean): Promise<Application> {
|
||||
return this.DB.transaction(async tx => {
|
||||
const owner = await this.userStorage.AddUser(0, tx)
|
||||
const repo = this.DB.getRepository(Application)
|
||||
const newApplication = repo.create({
|
||||
app_id: crypto.randomBytes(32).toString('hex'),
|
||||
name,
|
||||
owner,
|
||||
allow_user_creation: allowUserCreation
|
||||
})
|
||||
return tx.getRepository(Application).save(newApplication)
|
||||
})
|
||||
}
|
||||
|
||||
async GetApplicationByName(name: string, entityManager = this.DB) {
|
||||
const found = await entityManager.getRepository(Application).findOne({
|
||||
where: {
|
||||
name
|
||||
}
|
||||
})
|
||||
if (!found) {
|
||||
throw new Error(`application ${name} not found`)
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
async GetApplications(entityManager = this.DB): Promise<Application[]> {
|
||||
return entityManager.getRepository(Application).find()
|
||||
}
|
||||
async GetApplication(appId: string, entityManager = this.DB): Promise<Application> {
|
||||
if (!appId) {
|
||||
throw new Error("invalid app id provided")
|
||||
}
|
||||
const found = await entityManager.getRepository(Application).findOne({
|
||||
where: {
|
||||
app_id: appId
|
||||
}
|
||||
})
|
||||
if (!found) {
|
||||
throw new Error(`application ${appId} not found`)
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
async UpdateApplication(app: Application, update: Partial<Application>, entityManager = this.DB) {
|
||||
await entityManager.getRepository(Application).update(app.serial_id, update)
|
||||
}
|
||||
|
||||
async GenerateApplicationKeys(app: Application) {
|
||||
const priv = generatePrivateKey()
|
||||
const pub = getPublicKey(priv)
|
||||
await this.UpdateApplication(app, { nostr_private_key: priv, nostr_public_key: pub })
|
||||
return { privateKey: priv, publicKey: pub, appId: app.app_id, name: app.name }
|
||||
}
|
||||
|
||||
async AddApplicationUser(application: Application, userIdentifier: string, balance: number, nostrPub?: string) {
|
||||
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,
|
||||
identifier: userIdentifier,
|
||||
nostr_public_key: nostrPub
|
||||
})
|
||||
return repo.save(appUser)
|
||||
})
|
||||
}
|
||||
|
||||
async GetApplicationUserIfExists(application: Application, userIdentifier: string, entityManager = this.DB): Promise<ApplicationUser | null> {
|
||||
return entityManager.getRepository(ApplicationUser).findOne({ where: { identifier: userIdentifier, application: { serial_id: application.serial_id } } })
|
||||
}
|
||||
|
||||
async GetOrCreateNostrAppUser(application: Application, nostrPub: string, entityManager = this.DB): Promise<ApplicationUser> {
|
||||
if (!nostrPub) {
|
||||
throw new Error("no nostrPub provided")
|
||||
}
|
||||
const user = await entityManager.getRepository(ApplicationUser).findOne({ where: { nostr_public_key: nostrPub } })
|
||||
if (user) {
|
||||
//if (user.application.app_id !== application.app_id) {
|
||||
// throw new Error("tried to access a user of application:" + user.application.app_id + "from application:" + application.app_id)
|
||||
//}
|
||||
return user
|
||||
}
|
||||
if (!application.allow_user_creation) {
|
||||
throw new Error("user creation by client is not allowed in this app")
|
||||
}
|
||||
return this.AddApplicationUser(application, crypto.randomBytes(32).toString('hex'), 0, nostrPub)
|
||||
}
|
||||
|
||||
async FindNostrAppUser(nostrPub: string, entityManager = this.DB) {
|
||||
return entityManager.getRepository(ApplicationUser).findOne({ where: { nostr_public_key: nostrPub } })
|
||||
}
|
||||
|
||||
async GetOrCreateApplicationUser(application: Application, userIdentifier: string, balance: number, entityManager = this.DB): Promise<{ user: ApplicationUser, created: boolean }> {
|
||||
const user = await this.GetApplicationUserIfExists(application, userIdentifier, entityManager)
|
||||
if (user) {
|
||||
return { user, created: false }
|
||||
}
|
||||
return { user: await this.AddApplicationUser(application, userIdentifier, balance), created: true }
|
||||
}
|
||||
|
||||
async GetApplicationUser(application: Application, userIdentifier: string, entityManager = this.DB): Promise<ApplicationUser> {
|
||||
const found = await this.GetApplicationUserIfExists(application, userIdentifier, entityManager)
|
||||
if (!found) {
|
||||
getLogger({ appName: application.name })("user", userIdentifier, "not found", application.name)
|
||||
throw new Error(`application user not found`)
|
||||
}
|
||||
|
||||
if (found.application.app_id !== application.app_id) {
|
||||
throw new Error("requested user does not belong to requestor application")
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
async GetApplicationUsers(application: Application | null, { from, to }: { from?: number, to?: number }, entityManager = this.DB) {
|
||||
const q = application ? { app_id: application.app_id } : IsNull()
|
||||
let time: { created_at?: FindOperator<Date> } = {}
|
||||
if (!!from && !!to) {
|
||||
time.created_at = Between<Date>(new Date(from * 1000), new Date(to * 1000))
|
||||
} else if (!!from) {
|
||||
time.created_at = MoreThanOrEqual<Date>(new Date(from * 1000))
|
||||
} else if (!!to) {
|
||||
time.created_at = LessThanOrEqual<Date>(new Date(to * 1000))
|
||||
}
|
||||
return entityManager.getRepository(ApplicationUser).find({ where: { application: q, ...time } })
|
||||
}
|
||||
|
||||
async GetAppUserFromUser(application: Application, userId: string, entityManager = this.DB): Promise<ApplicationUser | null> {
|
||||
return entityManager.getRepository(ApplicationUser).findOne({ where: { user: { user_id: userId }, application: { app_id: application.app_id } } })
|
||||
}
|
||||
|
||||
async GetAllAppUsersFromUser(userId: string, entityManager = this.DB): Promise<ApplicationUser[]> {
|
||||
return entityManager.getRepository(ApplicationUser).find({ where: { user: { user_id: userId } } })
|
||||
}
|
||||
|
||||
async IsApplicationOwner(userId: string, entityManager = this.DB) {
|
||||
return entityManager.getRepository(Application).findOne({ where: { owner: { user_id: userId } } })
|
||||
}
|
||||
|
||||
|
||||
async AddNPubToApplicationUser(serialId: number, nPub: string, entityManager = this.DB) {
|
||||
return entityManager.getRepository(ApplicationUser).update(serialId, { nostr_public_key: nPub })
|
||||
|
||||
}
|
||||
|
||||
|
||||
async RemoveApplicationUserAndBaseUser(appUser: ApplicationUser, entityManager = this.DB) {
|
||||
const baseUser = appUser.user;
|
||||
await entityManager.getRepository(ApplicationUser).remove(appUser);
|
||||
await entityManager.getRepository(User).remove(baseUser);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +1,84 @@
|
|||
import "reflect-metadata"
|
||||
import { DataSource, Migration } from "typeorm"
|
||||
import { AddressReceivingTransaction } from "./entity/AddressReceivingTransaction.js"
|
||||
import { User } from "./entity/User.js"
|
||||
import { UserReceivingAddress } from "./entity/UserReceivingAddress.js"
|
||||
import { UserReceivingInvoice } from "./entity/UserReceivingInvoice.js"
|
||||
import { UserInvoicePayment } from "./entity/UserInvoicePayment.js"
|
||||
import { EnvMustBeNonEmptyString } from "../helpers/envParser.js"
|
||||
import { UserTransactionPayment } from "./entity/UserTransactionPayment.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"
|
||||
import { BalanceEvent } from "./entity/BalanceEvent.js"
|
||||
import { ChannelBalanceEvent } from "./entity/ChannelsBalanceEvent.js"
|
||||
import { getLogger } from "../helpers/logger.js"
|
||||
import { ChannelRouting } from "./entity/ChannelRouting.js"
|
||||
|
||||
|
||||
export type DbSettings = {
|
||||
databaseFile: string
|
||||
migrate: boolean
|
||||
metricsDatabaseFile: string
|
||||
}
|
||||
export const LoadDbSettingsFromEnv = (): DbSettings => {
|
||||
return {
|
||||
databaseFile: process.env.DATABASE_FILE || "db.sqlite",
|
||||
migrate: process.env.MIGRATE_DB === 'true' || false,
|
||||
metricsDatabaseFile: process.env.METRICS_DATABASE_FILE || "metrics.sqlite"
|
||||
}
|
||||
}
|
||||
|
||||
export const newMetricsDb = async (settings: DbSettings, metricsMigrations: Function[]): Promise<{ source: DataSource, executedMigrations: Migration[] }> => {
|
||||
const source = await new DataSource({
|
||||
type: "sqlite",
|
||||
database: settings.metricsDatabaseFile,
|
||||
entities: [BalanceEvent, ChannelBalanceEvent, ChannelRouting],
|
||||
migrations: metricsMigrations
|
||||
}).initialize();
|
||||
const log = getLogger({});
|
||||
const pendingMigrations = await source.showMigrations()
|
||||
if (pendingMigrations) {
|
||||
log("Migrations found, migrating...")
|
||||
const executedMigrations = await source.runMigrations({ transaction: 'all' })
|
||||
return { source, executedMigrations }
|
||||
}
|
||||
return { source, executedMigrations: [] }
|
||||
|
||||
}
|
||||
|
||||
export default async (settings: DbSettings, migrations: Function[]): Promise<{ source: DataSource, executedMigrations: Migration[] }> => {
|
||||
const source = await new DataSource({
|
||||
type: "sqlite",
|
||||
database: settings.databaseFile,
|
||||
// logging: true,
|
||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment],
|
||||
//synchronize: true,
|
||||
migrations
|
||||
}).initialize()
|
||||
const log = getLogger({})
|
||||
const pendingMigrations = await source.showMigrations()
|
||||
if (pendingMigrations) {
|
||||
log("migrations found, migrating...")
|
||||
const executedMigrations = await source.runMigrations({ transaction: 'all' })
|
||||
return { source, executedMigrations }
|
||||
}
|
||||
return { source, executedMigrations: [] }
|
||||
}
|
||||
|
||||
export const runFakeMigration = async (databaseFile: string, migrations: Function[]) => {
|
||||
const source = await new DataSource({
|
||||
type: "sqlite",
|
||||
database: databaseFile,
|
||||
// logging: true,
|
||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment],
|
||||
//synchronize: true,
|
||||
migrations
|
||||
}).initialize()
|
||||
return source.runMigrations({ fake: true })
|
||||
import "reflect-metadata"
|
||||
import { DataSource, Migration } from "typeorm"
|
||||
import { AddressReceivingTransaction } from "./entity/AddressReceivingTransaction.js"
|
||||
import { User } from "./entity/User.js"
|
||||
import { UserReceivingAddress } from "./entity/UserReceivingAddress.js"
|
||||
import { UserReceivingInvoice } from "./entity/UserReceivingInvoice.js"
|
||||
import { UserInvoicePayment } from "./entity/UserInvoicePayment.js"
|
||||
import { EnvMustBeNonEmptyString } from "../helpers/envParser.js"
|
||||
import { UserTransactionPayment } from "./entity/UserTransactionPayment.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"
|
||||
import { BalanceEvent } from "./entity/BalanceEvent.js"
|
||||
import { ChannelBalanceEvent } from "./entity/ChannelsBalanceEvent.js"
|
||||
import { getLogger } from "../helpers/logger.js"
|
||||
import { ChannelRouting } from "./entity/ChannelRouting.js"
|
||||
|
||||
|
||||
export type DbSettings = {
|
||||
databaseFile: string
|
||||
migrate: boolean
|
||||
metricsDatabaseFile: string
|
||||
}
|
||||
export const LoadDbSettingsFromEnv = (): DbSettings => {
|
||||
return {
|
||||
databaseFile: process.env.DATABASE_FILE || "db.sqlite",
|
||||
migrate: process.env.MIGRATE_DB === 'true' || false,
|
||||
metricsDatabaseFile: process.env.METRICS_DATABASE_FILE || "metrics.sqlite"
|
||||
}
|
||||
}
|
||||
|
||||
export const newMetricsDb = async (settings: DbSettings, metricsMigrations: Function[]): Promise<{ source: DataSource, executedMigrations: Migration[] }> => {
|
||||
const source = await new DataSource({
|
||||
type: "sqlite",
|
||||
database: settings.metricsDatabaseFile,
|
||||
entities: [BalanceEvent, ChannelBalanceEvent, ChannelRouting],
|
||||
migrations: metricsMigrations
|
||||
}).initialize();
|
||||
const log = getLogger({});
|
||||
const pendingMigrations = await source.showMigrations()
|
||||
if (pendingMigrations) {
|
||||
log("Migrations found, migrating...")
|
||||
const executedMigrations = await source.runMigrations({ transaction: 'all' })
|
||||
return { source, executedMigrations }
|
||||
}
|
||||
return { source, executedMigrations: [] }
|
||||
|
||||
}
|
||||
|
||||
export default async (settings: DbSettings, migrations: Function[]): Promise<{ source: DataSource, executedMigrations: Migration[] }> => {
|
||||
const source = await new DataSource({
|
||||
type: "sqlite",
|
||||
database: settings.databaseFile,
|
||||
// logging: true,
|
||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment],
|
||||
//synchronize: true,
|
||||
migrations
|
||||
}).initialize()
|
||||
const log = getLogger({})
|
||||
const pendingMigrations = await source.showMigrations()
|
||||
if (pendingMigrations) {
|
||||
log("migrations found, migrating...")
|
||||
const executedMigrations = await source.runMigrations({ transaction: 'all' })
|
||||
return { source, executedMigrations }
|
||||
}
|
||||
return { source, executedMigrations: [] }
|
||||
}
|
||||
|
||||
export const runFakeMigration = async (databaseFile: string, migrations: Function[]) => {
|
||||
const source = await new DataSource({
|
||||
type: "sqlite",
|
||||
database: databaseFile,
|
||||
// logging: true,
|
||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment],
|
||||
//synchronize: true,
|
||||
migrations
|
||||
}).initialize()
|
||||
return source.runMigrations({ fake: true })
|
||||
}
|
||||
|
|
@ -1,45 +1,45 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
|
||||
import { UserReceivingAddress } from "./UserReceivingAddress.js"
|
||||
|
||||
@Entity()
|
||||
@Index("address_receiving_transaction_unique", ["tx_hash", "output_index"], { unique: true })
|
||||
export class AddressReceivingTransaction {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => UserReceivingAddress, { eager: true })
|
||||
@JoinColumn()
|
||||
user_address: UserReceivingAddress
|
||||
|
||||
@Column()
|
||||
tx_hash: string
|
||||
|
||||
@Column()
|
||||
output_index: number
|
||||
|
||||
@Column()
|
||||
paid_amount: number
|
||||
|
||||
@Column()
|
||||
service_fee: number
|
||||
|
||||
@Column()
|
||||
paid_at_unix: number
|
||||
|
||||
@Column({ default: false })
|
||||
internal: boolean
|
||||
|
||||
@Column({ default: 0 })
|
||||
confs: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
broadcast_height: number
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
|
||||
import { UserReceivingAddress } from "./UserReceivingAddress.js"
|
||||
|
||||
@Entity()
|
||||
@Index("address_receiving_transaction_unique", ["tx_hash", "output_index"], { unique: true })
|
||||
export class AddressReceivingTransaction {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => UserReceivingAddress, { eager: true })
|
||||
@JoinColumn()
|
||||
user_address: UserReceivingAddress
|
||||
|
||||
@Column()
|
||||
tx_hash: string
|
||||
|
||||
@Column()
|
||||
output_index: number
|
||||
|
||||
@Column()
|
||||
paid_amount: number
|
||||
|
||||
@Column()
|
||||
service_fee: number
|
||||
|
||||
@Column()
|
||||
paid_at_unix: number
|
||||
|
||||
@Column({ default: false })
|
||||
internal: boolean
|
||||
|
||||
@Column({ default: 0 })
|
||||
confs: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
broadcast_height: number
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,35 @@
|
|||
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
|
||||
|
||||
@Column({ default: false })
|
||||
allow_user_creation: boolean
|
||||
|
||||
@Column({ nullable: true, unique: true })
|
||||
nostr_private_key?: string
|
||||
|
||||
@Column({ nullable: true, unique: true })
|
||||
nostr_public_key?: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
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
|
||||
|
||||
@Column({ default: false })
|
||||
allow_user_creation: boolean
|
||||
|
||||
@Column({ nullable: true, unique: true })
|
||||
nostr_private_key?: string
|
||||
|
||||
@Column({ nullable: true, unique: true })
|
||||
nostr_public_key?: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
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
|
||||
|
||||
@Column({ nullable: true, unique: true })
|
||||
nostr_public_key?: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
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
|
||||
|
||||
@Column({ nullable: true, unique: true })
|
||||
nostr_public_key?: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
|
||||
@Entity()
|
||||
export class BalanceEvent {
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@Column()
|
||||
block_height: number
|
||||
|
||||
@Column()
|
||||
confirmed_chain_balance: number
|
||||
|
||||
@Column()
|
||||
unconfirmed_chain_balance: number
|
||||
|
||||
@Column()
|
||||
total_chain_balance: number
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
|
||||
@Entity()
|
||||
export class BalanceEvent {
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@Column()
|
||||
block_height: number
|
||||
|
||||
@Column()
|
||||
confirmed_chain_balance: number
|
||||
|
||||
@Column()
|
||||
unconfirmed_chain_balance: number
|
||||
|
||||
@Column()
|
||||
total_chain_balance: number
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,46 +1,46 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
|
||||
@Entity()
|
||||
export class ChannelRouting {
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@Column()
|
||||
day_unix: number
|
||||
|
||||
@Column()
|
||||
channel_id: string
|
||||
|
||||
@Column({ default: 0 })
|
||||
send_errors: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
receive_errors: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
forward_errors_as_input: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
forward_errors_as_output: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
missed_forward_fee_as_input: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
missed_forward_fee_as_output: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
forward_fee_as_input: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
forward_fee_as_output: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
latest_index_offset: number
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
|
||||
@Entity()
|
||||
export class ChannelRouting {
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@Column()
|
||||
day_unix: number
|
||||
|
||||
@Column()
|
||||
channel_id: string
|
||||
|
||||
@Column({ default: 0 })
|
||||
send_errors: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
receive_errors: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
forward_errors_as_input: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
forward_errors_as_output: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
missed_forward_fee_as_input: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
missed_forward_fee_as_output: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
forward_fee_as_input: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
forward_fee_as_output: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
latest_index_offset: number
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from "typeorm"
|
||||
import { BalanceEvent } from "./BalanceEvent.js"
|
||||
|
||||
@Entity()
|
||||
export class ChannelBalanceEvent {
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => BalanceEvent, { eager: true })
|
||||
@JoinColumn()
|
||||
balance_event: BalanceEvent
|
||||
|
||||
@Column()
|
||||
channel_id: string
|
||||
|
||||
@Column()
|
||||
local_balance_sats: number
|
||||
|
||||
@Column()
|
||||
remote_balance_sats: number
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from "typeorm"
|
||||
import { BalanceEvent } from "./BalanceEvent.js"
|
||||
|
||||
@Entity()
|
||||
export class ChannelBalanceEvent {
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => BalanceEvent, { eager: true })
|
||||
@JoinColumn()
|
||||
balance_event: BalanceEvent
|
||||
|
||||
@Column()
|
||||
channel_id: string
|
||||
|
||||
@Column()
|
||||
local_balance_sats: number
|
||||
|
||||
@Column()
|
||||
remote_balance_sats: number
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
|
||||
@Entity()
|
||||
export class Product {
|
||||
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
product_id: string
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
owner: User
|
||||
|
||||
@Column()
|
||||
name: string
|
||||
|
||||
@Column()
|
||||
price_sats: number
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
|
||||
@Entity()
|
||||
export class Product {
|
||||
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
product_id: string
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
owner: User
|
||||
|
||||
@Column()
|
||||
name: string
|
||||
|
||||
@Column()
|
||||
price_sats: number
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@Column()
|
||||
@Index({ unique: true })
|
||||
user_id: string
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
balance_sats: number
|
||||
|
||||
@Column({ default: false })
|
||||
locked: boolean
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@Column()
|
||||
@Index({ unique: true })
|
||||
user_id: string
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
balance_sats: number
|
||||
|
||||
@Column({ default: false })
|
||||
locked: boolean
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, JoinColumn, OneToOne, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
|
||||
@Entity()
|
||||
export class UserBasicAuth {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@OneToOne(type => User, { eager: true })
|
||||
@JoinColumn()
|
||||
user: User
|
||||
|
||||
@Column()
|
||||
@Index({ unique: true })
|
||||
name: string
|
||||
|
||||
@Column()
|
||||
secret_sha256: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index, JoinColumn, OneToOne, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
|
||||
@Entity()
|
||||
export class UserBasicAuth {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@OneToOne(type => User, { eager: true })
|
||||
@JoinColumn()
|
||||
user: User
|
||||
|
||||
@Column()
|
||||
@Index({ unique: true })
|
||||
name: string
|
||||
|
||||
@Column()
|
||||
secret_sha256: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.js"
|
||||
export type EphemeralKeyType = 'balanceCheck' | 'withdraw' | 'pay'
|
||||
@Entity()
|
||||
export class UserEphemeralKey {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
@JoinColumn()
|
||||
user: User
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
@JoinColumn()
|
||||
linkedApplication: Application | null
|
||||
|
||||
@Column()
|
||||
@Index({ unique: true })
|
||||
key: string
|
||||
|
||||
@Column()
|
||||
type: EphemeralKeyType
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.js"
|
||||
export type EphemeralKeyType = 'balanceCheck' | 'withdraw' | 'pay'
|
||||
@Entity()
|
||||
export class UserEphemeralKey {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
@JoinColumn()
|
||||
user: User
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
@JoinColumn()
|
||||
linkedApplication: Application | null
|
||||
|
||||
@Column()
|
||||
@Index({ unique: true })
|
||||
key: string
|
||||
|
||||
@Column()
|
||||
type: EphemeralKeyType
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,42 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.js"
|
||||
|
||||
@Entity()
|
||||
export class UserInvoicePayment {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
@JoinColumn()
|
||||
user: User
|
||||
|
||||
@Column()
|
||||
@Index({ unique: true })
|
||||
invoice: string
|
||||
|
||||
@Column()
|
||||
paid_amount: number
|
||||
|
||||
@Column()
|
||||
routing_fees: number
|
||||
|
||||
@Column()
|
||||
service_fees: number
|
||||
|
||||
@Column()
|
||||
paid_at_unix: number
|
||||
|
||||
@Column({ default: false })
|
||||
internal: boolean
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
linkedApplication: Application | null
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.js"
|
||||
|
||||
@Entity()
|
||||
export class UserInvoicePayment {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
@JoinColumn()
|
||||
user: User
|
||||
|
||||
@Column()
|
||||
@Index({ unique: true })
|
||||
invoice: string
|
||||
|
||||
@Column()
|
||||
paid_amount: number
|
||||
|
||||
@Column()
|
||||
routing_fees: number
|
||||
|
||||
@Column()
|
||||
service_fees: number
|
||||
|
||||
@Column()
|
||||
paid_at_unix: number
|
||||
|
||||
@Column({ default: false })
|
||||
internal: boolean
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
linkedApplication: Application | null
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.js"
|
||||
|
||||
@Entity()
|
||||
export class UserReceivingAddress {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
@JoinColumn()
|
||||
user: User
|
||||
|
||||
@Column()
|
||||
@Index({ unique: true })
|
||||
address: string
|
||||
|
||||
@Column()
|
||||
callbackUrl: string
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
linkedApplication: Application | null
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.js"
|
||||
|
||||
@Entity()
|
||||
export class UserReceivingAddress {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
@JoinColumn()
|
||||
user: User
|
||||
|
||||
@Column()
|
||||
@Index({ unique: true })
|
||||
address: string
|
||||
|
||||
@Column()
|
||||
callbackUrl: string
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
linkedApplication: Application | null
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +1,66 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { Product } from "./Product.js"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.js"
|
||||
export type ZapInfo = {
|
||||
pub: string
|
||||
eventId: string
|
||||
relays: string[]
|
||||
description: string
|
||||
}
|
||||
@Entity()
|
||||
export class UserReceivingInvoice {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
@JoinColumn()
|
||||
user: User
|
||||
|
||||
@Column()
|
||||
@Index({ unique: true })
|
||||
invoice: string
|
||||
|
||||
@Column()
|
||||
expires_at_unix: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
paid_at_unix: number
|
||||
|
||||
@Column({ default: false })
|
||||
internal: boolean
|
||||
|
||||
@Column({ default: false })
|
||||
paidByLnd: boolean
|
||||
|
||||
@Column({ default: "" })
|
||||
callbackUrl: string
|
||||
|
||||
@Column({ default: 0 })
|
||||
paid_amount: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
service_fee: number
|
||||
|
||||
@ManyToOne(type => Product, { eager: true })
|
||||
product: Product | null
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
payer: User | null
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
linkedApplication: Application | null
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: 'simple-json'
|
||||
})
|
||||
zap_info?: ZapInfo
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { Product } from "./Product.js"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.js"
|
||||
export type ZapInfo = {
|
||||
pub: string
|
||||
eventId: string
|
||||
relays: string[]
|
||||
description: string
|
||||
}
|
||||
@Entity()
|
||||
export class UserReceivingInvoice {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
@JoinColumn()
|
||||
user: User
|
||||
|
||||
@Column()
|
||||
@Index({ unique: true })
|
||||
invoice: string
|
||||
|
||||
@Column()
|
||||
expires_at_unix: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
paid_at_unix: number
|
||||
|
||||
@Column({ default: false })
|
||||
internal: boolean
|
||||
|
||||
@Column({ default: false })
|
||||
paidByLnd: boolean
|
||||
|
||||
@Column({ default: "" })
|
||||
callbackUrl: string
|
||||
|
||||
@Column({ default: 0 })
|
||||
paid_amount: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
service_fee: number
|
||||
|
||||
@ManyToOne(type => Product, { eager: true })
|
||||
product: Product | null
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
payer: User | null
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
linkedApplication: Application | null
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: 'simple-json'
|
||||
})
|
||||
zap_info?: ZapInfo
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.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
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
linkedApplication: Application | null
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.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
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
linkedApplication: Application | null
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +1,54 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.js"
|
||||
|
||||
@Entity()
|
||||
@Index("user_transaction_unique", ["tx_hash", "output_index"], { unique: true })
|
||||
export class UserTransactionPayment {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
@JoinColumn()
|
||||
user: User
|
||||
|
||||
@Column()
|
||||
address: string
|
||||
|
||||
@Column()
|
||||
tx_hash: string
|
||||
|
||||
@Column()
|
||||
output_index: number
|
||||
|
||||
@Column()
|
||||
paid_amount: number
|
||||
|
||||
@Column()
|
||||
chain_fees: number
|
||||
|
||||
@Column()
|
||||
service_fees: number
|
||||
|
||||
@Column()
|
||||
paid_at_unix: number
|
||||
|
||||
@Column({ default: false })
|
||||
internal: boolean
|
||||
|
||||
@Column({ default: 0 })
|
||||
confs: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
broadcast_height: number
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
linkedApplication: Application | null
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.js"
|
||||
|
||||
@Entity()
|
||||
@Index("user_transaction_unique", ["tx_hash", "output_index"], { unique: true })
|
||||
export class UserTransactionPayment {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@ManyToOne(type => User, { eager: true })
|
||||
@JoinColumn()
|
||||
user: User
|
||||
|
||||
@Column()
|
||||
address: string
|
||||
|
||||
@Column()
|
||||
tx_hash: string
|
||||
|
||||
@Column()
|
||||
output_index: number
|
||||
|
||||
@Column()
|
||||
paid_amount: number
|
||||
|
||||
@Column()
|
||||
chain_fees: number
|
||||
|
||||
@Column()
|
||||
service_fees: number
|
||||
|
||||
@Column()
|
||||
paid_at_unix: number
|
||||
|
||||
@Column({ default: false })
|
||||
internal: boolean
|
||||
|
||||
@Column({ default: 0 })
|
||||
confs: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
broadcast_height: number
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
linkedApplication: Application | null
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,125 +1,125 @@
|
|||
import fs from 'fs'
|
||||
import { parse, stringify } from 'csv'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
//const eventLogPath = "logs/eventLogV2.csv"
|
||||
type LoggedEventType = 'new_invoice' | 'new_address' | 'address_paid' | 'invoice_paid' | 'invoice_payment' | 'address_payment' | 'u2u_receiver' | 'u2u_sender' | 'balance_increment' | 'balance_decrement'
|
||||
export type LoggedEvent = {
|
||||
timestampMs: number
|
||||
userId: string
|
||||
appUserId: string
|
||||
appId: string
|
||||
balance: number
|
||||
type: LoggedEventType
|
||||
data: string
|
||||
amount: number
|
||||
}
|
||||
type TimeEntry = {
|
||||
timestamp: number
|
||||
amount: number
|
||||
balance: number
|
||||
userId: string
|
||||
}
|
||||
const columns = ["timestampMs", "userId", "appUserId", "appId", "balance", "type", "data", "amount"]
|
||||
type StringerWrite = (chunk: any, cb: (error: Error | null | undefined) => void) => boolean
|
||||
export default class EventsLogManager {
|
||||
eventLogPath: string
|
||||
log = getLogger({ component: "EventsLogManager" })
|
||||
stringerWrite: StringerWrite
|
||||
constructor(eventLogPath: string) {
|
||||
this.eventLogPath = eventLogPath
|
||||
const exists = fs.existsSync(eventLogPath)
|
||||
if (!exists) {
|
||||
const stringer = stringify({ header: true, columns })
|
||||
stringer.pipe(fs.createWriteStream(eventLogPath, { flags: 'a' }))
|
||||
this.stringerWrite = (chunk, cb) => stringer.write(chunk, cb)
|
||||
} else {
|
||||
const stringer = stringify({})
|
||||
stringer.pipe(fs.createWriteStream(eventLogPath, { flags: 'a' }))
|
||||
this.stringerWrite = (chunk, cb) => stringer.write(chunk, cb)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
LogEvent = (e: Omit<LoggedEvent, 'timestampMs'>) => {
|
||||
this.log(e.type, "->", e.userId, "->", e.appId, "->", e.appUserId, "->", e.balance, "->", e.data, "->", e.amount)
|
||||
this.write([Date.now(), e.userId, e.appUserId, e.appId, e.balance, e.type, e.data, e.amount])
|
||||
}
|
||||
|
||||
GetAllLogs = async (path?: string): Promise<LoggedEvent[]> => {
|
||||
const logs = await this.Read(path)
|
||||
this.log("found", logs.length, "event logs")
|
||||
return logs
|
||||
}
|
||||
|
||||
Read = async (path?: string): Promise<LoggedEvent[]> => {
|
||||
const filePath = path ? path : this.eventLogPath
|
||||
const exists = fs.existsSync(filePath)
|
||||
if (!exists) {
|
||||
return []
|
||||
}
|
||||
return new Promise<LoggedEvent[]>((res, rej) => {
|
||||
const result: LoggedEvent[] = []
|
||||
fs.createReadStream(filePath)
|
||||
.pipe(parse({ delimiter: ",", from_line: 2 }))
|
||||
.on('data', data => { result.push(this.parseEvent(data)) })
|
||||
.on('error', err => { rej(err) })
|
||||
.on('end', () => { res(result) })
|
||||
})
|
||||
}
|
||||
|
||||
parseEvent = (args: string[]): LoggedEvent => {
|
||||
const [timestampMs, userId, appUserId, appId, balance, type, data, amount] = args
|
||||
return { timestampMs: +timestampMs, userId, appUserId, appId, balance: +balance, type: type as LoggedEventType, data, amount: +amount }
|
||||
}
|
||||
|
||||
write = async (args: (string | number)[]) => {
|
||||
return new Promise<void>((res, rej) => {
|
||||
this.stringerWrite(args, err => {
|
||||
if (err) {
|
||||
rej(err)
|
||||
} else { res() }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
ignoredKeys = ['fees', "bc1qkafgye62h2zhzlwtrga6jytz2p7af4lg8fwqt6", "6eb1d279f95377b8514aad3b79ff1cddbe9f5d3b95653b55719850df9df63821", "b11585413bfa7bf65a5f1263e3100e53b4c9afe6b5d8c94c6b85017dfcbf3d49"]
|
||||
createTimeSeries = (events: LoggedEvent[]) => {
|
||||
const dataAppIds: Record<string, string> = {}
|
||||
const order: { timestamp: number, data: string, type: 'inc' | 'dec' }[] = []
|
||||
const incrementEntries: Record<string, TimeEntry> = {}
|
||||
const decrementEntries: Record<string, TimeEntry> = {}
|
||||
events.forEach(e => {
|
||||
if (this.ignoredKeys.includes(e.data)) {
|
||||
return
|
||||
}
|
||||
if (e.type === 'balance_increment') {
|
||||
if (incrementEntries[e.data]) {
|
||||
throw new Error("increment duplicate! " + e.data)
|
||||
}
|
||||
incrementEntries[e.data] = { timestamp: e.timestampMs, balance: e.balance, amount: e.amount, userId: e.userId }
|
||||
order.push({ timestamp: e.timestampMs, data: e.data, type: 'inc' })
|
||||
} else if (e.type === 'balance_decrement') {
|
||||
if (decrementEntries[e.data]) {
|
||||
throw new Error("decrement duplicate! " + e.data)
|
||||
}
|
||||
decrementEntries[e.data] = { timestamp: e.timestampMs, balance: e.balance, amount: e.amount, userId: e.userId }
|
||||
order.push({ timestamp: e.timestampMs, data: e.data, type: 'dec' })
|
||||
} else if (e.appId) {
|
||||
dataAppIds[e.data] = e.appId
|
||||
}
|
||||
})
|
||||
const full = order.map(o => {
|
||||
const { type } = o
|
||||
if (type === 'inc') {
|
||||
const entry = incrementEntries[o.data]
|
||||
return { timestamp: entry.timestamp, amount: entry.amount, balance: entry.balance, userId: entry.userId, appId: dataAppIds[o.data], internal: !!decrementEntries[o.data] }
|
||||
} else {
|
||||
const entry = decrementEntries[o.data]
|
||||
return { timestamp: entry.timestamp, amount: -entry.amount, balance: entry.balance, userId: entry.userId, appId: dataAppIds[o.data], internal: !!incrementEntries[o.data] }
|
||||
}
|
||||
})
|
||||
full.sort((a, b) => a.timestamp - b.timestamp)
|
||||
fs.writeFileSync("timeSeries.json", JSON.stringify(full, null, 2))
|
||||
}
|
||||
import fs from 'fs'
|
||||
import { parse, stringify } from 'csv'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
//const eventLogPath = "logs/eventLogV2.csv"
|
||||
type LoggedEventType = 'new_invoice' | 'new_address' | 'address_paid' | 'invoice_paid' | 'invoice_payment' | 'address_payment' | 'u2u_receiver' | 'u2u_sender' | 'balance_increment' | 'balance_decrement'
|
||||
export type LoggedEvent = {
|
||||
timestampMs: number
|
||||
userId: string
|
||||
appUserId: string
|
||||
appId: string
|
||||
balance: number
|
||||
type: LoggedEventType
|
||||
data: string
|
||||
amount: number
|
||||
}
|
||||
type TimeEntry = {
|
||||
timestamp: number
|
||||
amount: number
|
||||
balance: number
|
||||
userId: string
|
||||
}
|
||||
const columns = ["timestampMs", "userId", "appUserId", "appId", "balance", "type", "data", "amount"]
|
||||
type StringerWrite = (chunk: any, cb: (error: Error | null | undefined) => void) => boolean
|
||||
export default class EventsLogManager {
|
||||
eventLogPath: string
|
||||
log = getLogger({ component: "EventsLogManager" })
|
||||
stringerWrite: StringerWrite
|
||||
constructor(eventLogPath: string) {
|
||||
this.eventLogPath = eventLogPath
|
||||
const exists = fs.existsSync(eventLogPath)
|
||||
if (!exists) {
|
||||
const stringer = stringify({ header: true, columns })
|
||||
stringer.pipe(fs.createWriteStream(eventLogPath, { flags: 'a' }))
|
||||
this.stringerWrite = (chunk, cb) => stringer.write(chunk, cb)
|
||||
} else {
|
||||
const stringer = stringify({})
|
||||
stringer.pipe(fs.createWriteStream(eventLogPath, { flags: 'a' }))
|
||||
this.stringerWrite = (chunk, cb) => stringer.write(chunk, cb)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
LogEvent = (e: Omit<LoggedEvent, 'timestampMs'>) => {
|
||||
this.log(e.type, "->", e.userId, "->", e.appId, "->", e.appUserId, "->", e.balance, "->", e.data, "->", e.amount)
|
||||
this.write([Date.now(), e.userId, e.appUserId, e.appId, e.balance, e.type, e.data, e.amount])
|
||||
}
|
||||
|
||||
GetAllLogs = async (path?: string): Promise<LoggedEvent[]> => {
|
||||
const logs = await this.Read(path)
|
||||
this.log("found", logs.length, "event logs")
|
||||
return logs
|
||||
}
|
||||
|
||||
Read = async (path?: string): Promise<LoggedEvent[]> => {
|
||||
const filePath = path ? path : this.eventLogPath
|
||||
const exists = fs.existsSync(filePath)
|
||||
if (!exists) {
|
||||
return []
|
||||
}
|
||||
return new Promise<LoggedEvent[]>((res, rej) => {
|
||||
const result: LoggedEvent[] = []
|
||||
fs.createReadStream(filePath)
|
||||
.pipe(parse({ delimiter: ",", from_line: 2 }))
|
||||
.on('data', data => { result.push(this.parseEvent(data)) })
|
||||
.on('error', err => { rej(err) })
|
||||
.on('end', () => { res(result) })
|
||||
})
|
||||
}
|
||||
|
||||
parseEvent = (args: string[]): LoggedEvent => {
|
||||
const [timestampMs, userId, appUserId, appId, balance, type, data, amount] = args
|
||||
return { timestampMs: +timestampMs, userId, appUserId, appId, balance: +balance, type: type as LoggedEventType, data, amount: +amount }
|
||||
}
|
||||
|
||||
write = async (args: (string | number)[]) => {
|
||||
return new Promise<void>((res, rej) => {
|
||||
this.stringerWrite(args, err => {
|
||||
if (err) {
|
||||
rej(err)
|
||||
} else { res() }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
ignoredKeys = ['fees', "bc1qkafgye62h2zhzlwtrga6jytz2p7af4lg8fwqt6", "6eb1d279f95377b8514aad3b79ff1cddbe9f5d3b95653b55719850df9df63821", "b11585413bfa7bf65a5f1263e3100e53b4c9afe6b5d8c94c6b85017dfcbf3d49"]
|
||||
createTimeSeries = (events: LoggedEvent[]) => {
|
||||
const dataAppIds: Record<string, string> = {}
|
||||
const order: { timestamp: number, data: string, type: 'inc' | 'dec' }[] = []
|
||||
const incrementEntries: Record<string, TimeEntry> = {}
|
||||
const decrementEntries: Record<string, TimeEntry> = {}
|
||||
events.forEach(e => {
|
||||
if (this.ignoredKeys.includes(e.data)) {
|
||||
return
|
||||
}
|
||||
if (e.type === 'balance_increment') {
|
||||
if (incrementEntries[e.data]) {
|
||||
throw new Error("increment duplicate! " + e.data)
|
||||
}
|
||||
incrementEntries[e.data] = { timestamp: e.timestampMs, balance: e.balance, amount: e.amount, userId: e.userId }
|
||||
order.push({ timestamp: e.timestampMs, data: e.data, type: 'inc' })
|
||||
} else if (e.type === 'balance_decrement') {
|
||||
if (decrementEntries[e.data]) {
|
||||
throw new Error("decrement duplicate! " + e.data)
|
||||
}
|
||||
decrementEntries[e.data] = { timestamp: e.timestampMs, balance: e.balance, amount: e.amount, userId: e.userId }
|
||||
order.push({ timestamp: e.timestampMs, data: e.data, type: 'dec' })
|
||||
} else if (e.appId) {
|
||||
dataAppIds[e.data] = e.appId
|
||||
}
|
||||
})
|
||||
const full = order.map(o => {
|
||||
const { type } = o
|
||||
if (type === 'inc') {
|
||||
const entry = incrementEntries[o.data]
|
||||
return { timestamp: entry.timestamp, amount: entry.amount, balance: entry.balance, userId: entry.userId, appId: dataAppIds[o.data], internal: !!decrementEntries[o.data] }
|
||||
} else {
|
||||
const entry = decrementEntries[o.data]
|
||||
return { timestamp: entry.timestamp, amount: -entry.amount, balance: entry.balance, userId: entry.userId, appId: dataAppIds[o.data], internal: !!incrementEntries[o.data] }
|
||||
}
|
||||
})
|
||||
full.sort((a, b) => a.timestamp - b.timestamp)
|
||||
fs.writeFileSync("timeSeries.json", JSON.stringify(full, null, 2))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +1,47 @@
|
|||
import { DataSource, EntityManager } from "typeorm"
|
||||
import NewDB, { DbSettings, LoadDbSettingsFromEnv } from "./db.js"
|
||||
import ProductStorage from './productStorage.js'
|
||||
import ApplicationStorage from './applicationStorage.js'
|
||||
import UserStorage from "./userStorage.js";
|
||||
import PaymentStorage from "./paymentStorage.js";
|
||||
import MetricsStorage from "./metricsStorage.js";
|
||||
import TransactionsQueue, { TX } from "./transactionsQueue.js";
|
||||
import EventsLogManager from "./eventsLog.js";
|
||||
export type StorageSettings = {
|
||||
dbSettings: DbSettings
|
||||
eventLogPath: string
|
||||
}
|
||||
export const LoadStorageSettingsFromEnv = (): StorageSettings => {
|
||||
return { dbSettings: LoadDbSettingsFromEnv(), eventLogPath: "logs/eventLogV2.csv" }
|
||||
}
|
||||
export default class {
|
||||
DB: DataSource | EntityManager
|
||||
settings: StorageSettings
|
||||
txQueue: TransactionsQueue
|
||||
productStorage: ProductStorage
|
||||
applicationStorage: ApplicationStorage
|
||||
userStorage: UserStorage
|
||||
paymentStorage: PaymentStorage
|
||||
metricsStorage: MetricsStorage
|
||||
eventsLog: EventsLogManager
|
||||
constructor(settings: StorageSettings) {
|
||||
this.settings = settings
|
||||
this.eventsLog = new EventsLogManager(settings.eventLogPath)
|
||||
}
|
||||
async Connect(migrations: Function[], metricsMigrations: Function[]) {
|
||||
const { source, executedMigrations } = await NewDB(this.settings.dbSettings, migrations)
|
||||
this.DB = source
|
||||
this.txQueue = new TransactionsQueue("main", this.DB)
|
||||
this.userStorage = new UserStorage(this.DB, this.txQueue, this.eventsLog)
|
||||
this.productStorage = new ProductStorage(this.DB, this.txQueue)
|
||||
this.applicationStorage = new ApplicationStorage(this.DB, this.userStorage, this.txQueue)
|
||||
this.paymentStorage = new PaymentStorage(this.DB, this.userStorage, this.txQueue)
|
||||
this.metricsStorage = new MetricsStorage(this.settings)
|
||||
const executedMetricsMigrations = await this.metricsStorage.Connect(metricsMigrations)
|
||||
return { executedMigrations, executedMetricsMigrations };
|
||||
}
|
||||
|
||||
StartTransaction<T>(exec: TX<T>, description?: string) {
|
||||
return this.txQueue.PushToQueue({ exec, dbTx: true, description })
|
||||
}
|
||||
import { DataSource, EntityManager } from "typeorm"
|
||||
import NewDB, { DbSettings, LoadDbSettingsFromEnv } from "./db.js"
|
||||
import ProductStorage from './productStorage.js'
|
||||
import ApplicationStorage from './applicationStorage.js'
|
||||
import UserStorage from "./userStorage.js";
|
||||
import PaymentStorage from "./paymentStorage.js";
|
||||
import MetricsStorage from "./metricsStorage.js";
|
||||
import TransactionsQueue, { TX } from "./transactionsQueue.js";
|
||||
import EventsLogManager from "./eventsLog.js";
|
||||
export type StorageSettings = {
|
||||
dbSettings: DbSettings
|
||||
eventLogPath: string
|
||||
}
|
||||
export const LoadStorageSettingsFromEnv = (): StorageSettings => {
|
||||
return { dbSettings: LoadDbSettingsFromEnv(), eventLogPath: "logs/eventLogV2.csv" }
|
||||
}
|
||||
export default class {
|
||||
DB: DataSource | EntityManager
|
||||
settings: StorageSettings
|
||||
txQueue: TransactionsQueue
|
||||
productStorage: ProductStorage
|
||||
applicationStorage: ApplicationStorage
|
||||
userStorage: UserStorage
|
||||
paymentStorage: PaymentStorage
|
||||
metricsStorage: MetricsStorage
|
||||
eventsLog: EventsLogManager
|
||||
constructor(settings: StorageSettings) {
|
||||
this.settings = settings
|
||||
this.eventsLog = new EventsLogManager(settings.eventLogPath)
|
||||
}
|
||||
async Connect(migrations: Function[], metricsMigrations: Function[]) {
|
||||
const { source, executedMigrations } = await NewDB(this.settings.dbSettings, migrations)
|
||||
this.DB = source
|
||||
this.txQueue = new TransactionsQueue("main", this.DB)
|
||||
this.userStorage = new UserStorage(this.DB, this.txQueue, this.eventsLog)
|
||||
this.productStorage = new ProductStorage(this.DB, this.txQueue)
|
||||
this.applicationStorage = new ApplicationStorage(this.DB, this.userStorage, this.txQueue)
|
||||
this.paymentStorage = new PaymentStorage(this.DB, this.userStorage, this.txQueue)
|
||||
this.metricsStorage = new MetricsStorage(this.settings)
|
||||
const executedMetricsMigrations = await this.metricsStorage.Connect(metricsMigrations)
|
||||
return { executedMigrations, executedMetricsMigrations };
|
||||
}
|
||||
|
||||
StartTransaction<T>(exec: TX<T>, description?: string) {
|
||||
return this.txQueue.PushToQueue({ exec, dbTx: true, description })
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +1,114 @@
|
|||
import { Between, DataSource, EntityManager, FindOperator, LessThanOrEqual, MoreThanOrEqual } from "typeorm"
|
||||
import { BalanceEvent } from "./entity/BalanceEvent.js"
|
||||
import { ChannelBalanceEvent } from "./entity/ChannelsBalanceEvent.js"
|
||||
import TransactionsQueue, { TX } from "./transactionsQueue.js";
|
||||
import { StorageSettings } from "./index.js";
|
||||
import { newMetricsDb } from "./db.js";
|
||||
import { ChannelRouting } from "./entity/ChannelRouting.js";
|
||||
export default class {
|
||||
|
||||
DB: DataSource | EntityManager
|
||||
settings: StorageSettings
|
||||
txQueue: TransactionsQueue
|
||||
constructor(settings: StorageSettings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
async Connect(metricsMigrations: Function[]) {
|
||||
const { source, executedMigrations } = await newMetricsDb(this.settings.dbSettings, metricsMigrations)
|
||||
this.DB = source;
|
||||
this.txQueue = new TransactionsQueue("metrics", this.DB)
|
||||
return executedMigrations;
|
||||
}
|
||||
|
||||
async SaveBalanceEvents(balanceEvent: Partial<BalanceEvent>, channelBalanceEvents: Partial<ChannelBalanceEvent>[]) {
|
||||
const blanceEventEntry = this.DB.getRepository(BalanceEvent).create(balanceEvent)
|
||||
const balanceEntry = await this.txQueue.PushToQueue<BalanceEvent>({ exec: async db => db.getRepository(BalanceEvent).save(blanceEventEntry), dbTx: false })
|
||||
|
||||
const channelsEntry = this.DB.getRepository(ChannelBalanceEvent).create(channelBalanceEvents.map(e => ({ ...e, balance_event: balanceEntry })))
|
||||
const channelsEntries = await this.txQueue.PushToQueue<ChannelBalanceEvent[]>({ exec: async db => db.getRepository(ChannelBalanceEvent).save(channelsEntry), dbTx: false })
|
||||
return { balanceEntry, channelsEntries }
|
||||
}
|
||||
|
||||
async GetBalanceEvents({ from, to }: { from?: number, to?: number }, entityManager = this.DB) {
|
||||
const q = getTimeQuery({ from, to })
|
||||
|
||||
const [chainBalanceEvents, channelsBalanceEvents] = await Promise.all([
|
||||
entityManager.getRepository(BalanceEvent).find(q),
|
||||
entityManager.getRepository(ChannelBalanceEvent).find(q),
|
||||
])
|
||||
return { chainBalanceEvents, channelsBalanceEvents }
|
||||
}
|
||||
|
||||
async initChannelRoutingEvent(dayUnix: number, channelId: string) {
|
||||
const existing = await this.DB.getRepository(ChannelRouting).findOne({ where: { day_unix: dayUnix, channel_id: channelId } })
|
||||
if (!existing) {
|
||||
const entry = this.DB.getRepository(ChannelRouting).create({ day_unix: dayUnix, channel_id: channelId })
|
||||
return this.txQueue.PushToQueue<ChannelRouting>({ exec: async db => db.getRepository(ChannelRouting).save(entry), dbTx: false })
|
||||
}
|
||||
return existing
|
||||
}
|
||||
|
||||
GetChannelRouting({ from, to }: { from?: number, to?: number }, entityManager = this.DB) {
|
||||
const q = getTimeQuery({ from, to })
|
||||
return entityManager.getRepository(ChannelRouting).find(q)
|
||||
}
|
||||
|
||||
async GetLatestForwardingIndexOffset() {
|
||||
const latestIndex = await this.DB.getRepository(ChannelRouting).findOne({ order: { latest_index_offset: "DESC" } })
|
||||
if (latestIndex) {
|
||||
return latestIndex.latest_index_offset
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
async IncrementChannelRouting(channelId: string, event: Partial<ChannelRouting>) {
|
||||
const dayUnix = getTodayUnix()
|
||||
const existing = await this.initChannelRoutingEvent(dayUnix, channelId)
|
||||
const repo = this.DB.getRepository(ChannelRouting)
|
||||
if (event.send_errors) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "send_errors", event.send_errors)
|
||||
}
|
||||
if (event.receive_errors) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "receive_errors", event.receive_errors)
|
||||
}
|
||||
if (event.forward_errors_as_input) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "forward_errors_as_input", event.forward_errors_as_input)
|
||||
}
|
||||
if (event.forward_errors_as_output) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "forward_errors_as_output", event.forward_errors_as_output)
|
||||
}
|
||||
if (event.missed_forward_fee_as_input) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "missed_forward_fee_as_input", event.missed_forward_fee_as_input)
|
||||
}
|
||||
if (event.missed_forward_fee_as_output) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "missed_forward_fee_as_output", event.missed_forward_fee_as_output)
|
||||
}
|
||||
if (event.forward_fee_as_input) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "forward_fee_as_input", event.forward_fee_as_input)
|
||||
}
|
||||
if (event.forward_fee_as_output) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "forward_fee_as_output", event.forward_fee_as_output)
|
||||
}
|
||||
if (event.latest_index_offset) {
|
||||
await repo.update(existing.serial_id, { latest_index_offset: event.latest_index_offset })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
const getTimeQuery = ({ from, to }: { from?: number, to?: number }) => {
|
||||
if (!!from && !!to) {
|
||||
return { where: { created_at: Between<Date>(new Date(from * 1000), new Date(to * 1000)) } }
|
||||
} else if (!!from) {
|
||||
return { where: { created_at: MoreThanOrEqual<Date>(new Date(from * 1000)) } }
|
||||
} else if (!!to) {
|
||||
return { where: { created_at: LessThanOrEqual<Date>(new Date(to * 1000)) } }
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
const getTodayUnix = () => {
|
||||
const now = new Date()
|
||||
return new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime() / 1000
|
||||
import { Between, DataSource, EntityManager, FindOperator, LessThanOrEqual, MoreThanOrEqual } from "typeorm"
|
||||
import { BalanceEvent } from "./entity/BalanceEvent.js"
|
||||
import { ChannelBalanceEvent } from "./entity/ChannelsBalanceEvent.js"
|
||||
import TransactionsQueue, { TX } from "./transactionsQueue.js";
|
||||
import { StorageSettings } from "./index.js";
|
||||
import { newMetricsDb } from "./db.js";
|
||||
import { ChannelRouting } from "./entity/ChannelRouting.js";
|
||||
export default class {
|
||||
|
||||
DB: DataSource | EntityManager
|
||||
settings: StorageSettings
|
||||
txQueue: TransactionsQueue
|
||||
constructor(settings: StorageSettings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
async Connect(metricsMigrations: Function[]) {
|
||||
const { source, executedMigrations } = await newMetricsDb(this.settings.dbSettings, metricsMigrations)
|
||||
this.DB = source;
|
||||
this.txQueue = new TransactionsQueue("metrics", this.DB)
|
||||
return executedMigrations;
|
||||
}
|
||||
|
||||
async SaveBalanceEvents(balanceEvent: Partial<BalanceEvent>, channelBalanceEvents: Partial<ChannelBalanceEvent>[]) {
|
||||
const blanceEventEntry = this.DB.getRepository(BalanceEvent).create(balanceEvent)
|
||||
const balanceEntry = await this.txQueue.PushToQueue<BalanceEvent>({ exec: async db => db.getRepository(BalanceEvent).save(blanceEventEntry), dbTx: false })
|
||||
|
||||
const channelsEntry = this.DB.getRepository(ChannelBalanceEvent).create(channelBalanceEvents.map(e => ({ ...e, balance_event: balanceEntry })))
|
||||
const channelsEntries = await this.txQueue.PushToQueue<ChannelBalanceEvent[]>({ exec: async db => db.getRepository(ChannelBalanceEvent).save(channelsEntry), dbTx: false })
|
||||
return { balanceEntry, channelsEntries }
|
||||
}
|
||||
|
||||
async GetBalanceEvents({ from, to }: { from?: number, to?: number }, entityManager = this.DB) {
|
||||
const q = getTimeQuery({ from, to })
|
||||
|
||||
const [chainBalanceEvents, channelsBalanceEvents] = await Promise.all([
|
||||
entityManager.getRepository(BalanceEvent).find(q),
|
||||
entityManager.getRepository(ChannelBalanceEvent).find(q),
|
||||
])
|
||||
return { chainBalanceEvents, channelsBalanceEvents }
|
||||
}
|
||||
|
||||
async initChannelRoutingEvent(dayUnix: number, channelId: string) {
|
||||
const existing = await this.DB.getRepository(ChannelRouting).findOne({ where: { day_unix: dayUnix, channel_id: channelId } })
|
||||
if (!existing) {
|
||||
const entry = this.DB.getRepository(ChannelRouting).create({ day_unix: dayUnix, channel_id: channelId })
|
||||
return this.txQueue.PushToQueue<ChannelRouting>({ exec: async db => db.getRepository(ChannelRouting).save(entry), dbTx: false })
|
||||
}
|
||||
return existing
|
||||
}
|
||||
|
||||
GetChannelRouting({ from, to }: { from?: number, to?: number }, entityManager = this.DB) {
|
||||
const q = getTimeQuery({ from, to })
|
||||
return entityManager.getRepository(ChannelRouting).find(q)
|
||||
}
|
||||
|
||||
async GetLatestForwardingIndexOffset() {
|
||||
const latestIndex = await this.DB.getRepository(ChannelRouting).findOne({ order: { latest_index_offset: "DESC" } })
|
||||
if (latestIndex) {
|
||||
return latestIndex.latest_index_offset
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
async IncrementChannelRouting(channelId: string, event: Partial<ChannelRouting>) {
|
||||
const dayUnix = getTodayUnix()
|
||||
const existing = await this.initChannelRoutingEvent(dayUnix, channelId)
|
||||
const repo = this.DB.getRepository(ChannelRouting)
|
||||
if (event.send_errors) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "send_errors", event.send_errors)
|
||||
}
|
||||
if (event.receive_errors) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "receive_errors", event.receive_errors)
|
||||
}
|
||||
if (event.forward_errors_as_input) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "forward_errors_as_input", event.forward_errors_as_input)
|
||||
}
|
||||
if (event.forward_errors_as_output) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "forward_errors_as_output", event.forward_errors_as_output)
|
||||
}
|
||||
if (event.missed_forward_fee_as_input) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "missed_forward_fee_as_input", event.missed_forward_fee_as_input)
|
||||
}
|
||||
if (event.missed_forward_fee_as_output) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "missed_forward_fee_as_output", event.missed_forward_fee_as_output)
|
||||
}
|
||||
if (event.forward_fee_as_input) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "forward_fee_as_input", event.forward_fee_as_input)
|
||||
}
|
||||
if (event.forward_fee_as_output) {
|
||||
await repo.increment({ day_unix: dayUnix, channel_id: channelId }, "forward_fee_as_output", event.forward_fee_as_output)
|
||||
}
|
||||
if (event.latest_index_offset) {
|
||||
await repo.update(existing.serial_id, { latest_index_offset: event.latest_index_offset })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
const getTimeQuery = ({ from, to }: { from?: number, to?: number }) => {
|
||||
if (!!from && !!to) {
|
||||
return { where: { created_at: Between<Date>(new Date(from * 1000), new Date(to * 1000)) } }
|
||||
} else if (!!from) {
|
||||
return { where: { created_at: MoreThanOrEqual<Date>(new Date(from * 1000)) } }
|
||||
} else if (!!to) {
|
||||
return { where: { created_at: LessThanOrEqual<Date>(new Date(to * 1000)) } }
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
const getTodayUnix = () => {
|
||||
const now = new Date()
|
||||
return new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime() / 1000
|
||||
}
|
||||
|
|
@ -1,180 +1,180 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class Initial1703170309875 implements MigrationInterface {
|
||||
name = 'Initial1703170309875'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "user" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" varchar NOT NULL, "balance_sats" integer NOT NULL DEFAULT (0), "locked" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_758b8ce7c18b9d347461b30228" ON "user" ("user_id") `);
|
||||
await queryRunner.query(`CREATE TABLE "product" ("product_id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "price_sats" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "ownerSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE TABLE "application" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_id" varchar NOT NULL, "name" varchar NOT NULL, "allow_user_creation" boolean NOT NULL DEFAULT (0), "nostr_private_key" varchar, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "ownerSerialId" integer, CONSTRAINT "UQ_608bb41e7e1ef5f6d7abb07e394" UNIQUE ("name"), CONSTRAINT "UQ_f190e1a83a524035b84c1fe0696" UNIQUE ("nostr_private_key"), CONSTRAINT "UQ_87c12c4528183bf7a211221cc3c" UNIQUE ("nostr_public_key"))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e04964210949f10bb25dc6e747" ON "application" ("app_id") `);
|
||||
await queryRunner.query(`CREATE TABLE "user_receiving_invoice" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "expires_at_unix" integer NOT NULL, "paid_at_unix" integer NOT NULL DEFAULT (0), "internal" boolean NOT NULL DEFAULT (0), "paidByLnd" boolean NOT NULL DEFAULT (0), "callbackUrl" varchar NOT NULL DEFAULT (''), "paid_amount" integer NOT NULL DEFAULT (0), "service_fee" integer NOT NULL DEFAULT (0), "zap_info" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "productProductId" varchar, "payerSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a131e6b58f084f1340538681b5" ON "user_receiving_invoice" ("invoice") `);
|
||||
await queryRunner.query(`CREATE TABLE "user_receiving_address" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "address" varchar NOT NULL, "callbackUrl" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ecf415a7a0b2fa64aa03c5b067" ON "user_receiving_address" ("address") `);
|
||||
await queryRunner.query(`CREATE TABLE "address_receiving_transaction" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "tx_hash" varchar NOT NULL, "output_index" integer NOT NULL, "paid_amount" integer NOT NULL, "service_fee" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "confs" integer NOT NULL DEFAULT (0), "broadcast_height" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userAddressSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "address_receiving_transaction_unique" ON "address_receiving_transaction" ("tx_hash", "output_index") `);
|
||||
await queryRunner.query(`CREATE TABLE "application_user" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "identifier" varchar NOT NULL, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "applicationSerialId" integer, CONSTRAINT "UQ_3175dc397c8285d1e532554dea5" UNIQUE ("nostr_public_key"), CONSTRAINT "REL_0796a381bcc624f52e9a155712" UNIQUE ("userSerialId"))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0a0dbb25a73306b037dec82251" ON "application_user" ("identifier") `);
|
||||
await queryRunner.query(`CREATE TABLE "user_basic_auth" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "secret_sha256" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, CONSTRAINT "REL_4474a3cab08c387040d4c66f33" UNIQUE ("userSerialId"))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_361b01c8e10453b2f979246535" ON "user_basic_auth" ("name") `);
|
||||
await queryRunner.query(`CREATE TABLE "user_ephemeral_key" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "key" varchar NOT NULL, "type" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b9628a8315cdc6afed037563d9" ON "user_ephemeral_key" ("key") `);
|
||||
await queryRunner.query(`CREATE TABLE "user_invoice_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "paid_amount" integer NOT NULL, "routing_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a609a4d3d8d9b07b90692a3c45" ON "user_invoice_payment" ("invoice") `);
|
||||
await queryRunner.query(`CREATE TABLE "user_to_user_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "paid_amount" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "fromUserSerialId" integer, "toUserSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE TABLE "user_transaction_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "address" varchar NOT NULL, "tx_hash" varchar NOT NULL, "output_index" integer NOT NULL, "paid_amount" integer NOT NULL, "chain_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "confs" integer NOT NULL DEFAULT (0), "broadcast_height" integer NOT NULL DEFAULT (0), "something" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "user_transaction_unique" ON "user_transaction_payment" ("tx_hash", "output_index") `);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_product" ("product_id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "price_sats" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "ownerSerialId" integer, CONSTRAINT "FK_9e072a061430561c76a631f506c" FOREIGN KEY ("ownerSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_product"("product_id", "name", "price_sats", "created_at", "updated_at", "ownerSerialId") SELECT "product_id", "name", "price_sats", "created_at", "updated_at", "ownerSerialId" FROM "product"`);
|
||||
await queryRunner.query(`DROP TABLE "product"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_product" RENAME TO "product"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_e04964210949f10bb25dc6e747"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_application" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_id" varchar NOT NULL, "name" varchar NOT NULL, "allow_user_creation" boolean NOT NULL DEFAULT (0), "nostr_private_key" varchar, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "ownerSerialId" integer, CONSTRAINT "UQ_608bb41e7e1ef5f6d7abb07e394" UNIQUE ("name"), CONSTRAINT "UQ_f190e1a83a524035b84c1fe0696" UNIQUE ("nostr_private_key"), CONSTRAINT "UQ_87c12c4528183bf7a211221cc3c" UNIQUE ("nostr_public_key"), CONSTRAINT "FK_cf04162e14c4641072b08f263c5" FOREIGN KEY ("ownerSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_application"("serial_id", "app_id", "name", "allow_user_creation", "nostr_private_key", "nostr_public_key", "created_at", "updated_at", "ownerSerialId") SELECT "serial_id", "app_id", "name", "allow_user_creation", "nostr_private_key", "nostr_public_key", "created_at", "updated_at", "ownerSerialId" FROM "application"`);
|
||||
await queryRunner.query(`DROP TABLE "application"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_application" RENAME TO "application"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e04964210949f10bb25dc6e747" ON "application" ("app_id") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a131e6b58f084f1340538681b5"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_receiving_invoice" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "expires_at_unix" integer NOT NULL, "paid_at_unix" integer NOT NULL DEFAULT (0), "internal" boolean NOT NULL DEFAULT (0), "paidByLnd" boolean NOT NULL DEFAULT (0), "callbackUrl" varchar NOT NULL DEFAULT (''), "paid_amount" integer NOT NULL DEFAULT (0), "service_fee" integer NOT NULL DEFAULT (0), "zap_info" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "productProductId" varchar, "payerSerialId" integer, "linkedApplicationSerialId" integer, CONSTRAINT "FK_2c0dfb3483f3e5e7e3cdd5dc71f" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_5263bde2a519db9ea608b702ec8" FOREIGN KEY ("productProductId") REFERENCES "product" ("product_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_d4bb1e4c60e8a869f1f43ca2e31" FOREIGN KEY ("payerSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_714a8b7d4f89f8a802ca181b789" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_receiving_invoice"("serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId") SELECT "serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId" FROM "user_receiving_invoice"`);
|
||||
await queryRunner.query(`DROP TABLE "user_receiving_invoice"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_receiving_invoice" RENAME TO "user_receiving_invoice"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a131e6b58f084f1340538681b5" ON "user_receiving_invoice" ("invoice") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_ecf415a7a0b2fa64aa03c5b067"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_receiving_address" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "address" varchar NOT NULL, "callbackUrl" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer, CONSTRAINT "FK_823e78a588858598aa91766c7ff" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_58480f93d00174952e00a0c0a5d" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_receiving_address"("serial_id", "address", "callbackUrl", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "address", "callbackUrl", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "user_receiving_address"`);
|
||||
await queryRunner.query(`DROP TABLE "user_receiving_address"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_receiving_address" RENAME TO "user_receiving_address"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ecf415a7a0b2fa64aa03c5b067" ON "user_receiving_address" ("address") `);
|
||||
await queryRunner.query(`DROP INDEX "address_receiving_transaction_unique"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_address_receiving_transaction" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "tx_hash" varchar NOT NULL, "output_index" integer NOT NULL, "paid_amount" integer NOT NULL, "service_fee" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "confs" integer NOT NULL DEFAULT (0), "broadcast_height" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userAddressSerialId" integer, CONSTRAINT "FK_fa29dc074c2e067beca4aefeaad" FOREIGN KEY ("userAddressSerialId") REFERENCES "user_receiving_address" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_address_receiving_transaction"("serial_id", "tx_hash", "output_index", "paid_amount", "service_fee", "paid_at_unix", "internal", "confs", "broadcast_height", "created_at", "updated_at", "userAddressSerialId") SELECT "serial_id", "tx_hash", "output_index", "paid_amount", "service_fee", "paid_at_unix", "internal", "confs", "broadcast_height", "created_at", "updated_at", "userAddressSerialId" FROM "address_receiving_transaction"`);
|
||||
await queryRunner.query(`DROP TABLE "address_receiving_transaction"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_address_receiving_transaction" RENAME TO "address_receiving_transaction"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "address_receiving_transaction_unique" ON "address_receiving_transaction" ("tx_hash", "output_index") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_0a0dbb25a73306b037dec82251"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_application_user" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "identifier" varchar NOT NULL, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "applicationSerialId" integer, CONSTRAINT "UQ_3175dc397c8285d1e532554dea5" UNIQUE ("nostr_public_key"), CONSTRAINT "REL_0796a381bcc624f52e9a155712" UNIQUE ("userSerialId"), CONSTRAINT "FK_0796a381bcc624f52e9a155712b" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_1b3bdb6f660cd99533a1e673ef1" FOREIGN KEY ("applicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_application_user"("serial_id", "identifier", "nostr_public_key", "created_at", "updated_at", "userSerialId", "applicationSerialId") SELECT "serial_id", "identifier", "nostr_public_key", "created_at", "updated_at", "userSerialId", "applicationSerialId" FROM "application_user"`);
|
||||
await queryRunner.query(`DROP TABLE "application_user"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_application_user" RENAME TO "application_user"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0a0dbb25a73306b037dec82251" ON "application_user" ("identifier") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_361b01c8e10453b2f979246535"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_basic_auth" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "secret_sha256" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, CONSTRAINT "REL_4474a3cab08c387040d4c66f33" UNIQUE ("userSerialId"), CONSTRAINT "FK_4474a3cab08c387040d4c66f337" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_basic_auth"("serial_id", "name", "secret_sha256", "created_at", "updated_at", "userSerialId") SELECT "serial_id", "name", "secret_sha256", "created_at", "updated_at", "userSerialId" FROM "user_basic_auth"`);
|
||||
await queryRunner.query(`DROP TABLE "user_basic_auth"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_basic_auth" RENAME TO "user_basic_auth"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_361b01c8e10453b2f979246535" ON "user_basic_auth" ("name") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_b9628a8315cdc6afed037563d9"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_ephemeral_key" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "key" varchar NOT NULL, "type" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer, CONSTRAINT "FK_13af336122427a543067b13c453" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_9dff387698d432ff8b931ea954d" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_ephemeral_key"("serial_id", "key", "type", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "key", "type", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "user_ephemeral_key"`);
|
||||
await queryRunner.query(`DROP TABLE "user_ephemeral_key"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_ephemeral_key" RENAME TO "user_ephemeral_key"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b9628a8315cdc6afed037563d9" ON "user_ephemeral_key" ("key") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a609a4d3d8d9b07b90692a3c45"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_invoice_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "paid_amount" integer NOT NULL, "routing_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer, CONSTRAINT "FK_ef2aa6761ab681bbbd5f94e0fcb" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_6bcac90887eea1dc61d37db2994" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_invoice_payment"("serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "user_invoice_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "user_invoice_payment"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_invoice_payment" RENAME TO "user_invoice_payment"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a609a4d3d8d9b07b90692a3c45" ON "user_invoice_payment" ("invoice") `);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_to_user_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "paid_amount" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "fromUserSerialId" integer, "toUserSerialId" integer, "linkedApplicationSerialId" integer, CONSTRAINT "FK_239687f55807600edd2c515628a" FOREIGN KEY ("fromUserSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_d88c97ca05db8d408682a5944d2" FOREIGN KEY ("toUserSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_f08d33ed4949525faed9218ad25" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_to_user_payment"("serial_id", "paid_amount", "service_fees", "paid_at_unix", "created_at", "updated_at", "fromUserSerialId", "toUserSerialId", "linkedApplicationSerialId") SELECT "serial_id", "paid_amount", "service_fees", "paid_at_unix", "created_at", "updated_at", "fromUserSerialId", "toUserSerialId", "linkedApplicationSerialId" FROM "user_to_user_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "user_to_user_payment"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_to_user_payment" RENAME TO "user_to_user_payment"`);
|
||||
await queryRunner.query(`DROP INDEX "user_transaction_unique"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_transaction_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "address" varchar NOT NULL, "tx_hash" varchar NOT NULL, "output_index" integer NOT NULL, "paid_amount" integer NOT NULL, "chain_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "confs" integer NOT NULL DEFAULT (0), "broadcast_height" integer NOT NULL DEFAULT (0), "something" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer, CONSTRAINT "FK_6f24c901b4103f7146eb615a5db" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_75abdd29270979e901da0dba7b9" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_transaction_payment"("serial_id", "address", "tx_hash", "output_index", "paid_amount", "chain_fees", "service_fees", "paid_at_unix", "internal", "confs", "broadcast_height", "something", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "address", "tx_hash", "output_index", "paid_amount", "chain_fees", "service_fees", "paid_at_unix", "internal", "confs", "broadcast_height", "something", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "user_transaction_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "user_transaction_payment"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_transaction_payment" RENAME TO "user_transaction_payment"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "user_transaction_unique" ON "user_transaction_payment" ("tx_hash", "output_index") `);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "user_transaction_unique"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_transaction_payment" RENAME TO "temporary_user_transaction_payment"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_transaction_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "address" varchar NOT NULL, "tx_hash" varchar NOT NULL, "output_index" integer NOT NULL, "paid_amount" integer NOT NULL, "chain_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "confs" integer NOT NULL DEFAULT (0), "broadcast_height" integer NOT NULL DEFAULT (0), "something" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "user_transaction_payment"("serial_id", "address", "tx_hash", "output_index", "paid_amount", "chain_fees", "service_fees", "paid_at_unix", "internal", "confs", "broadcast_height", "something", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "address", "tx_hash", "output_index", "paid_amount", "chain_fees", "service_fees", "paid_at_unix", "internal", "confs", "broadcast_height", "something", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "temporary_user_transaction_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_transaction_payment"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "user_transaction_unique" ON "user_transaction_payment" ("tx_hash", "output_index") `);
|
||||
await queryRunner.query(`ALTER TABLE "user_to_user_payment" RENAME TO "temporary_user_to_user_payment"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_to_user_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "paid_amount" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "fromUserSerialId" integer, "toUserSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "user_to_user_payment"("serial_id", "paid_amount", "service_fees", "paid_at_unix", "created_at", "updated_at", "fromUserSerialId", "toUserSerialId", "linkedApplicationSerialId") SELECT "serial_id", "paid_amount", "service_fees", "paid_at_unix", "created_at", "updated_at", "fromUserSerialId", "toUserSerialId", "linkedApplicationSerialId" FROM "temporary_user_to_user_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_to_user_payment"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a609a4d3d8d9b07b90692a3c45"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_invoice_payment" RENAME TO "temporary_user_invoice_payment"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_invoice_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "paid_amount" integer NOT NULL, "routing_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "user_invoice_payment"("serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "temporary_user_invoice_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_invoice_payment"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a609a4d3d8d9b07b90692a3c45" ON "user_invoice_payment" ("invoice") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_b9628a8315cdc6afed037563d9"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_ephemeral_key" RENAME TO "temporary_user_ephemeral_key"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_ephemeral_key" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "key" varchar NOT NULL, "type" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "user_ephemeral_key"("serial_id", "key", "type", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "key", "type", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "temporary_user_ephemeral_key"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_ephemeral_key"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b9628a8315cdc6afed037563d9" ON "user_ephemeral_key" ("key") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_361b01c8e10453b2f979246535"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_basic_auth" RENAME TO "temporary_user_basic_auth"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_basic_auth" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "secret_sha256" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, CONSTRAINT "REL_4474a3cab08c387040d4c66f33" UNIQUE ("userSerialId"))`);
|
||||
await queryRunner.query(`INSERT INTO "user_basic_auth"("serial_id", "name", "secret_sha256", "created_at", "updated_at", "userSerialId") SELECT "serial_id", "name", "secret_sha256", "created_at", "updated_at", "userSerialId" FROM "temporary_user_basic_auth"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_basic_auth"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_361b01c8e10453b2f979246535" ON "user_basic_auth" ("name") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_0a0dbb25a73306b037dec82251"`);
|
||||
await queryRunner.query(`ALTER TABLE "application_user" RENAME TO "temporary_application_user"`);
|
||||
await queryRunner.query(`CREATE TABLE "application_user" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "identifier" varchar NOT NULL, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "applicationSerialId" integer, CONSTRAINT "UQ_3175dc397c8285d1e532554dea5" UNIQUE ("nostr_public_key"), CONSTRAINT "REL_0796a381bcc624f52e9a155712" UNIQUE ("userSerialId"))`);
|
||||
await queryRunner.query(`INSERT INTO "application_user"("serial_id", "identifier", "nostr_public_key", "created_at", "updated_at", "userSerialId", "applicationSerialId") SELECT "serial_id", "identifier", "nostr_public_key", "created_at", "updated_at", "userSerialId", "applicationSerialId" FROM "temporary_application_user"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_application_user"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0a0dbb25a73306b037dec82251" ON "application_user" ("identifier") `);
|
||||
await queryRunner.query(`DROP INDEX "address_receiving_transaction_unique"`);
|
||||
await queryRunner.query(`ALTER TABLE "address_receiving_transaction" RENAME TO "temporary_address_receiving_transaction"`);
|
||||
await queryRunner.query(`CREATE TABLE "address_receiving_transaction" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "tx_hash" varchar NOT NULL, "output_index" integer NOT NULL, "paid_amount" integer NOT NULL, "service_fee" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "confs" integer NOT NULL DEFAULT (0), "broadcast_height" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userAddressSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "address_receiving_transaction"("serial_id", "tx_hash", "output_index", "paid_amount", "service_fee", "paid_at_unix", "internal", "confs", "broadcast_height", "created_at", "updated_at", "userAddressSerialId") SELECT "serial_id", "tx_hash", "output_index", "paid_amount", "service_fee", "paid_at_unix", "internal", "confs", "broadcast_height", "created_at", "updated_at", "userAddressSerialId" FROM "temporary_address_receiving_transaction"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_address_receiving_transaction"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "address_receiving_transaction_unique" ON "address_receiving_transaction" ("tx_hash", "output_index") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_ecf415a7a0b2fa64aa03c5b067"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_receiving_address" RENAME TO "temporary_user_receiving_address"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_receiving_address" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "address" varchar NOT NULL, "callbackUrl" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "user_receiving_address"("serial_id", "address", "callbackUrl", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "address", "callbackUrl", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "temporary_user_receiving_address"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_receiving_address"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ecf415a7a0b2fa64aa03c5b067" ON "user_receiving_address" ("address") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a131e6b58f084f1340538681b5"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_receiving_invoice" RENAME TO "temporary_user_receiving_invoice"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_receiving_invoice" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "expires_at_unix" integer NOT NULL, "paid_at_unix" integer NOT NULL DEFAULT (0), "internal" boolean NOT NULL DEFAULT (0), "paidByLnd" boolean NOT NULL DEFAULT (0), "callbackUrl" varchar NOT NULL DEFAULT (''), "paid_amount" integer NOT NULL DEFAULT (0), "service_fee" integer NOT NULL DEFAULT (0), "zap_info" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "productProductId" varchar, "payerSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "user_receiving_invoice"("serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId") SELECT "serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId" FROM "temporary_user_receiving_invoice"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_receiving_invoice"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a131e6b58f084f1340538681b5" ON "user_receiving_invoice" ("invoice") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_e04964210949f10bb25dc6e747"`);
|
||||
await queryRunner.query(`ALTER TABLE "application" RENAME TO "temporary_application"`);
|
||||
await queryRunner.query(`CREATE TABLE "application" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_id" varchar NOT NULL, "name" varchar NOT NULL, "allow_user_creation" boolean NOT NULL DEFAULT (0), "nostr_private_key" varchar, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "ownerSerialId" integer, CONSTRAINT "UQ_608bb41e7e1ef5f6d7abb07e394" UNIQUE ("name"), CONSTRAINT "UQ_f190e1a83a524035b84c1fe0696" UNIQUE ("nostr_private_key"), CONSTRAINT "UQ_87c12c4528183bf7a211221cc3c" UNIQUE ("nostr_public_key"))`);
|
||||
await queryRunner.query(`INSERT INTO "application"("serial_id", "app_id", "name", "allow_user_creation", "nostr_private_key", "nostr_public_key", "created_at", "updated_at", "ownerSerialId") SELECT "serial_id", "app_id", "name", "allow_user_creation", "nostr_private_key", "nostr_public_key", "created_at", "updated_at", "ownerSerialId" FROM "temporary_application"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_application"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e04964210949f10bb25dc6e747" ON "application" ("app_id") `);
|
||||
await queryRunner.query(`ALTER TABLE "product" RENAME TO "temporary_product"`);
|
||||
await queryRunner.query(`CREATE TABLE "product" ("product_id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "price_sats" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "ownerSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "product"("product_id", "name", "price_sats", "created_at", "updated_at", "ownerSerialId") SELECT "product_id", "name", "price_sats", "created_at", "updated_at", "ownerSerialId" FROM "temporary_product"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_product"`);
|
||||
await queryRunner.query(`DROP INDEX "user_transaction_unique"`);
|
||||
await queryRunner.query(`DROP TABLE "user_transaction_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "user_to_user_payment"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a609a4d3d8d9b07b90692a3c45"`);
|
||||
await queryRunner.query(`DROP TABLE "user_invoice_payment"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_b9628a8315cdc6afed037563d9"`);
|
||||
await queryRunner.query(`DROP TABLE "user_ephemeral_key"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_361b01c8e10453b2f979246535"`);
|
||||
await queryRunner.query(`DROP TABLE "user_basic_auth"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_0a0dbb25a73306b037dec82251"`);
|
||||
await queryRunner.query(`DROP TABLE "application_user"`);
|
||||
await queryRunner.query(`DROP INDEX "address_receiving_transaction_unique"`);
|
||||
await queryRunner.query(`DROP TABLE "address_receiving_transaction"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_ecf415a7a0b2fa64aa03c5b067"`);
|
||||
await queryRunner.query(`DROP TABLE "user_receiving_address"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a131e6b58f084f1340538681b5"`);
|
||||
await queryRunner.query(`DROP TABLE "user_receiving_invoice"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_e04964210949f10bb25dc6e747"`);
|
||||
await queryRunner.query(`DROP TABLE "application"`);
|
||||
await queryRunner.query(`DROP TABLE "product"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_758b8ce7c18b9d347461b30228"`);
|
||||
await queryRunner.query(`DROP TABLE "user"`);
|
||||
}
|
||||
|
||||
}
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class Initial1703170309875 implements MigrationInterface {
|
||||
name = 'Initial1703170309875'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "user" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" varchar NOT NULL, "balance_sats" integer NOT NULL DEFAULT (0), "locked" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_758b8ce7c18b9d347461b30228" ON "user" ("user_id") `);
|
||||
await queryRunner.query(`CREATE TABLE "product" ("product_id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "price_sats" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "ownerSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE TABLE "application" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_id" varchar NOT NULL, "name" varchar NOT NULL, "allow_user_creation" boolean NOT NULL DEFAULT (0), "nostr_private_key" varchar, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "ownerSerialId" integer, CONSTRAINT "UQ_608bb41e7e1ef5f6d7abb07e394" UNIQUE ("name"), CONSTRAINT "UQ_f190e1a83a524035b84c1fe0696" UNIQUE ("nostr_private_key"), CONSTRAINT "UQ_87c12c4528183bf7a211221cc3c" UNIQUE ("nostr_public_key"))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e04964210949f10bb25dc6e747" ON "application" ("app_id") `);
|
||||
await queryRunner.query(`CREATE TABLE "user_receiving_invoice" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "expires_at_unix" integer NOT NULL, "paid_at_unix" integer NOT NULL DEFAULT (0), "internal" boolean NOT NULL DEFAULT (0), "paidByLnd" boolean NOT NULL DEFAULT (0), "callbackUrl" varchar NOT NULL DEFAULT (''), "paid_amount" integer NOT NULL DEFAULT (0), "service_fee" integer NOT NULL DEFAULT (0), "zap_info" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "productProductId" varchar, "payerSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a131e6b58f084f1340538681b5" ON "user_receiving_invoice" ("invoice") `);
|
||||
await queryRunner.query(`CREATE TABLE "user_receiving_address" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "address" varchar NOT NULL, "callbackUrl" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ecf415a7a0b2fa64aa03c5b067" ON "user_receiving_address" ("address") `);
|
||||
await queryRunner.query(`CREATE TABLE "address_receiving_transaction" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "tx_hash" varchar NOT NULL, "output_index" integer NOT NULL, "paid_amount" integer NOT NULL, "service_fee" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "confs" integer NOT NULL DEFAULT (0), "broadcast_height" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userAddressSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "address_receiving_transaction_unique" ON "address_receiving_transaction" ("tx_hash", "output_index") `);
|
||||
await queryRunner.query(`CREATE TABLE "application_user" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "identifier" varchar NOT NULL, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "applicationSerialId" integer, CONSTRAINT "UQ_3175dc397c8285d1e532554dea5" UNIQUE ("nostr_public_key"), CONSTRAINT "REL_0796a381bcc624f52e9a155712" UNIQUE ("userSerialId"))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0a0dbb25a73306b037dec82251" ON "application_user" ("identifier") `);
|
||||
await queryRunner.query(`CREATE TABLE "user_basic_auth" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "secret_sha256" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, CONSTRAINT "REL_4474a3cab08c387040d4c66f33" UNIQUE ("userSerialId"))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_361b01c8e10453b2f979246535" ON "user_basic_auth" ("name") `);
|
||||
await queryRunner.query(`CREATE TABLE "user_ephemeral_key" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "key" varchar NOT NULL, "type" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b9628a8315cdc6afed037563d9" ON "user_ephemeral_key" ("key") `);
|
||||
await queryRunner.query(`CREATE TABLE "user_invoice_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "paid_amount" integer NOT NULL, "routing_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a609a4d3d8d9b07b90692a3c45" ON "user_invoice_payment" ("invoice") `);
|
||||
await queryRunner.query(`CREATE TABLE "user_to_user_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "paid_amount" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "fromUserSerialId" integer, "toUserSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE TABLE "user_transaction_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "address" varchar NOT NULL, "tx_hash" varchar NOT NULL, "output_index" integer NOT NULL, "paid_amount" integer NOT NULL, "chain_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "confs" integer NOT NULL DEFAULT (0), "broadcast_height" integer NOT NULL DEFAULT (0), "something" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "user_transaction_unique" ON "user_transaction_payment" ("tx_hash", "output_index") `);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_product" ("product_id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "price_sats" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "ownerSerialId" integer, CONSTRAINT "FK_9e072a061430561c76a631f506c" FOREIGN KEY ("ownerSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_product"("product_id", "name", "price_sats", "created_at", "updated_at", "ownerSerialId") SELECT "product_id", "name", "price_sats", "created_at", "updated_at", "ownerSerialId" FROM "product"`);
|
||||
await queryRunner.query(`DROP TABLE "product"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_product" RENAME TO "product"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_e04964210949f10bb25dc6e747"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_application" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_id" varchar NOT NULL, "name" varchar NOT NULL, "allow_user_creation" boolean NOT NULL DEFAULT (0), "nostr_private_key" varchar, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "ownerSerialId" integer, CONSTRAINT "UQ_608bb41e7e1ef5f6d7abb07e394" UNIQUE ("name"), CONSTRAINT "UQ_f190e1a83a524035b84c1fe0696" UNIQUE ("nostr_private_key"), CONSTRAINT "UQ_87c12c4528183bf7a211221cc3c" UNIQUE ("nostr_public_key"), CONSTRAINT "FK_cf04162e14c4641072b08f263c5" FOREIGN KEY ("ownerSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_application"("serial_id", "app_id", "name", "allow_user_creation", "nostr_private_key", "nostr_public_key", "created_at", "updated_at", "ownerSerialId") SELECT "serial_id", "app_id", "name", "allow_user_creation", "nostr_private_key", "nostr_public_key", "created_at", "updated_at", "ownerSerialId" FROM "application"`);
|
||||
await queryRunner.query(`DROP TABLE "application"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_application" RENAME TO "application"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e04964210949f10bb25dc6e747" ON "application" ("app_id") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a131e6b58f084f1340538681b5"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_receiving_invoice" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "expires_at_unix" integer NOT NULL, "paid_at_unix" integer NOT NULL DEFAULT (0), "internal" boolean NOT NULL DEFAULT (0), "paidByLnd" boolean NOT NULL DEFAULT (0), "callbackUrl" varchar NOT NULL DEFAULT (''), "paid_amount" integer NOT NULL DEFAULT (0), "service_fee" integer NOT NULL DEFAULT (0), "zap_info" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "productProductId" varchar, "payerSerialId" integer, "linkedApplicationSerialId" integer, CONSTRAINT "FK_2c0dfb3483f3e5e7e3cdd5dc71f" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_5263bde2a519db9ea608b702ec8" FOREIGN KEY ("productProductId") REFERENCES "product" ("product_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_d4bb1e4c60e8a869f1f43ca2e31" FOREIGN KEY ("payerSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_714a8b7d4f89f8a802ca181b789" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_receiving_invoice"("serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId") SELECT "serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId" FROM "user_receiving_invoice"`);
|
||||
await queryRunner.query(`DROP TABLE "user_receiving_invoice"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_receiving_invoice" RENAME TO "user_receiving_invoice"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a131e6b58f084f1340538681b5" ON "user_receiving_invoice" ("invoice") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_ecf415a7a0b2fa64aa03c5b067"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_receiving_address" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "address" varchar NOT NULL, "callbackUrl" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer, CONSTRAINT "FK_823e78a588858598aa91766c7ff" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_58480f93d00174952e00a0c0a5d" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_receiving_address"("serial_id", "address", "callbackUrl", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "address", "callbackUrl", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "user_receiving_address"`);
|
||||
await queryRunner.query(`DROP TABLE "user_receiving_address"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_receiving_address" RENAME TO "user_receiving_address"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ecf415a7a0b2fa64aa03c5b067" ON "user_receiving_address" ("address") `);
|
||||
await queryRunner.query(`DROP INDEX "address_receiving_transaction_unique"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_address_receiving_transaction" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "tx_hash" varchar NOT NULL, "output_index" integer NOT NULL, "paid_amount" integer NOT NULL, "service_fee" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "confs" integer NOT NULL DEFAULT (0), "broadcast_height" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userAddressSerialId" integer, CONSTRAINT "FK_fa29dc074c2e067beca4aefeaad" FOREIGN KEY ("userAddressSerialId") REFERENCES "user_receiving_address" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_address_receiving_transaction"("serial_id", "tx_hash", "output_index", "paid_amount", "service_fee", "paid_at_unix", "internal", "confs", "broadcast_height", "created_at", "updated_at", "userAddressSerialId") SELECT "serial_id", "tx_hash", "output_index", "paid_amount", "service_fee", "paid_at_unix", "internal", "confs", "broadcast_height", "created_at", "updated_at", "userAddressSerialId" FROM "address_receiving_transaction"`);
|
||||
await queryRunner.query(`DROP TABLE "address_receiving_transaction"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_address_receiving_transaction" RENAME TO "address_receiving_transaction"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "address_receiving_transaction_unique" ON "address_receiving_transaction" ("tx_hash", "output_index") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_0a0dbb25a73306b037dec82251"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_application_user" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "identifier" varchar NOT NULL, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "applicationSerialId" integer, CONSTRAINT "UQ_3175dc397c8285d1e532554dea5" UNIQUE ("nostr_public_key"), CONSTRAINT "REL_0796a381bcc624f52e9a155712" UNIQUE ("userSerialId"), CONSTRAINT "FK_0796a381bcc624f52e9a155712b" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_1b3bdb6f660cd99533a1e673ef1" FOREIGN KEY ("applicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_application_user"("serial_id", "identifier", "nostr_public_key", "created_at", "updated_at", "userSerialId", "applicationSerialId") SELECT "serial_id", "identifier", "nostr_public_key", "created_at", "updated_at", "userSerialId", "applicationSerialId" FROM "application_user"`);
|
||||
await queryRunner.query(`DROP TABLE "application_user"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_application_user" RENAME TO "application_user"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0a0dbb25a73306b037dec82251" ON "application_user" ("identifier") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_361b01c8e10453b2f979246535"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_basic_auth" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "secret_sha256" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, CONSTRAINT "REL_4474a3cab08c387040d4c66f33" UNIQUE ("userSerialId"), CONSTRAINT "FK_4474a3cab08c387040d4c66f337" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_basic_auth"("serial_id", "name", "secret_sha256", "created_at", "updated_at", "userSerialId") SELECT "serial_id", "name", "secret_sha256", "created_at", "updated_at", "userSerialId" FROM "user_basic_auth"`);
|
||||
await queryRunner.query(`DROP TABLE "user_basic_auth"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_basic_auth" RENAME TO "user_basic_auth"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_361b01c8e10453b2f979246535" ON "user_basic_auth" ("name") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_b9628a8315cdc6afed037563d9"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_ephemeral_key" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "key" varchar NOT NULL, "type" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer, CONSTRAINT "FK_13af336122427a543067b13c453" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_9dff387698d432ff8b931ea954d" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_ephemeral_key"("serial_id", "key", "type", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "key", "type", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "user_ephemeral_key"`);
|
||||
await queryRunner.query(`DROP TABLE "user_ephemeral_key"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_ephemeral_key" RENAME TO "user_ephemeral_key"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b9628a8315cdc6afed037563d9" ON "user_ephemeral_key" ("key") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a609a4d3d8d9b07b90692a3c45"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_invoice_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "paid_amount" integer NOT NULL, "routing_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer, CONSTRAINT "FK_ef2aa6761ab681bbbd5f94e0fcb" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_6bcac90887eea1dc61d37db2994" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_invoice_payment"("serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "user_invoice_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "user_invoice_payment"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_invoice_payment" RENAME TO "user_invoice_payment"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a609a4d3d8d9b07b90692a3c45" ON "user_invoice_payment" ("invoice") `);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_to_user_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "paid_amount" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "fromUserSerialId" integer, "toUserSerialId" integer, "linkedApplicationSerialId" integer, CONSTRAINT "FK_239687f55807600edd2c515628a" FOREIGN KEY ("fromUserSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_d88c97ca05db8d408682a5944d2" FOREIGN KEY ("toUserSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_f08d33ed4949525faed9218ad25" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_to_user_payment"("serial_id", "paid_amount", "service_fees", "paid_at_unix", "created_at", "updated_at", "fromUserSerialId", "toUserSerialId", "linkedApplicationSerialId") SELECT "serial_id", "paid_amount", "service_fees", "paid_at_unix", "created_at", "updated_at", "fromUserSerialId", "toUserSerialId", "linkedApplicationSerialId" FROM "user_to_user_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "user_to_user_payment"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_to_user_payment" RENAME TO "user_to_user_payment"`);
|
||||
await queryRunner.query(`DROP INDEX "user_transaction_unique"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_transaction_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "address" varchar NOT NULL, "tx_hash" varchar NOT NULL, "output_index" integer NOT NULL, "paid_amount" integer NOT NULL, "chain_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "confs" integer NOT NULL DEFAULT (0), "broadcast_height" integer NOT NULL DEFAULT (0), "something" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer, CONSTRAINT "FK_6f24c901b4103f7146eb615a5db" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_75abdd29270979e901da0dba7b9" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_transaction_payment"("serial_id", "address", "tx_hash", "output_index", "paid_amount", "chain_fees", "service_fees", "paid_at_unix", "internal", "confs", "broadcast_height", "something", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "address", "tx_hash", "output_index", "paid_amount", "chain_fees", "service_fees", "paid_at_unix", "internal", "confs", "broadcast_height", "something", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "user_transaction_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "user_transaction_payment"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_transaction_payment" RENAME TO "user_transaction_payment"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "user_transaction_unique" ON "user_transaction_payment" ("tx_hash", "output_index") `);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "user_transaction_unique"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_transaction_payment" RENAME TO "temporary_user_transaction_payment"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_transaction_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "address" varchar NOT NULL, "tx_hash" varchar NOT NULL, "output_index" integer NOT NULL, "paid_amount" integer NOT NULL, "chain_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "confs" integer NOT NULL DEFAULT (0), "broadcast_height" integer NOT NULL DEFAULT (0), "something" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "user_transaction_payment"("serial_id", "address", "tx_hash", "output_index", "paid_amount", "chain_fees", "service_fees", "paid_at_unix", "internal", "confs", "broadcast_height", "something", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "address", "tx_hash", "output_index", "paid_amount", "chain_fees", "service_fees", "paid_at_unix", "internal", "confs", "broadcast_height", "something", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "temporary_user_transaction_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_transaction_payment"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "user_transaction_unique" ON "user_transaction_payment" ("tx_hash", "output_index") `);
|
||||
await queryRunner.query(`ALTER TABLE "user_to_user_payment" RENAME TO "temporary_user_to_user_payment"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_to_user_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "paid_amount" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "fromUserSerialId" integer, "toUserSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "user_to_user_payment"("serial_id", "paid_amount", "service_fees", "paid_at_unix", "created_at", "updated_at", "fromUserSerialId", "toUserSerialId", "linkedApplicationSerialId") SELECT "serial_id", "paid_amount", "service_fees", "paid_at_unix", "created_at", "updated_at", "fromUserSerialId", "toUserSerialId", "linkedApplicationSerialId" FROM "temporary_user_to_user_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_to_user_payment"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a609a4d3d8d9b07b90692a3c45"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_invoice_payment" RENAME TO "temporary_user_invoice_payment"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_invoice_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "paid_amount" integer NOT NULL, "routing_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "user_invoice_payment"("serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "temporary_user_invoice_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_invoice_payment"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a609a4d3d8d9b07b90692a3c45" ON "user_invoice_payment" ("invoice") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_b9628a8315cdc6afed037563d9"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_ephemeral_key" RENAME TO "temporary_user_ephemeral_key"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_ephemeral_key" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "key" varchar NOT NULL, "type" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "user_ephemeral_key"("serial_id", "key", "type", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "key", "type", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "temporary_user_ephemeral_key"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_ephemeral_key"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b9628a8315cdc6afed037563d9" ON "user_ephemeral_key" ("key") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_361b01c8e10453b2f979246535"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_basic_auth" RENAME TO "temporary_user_basic_auth"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_basic_auth" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "secret_sha256" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, CONSTRAINT "REL_4474a3cab08c387040d4c66f33" UNIQUE ("userSerialId"))`);
|
||||
await queryRunner.query(`INSERT INTO "user_basic_auth"("serial_id", "name", "secret_sha256", "created_at", "updated_at", "userSerialId") SELECT "serial_id", "name", "secret_sha256", "created_at", "updated_at", "userSerialId" FROM "temporary_user_basic_auth"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_basic_auth"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_361b01c8e10453b2f979246535" ON "user_basic_auth" ("name") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_0a0dbb25a73306b037dec82251"`);
|
||||
await queryRunner.query(`ALTER TABLE "application_user" RENAME TO "temporary_application_user"`);
|
||||
await queryRunner.query(`CREATE TABLE "application_user" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "identifier" varchar NOT NULL, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "applicationSerialId" integer, CONSTRAINT "UQ_3175dc397c8285d1e532554dea5" UNIQUE ("nostr_public_key"), CONSTRAINT "REL_0796a381bcc624f52e9a155712" UNIQUE ("userSerialId"))`);
|
||||
await queryRunner.query(`INSERT INTO "application_user"("serial_id", "identifier", "nostr_public_key", "created_at", "updated_at", "userSerialId", "applicationSerialId") SELECT "serial_id", "identifier", "nostr_public_key", "created_at", "updated_at", "userSerialId", "applicationSerialId" FROM "temporary_application_user"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_application_user"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0a0dbb25a73306b037dec82251" ON "application_user" ("identifier") `);
|
||||
await queryRunner.query(`DROP INDEX "address_receiving_transaction_unique"`);
|
||||
await queryRunner.query(`ALTER TABLE "address_receiving_transaction" RENAME TO "temporary_address_receiving_transaction"`);
|
||||
await queryRunner.query(`CREATE TABLE "address_receiving_transaction" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "tx_hash" varchar NOT NULL, "output_index" integer NOT NULL, "paid_amount" integer NOT NULL, "service_fee" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "confs" integer NOT NULL DEFAULT (0), "broadcast_height" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userAddressSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "address_receiving_transaction"("serial_id", "tx_hash", "output_index", "paid_amount", "service_fee", "paid_at_unix", "internal", "confs", "broadcast_height", "created_at", "updated_at", "userAddressSerialId") SELECT "serial_id", "tx_hash", "output_index", "paid_amount", "service_fee", "paid_at_unix", "internal", "confs", "broadcast_height", "created_at", "updated_at", "userAddressSerialId" FROM "temporary_address_receiving_transaction"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_address_receiving_transaction"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "address_receiving_transaction_unique" ON "address_receiving_transaction" ("tx_hash", "output_index") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_ecf415a7a0b2fa64aa03c5b067"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_receiving_address" RENAME TO "temporary_user_receiving_address"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_receiving_address" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "address" varchar NOT NULL, "callbackUrl" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "user_receiving_address"("serial_id", "address", "callbackUrl", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId") SELECT "serial_id", "address", "callbackUrl", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId" FROM "temporary_user_receiving_address"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_receiving_address"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ecf415a7a0b2fa64aa03c5b067" ON "user_receiving_address" ("address") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a131e6b58f084f1340538681b5"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_receiving_invoice" RENAME TO "temporary_user_receiving_invoice"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_receiving_invoice" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "expires_at_unix" integer NOT NULL, "paid_at_unix" integer NOT NULL DEFAULT (0), "internal" boolean NOT NULL DEFAULT (0), "paidByLnd" boolean NOT NULL DEFAULT (0), "callbackUrl" varchar NOT NULL DEFAULT (''), "paid_amount" integer NOT NULL DEFAULT (0), "service_fee" integer NOT NULL DEFAULT (0), "zap_info" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "productProductId" varchar, "payerSerialId" integer, "linkedApplicationSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "user_receiving_invoice"("serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId") SELECT "serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId" FROM "temporary_user_receiving_invoice"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_receiving_invoice"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a131e6b58f084f1340538681b5" ON "user_receiving_invoice" ("invoice") `);
|
||||
await queryRunner.query(`DROP INDEX "IDX_e04964210949f10bb25dc6e747"`);
|
||||
await queryRunner.query(`ALTER TABLE "application" RENAME TO "temporary_application"`);
|
||||
await queryRunner.query(`CREATE TABLE "application" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_id" varchar NOT NULL, "name" varchar NOT NULL, "allow_user_creation" boolean NOT NULL DEFAULT (0), "nostr_private_key" varchar, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "ownerSerialId" integer, CONSTRAINT "UQ_608bb41e7e1ef5f6d7abb07e394" UNIQUE ("name"), CONSTRAINT "UQ_f190e1a83a524035b84c1fe0696" UNIQUE ("nostr_private_key"), CONSTRAINT "UQ_87c12c4528183bf7a211221cc3c" UNIQUE ("nostr_public_key"))`);
|
||||
await queryRunner.query(`INSERT INTO "application"("serial_id", "app_id", "name", "allow_user_creation", "nostr_private_key", "nostr_public_key", "created_at", "updated_at", "ownerSerialId") SELECT "serial_id", "app_id", "name", "allow_user_creation", "nostr_private_key", "nostr_public_key", "created_at", "updated_at", "ownerSerialId" FROM "temporary_application"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_application"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e04964210949f10bb25dc6e747" ON "application" ("app_id") `);
|
||||
await queryRunner.query(`ALTER TABLE "product" RENAME TO "temporary_product"`);
|
||||
await queryRunner.query(`CREATE TABLE "product" ("product_id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "price_sats" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "ownerSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "product"("product_id", "name", "price_sats", "created_at", "updated_at", "ownerSerialId") SELECT "product_id", "name", "price_sats", "created_at", "updated_at", "ownerSerialId" FROM "temporary_product"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_product"`);
|
||||
await queryRunner.query(`DROP INDEX "user_transaction_unique"`);
|
||||
await queryRunner.query(`DROP TABLE "user_transaction_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "user_to_user_payment"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a609a4d3d8d9b07b90692a3c45"`);
|
||||
await queryRunner.query(`DROP TABLE "user_invoice_payment"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_b9628a8315cdc6afed037563d9"`);
|
||||
await queryRunner.query(`DROP TABLE "user_ephemeral_key"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_361b01c8e10453b2f979246535"`);
|
||||
await queryRunner.query(`DROP TABLE "user_basic_auth"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_0a0dbb25a73306b037dec82251"`);
|
||||
await queryRunner.query(`DROP TABLE "application_user"`);
|
||||
await queryRunner.query(`DROP INDEX "address_receiving_transaction_unique"`);
|
||||
await queryRunner.query(`DROP TABLE "address_receiving_transaction"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_ecf415a7a0b2fa64aa03c5b067"`);
|
||||
await queryRunner.query(`DROP TABLE "user_receiving_address"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a131e6b58f084f1340538681b5"`);
|
||||
await queryRunner.query(`DROP TABLE "user_receiving_invoice"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_e04964210949f10bb25dc6e747"`);
|
||||
await queryRunner.query(`DROP TABLE "application"`);
|
||||
await queryRunner.query(`DROP TABLE "product"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_758b8ce7c18b9d347461b30228"`);
|
||||
await queryRunner.query(`DROP TABLE "user"`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class LndMetrics1703170330183 implements MigrationInterface {
|
||||
name = 'LndMetrics1703170330183'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "balance_event" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "block_height" integer NOT NULL, "confirmed_chain_balance" integer NOT NULL, "unconfirmed_chain_balance" integer NOT NULL, "total_chain_balance" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`CREATE TABLE "channel_balance_event" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "channel_id" varchar NOT NULL, "local_balance_sats" integer NOT NULL, "remote_balance_sats" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "balanceEventSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE TABLE "routing_event" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "incoming_channel_id" integer NOT NULL, "incoming_htlc_id" integer NOT NULL, "outgoing_channel_id" integer NOT NULL, "outgoing_htlc_id" integer NOT NULL, "timestamp_ns" integer NOT NULL, "event_type" varchar NOT NULL, "incoming_amt_msat" integer, "outgoing_amt_msat" integer, "failure_string" varchar, "settled" boolean, "offchain" boolean, "forward_fail_event" boolean, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_channel_balance_event" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "channel_id" varchar NOT NULL, "local_balance_sats" integer NOT NULL, "remote_balance_sats" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "balanceEventSerialId" integer, CONSTRAINT "FK_e203090b07e770fe2e21a32e7c1" FOREIGN KEY ("balanceEventSerialId") REFERENCES "balance_event" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_channel_balance_event"("serial_id", "channel_id", "local_balance_sats", "remote_balance_sats", "created_at", "updated_at", "balanceEventSerialId") SELECT "serial_id", "channel_id", "local_balance_sats", "remote_balance_sats", "created_at", "updated_at", "balanceEventSerialId" FROM "channel_balance_event"`);
|
||||
await queryRunner.query(`DROP TABLE "channel_balance_event"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_channel_balance_event" RENAME TO "channel_balance_event"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "channel_balance_event" RENAME TO "temporary_channel_balance_event"`);
|
||||
await queryRunner.query(`CREATE TABLE "channel_balance_event" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "channel_id" varchar NOT NULL, "local_balance_sats" integer NOT NULL, "remote_balance_sats" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "balanceEventSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "channel_balance_event"("serial_id", "channel_id", "local_balance_sats", "remote_balance_sats", "created_at", "updated_at", "balanceEventSerialId") SELECT "serial_id", "channel_id", "local_balance_sats", "remote_balance_sats", "created_at", "updated_at", "balanceEventSerialId" FROM "temporary_channel_balance_event"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_channel_balance_event"`);
|
||||
await queryRunner.query(`DROP TABLE "routing_event"`);
|
||||
await queryRunner.query(`DROP TABLE "channel_balance_event"`);
|
||||
await queryRunner.query(`DROP TABLE "balance_event"`);
|
||||
}
|
||||
|
||||
}
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class LndMetrics1703170330183 implements MigrationInterface {
|
||||
name = 'LndMetrics1703170330183'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "balance_event" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "block_height" integer NOT NULL, "confirmed_chain_balance" integer NOT NULL, "unconfirmed_chain_balance" integer NOT NULL, "total_chain_balance" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`CREATE TABLE "channel_balance_event" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "channel_id" varchar NOT NULL, "local_balance_sats" integer NOT NULL, "remote_balance_sats" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "balanceEventSerialId" integer)`);
|
||||
await queryRunner.query(`CREATE TABLE "routing_event" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "incoming_channel_id" integer NOT NULL, "incoming_htlc_id" integer NOT NULL, "outgoing_channel_id" integer NOT NULL, "outgoing_htlc_id" integer NOT NULL, "timestamp_ns" integer NOT NULL, "event_type" varchar NOT NULL, "incoming_amt_msat" integer, "outgoing_amt_msat" integer, "failure_string" varchar, "settled" boolean, "offchain" boolean, "forward_fail_event" boolean, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_channel_balance_event" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "channel_id" varchar NOT NULL, "local_balance_sats" integer NOT NULL, "remote_balance_sats" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "balanceEventSerialId" integer, CONSTRAINT "FK_e203090b07e770fe2e21a32e7c1" FOREIGN KEY ("balanceEventSerialId") REFERENCES "balance_event" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_channel_balance_event"("serial_id", "channel_id", "local_balance_sats", "remote_balance_sats", "created_at", "updated_at", "balanceEventSerialId") SELECT "serial_id", "channel_id", "local_balance_sats", "remote_balance_sats", "created_at", "updated_at", "balanceEventSerialId" FROM "channel_balance_event"`);
|
||||
await queryRunner.query(`DROP TABLE "channel_balance_event"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_channel_balance_event" RENAME TO "channel_balance_event"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "channel_balance_event" RENAME TO "temporary_channel_balance_event"`);
|
||||
await queryRunner.query(`CREATE TABLE "channel_balance_event" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "channel_id" varchar NOT NULL, "local_balance_sats" integer NOT NULL, "remote_balance_sats" integer NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "balanceEventSerialId" integer)`);
|
||||
await queryRunner.query(`INSERT INTO "channel_balance_event"("serial_id", "channel_id", "local_balance_sats", "remote_balance_sats", "created_at", "updated_at", "balanceEventSerialId") SELECT "serial_id", "channel_id", "local_balance_sats", "remote_balance_sats", "created_at", "updated_at", "balanceEventSerialId" FROM "temporary_channel_balance_event"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_channel_balance_event"`);
|
||||
await queryRunner.query(`DROP TABLE "routing_event"`);
|
||||
await queryRunner.query(`DROP TABLE "channel_balance_event"`);
|
||||
await queryRunner.query(`DROP TABLE "balance_event"`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class ChannelRouting1709316653538 implements MigrationInterface {
|
||||
name = 'ChannelRouting1709316653538'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "channel_routing" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "day_unix" integer NOT NULL, "channel_id" varchar NOT NULL, "send_errors" integer NOT NULL DEFAULT (0), "receive_errors" integer NOT NULL DEFAULT (0), "forward_errors_as_input" integer NOT NULL DEFAULT (0), "forward_errors_as_output" integer NOT NULL DEFAULT (0), "missed_forward_fee_as_input" integer NOT NULL DEFAULT (0), "missed_forward_fee_as_output" integer NOT NULL DEFAULT (0), "forward_fee_as_input" integer NOT NULL DEFAULT (0), "forward_fee_as_output" integer NOT NULL DEFAULT (0), "latest_index_offset" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "channel_routing"`);
|
||||
}
|
||||
|
||||
}
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class ChannelRouting1709316653538 implements MigrationInterface {
|
||||
name = 'ChannelRouting1709316653538'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "channel_routing" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "day_unix" integer NOT NULL, "channel_id" varchar NOT NULL, "send_errors" integer NOT NULL DEFAULT (0), "receive_errors" integer NOT NULL DEFAULT (0), "forward_errors_as_input" integer NOT NULL DEFAULT (0), "forward_errors_as_output" integer NOT NULL DEFAULT (0), "missed_forward_fee_as_input" integer NOT NULL DEFAULT (0), "missed_forward_fee_as_output" integer NOT NULL DEFAULT (0), "forward_fee_as_input" integer NOT NULL DEFAULT (0), "forward_fee_as_output" integer NOT NULL DEFAULT (0), "latest_index_offset" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "channel_routing"`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue