Compare commits
No commits in common. "master" and "v1.0.0" have entirely different histories.
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
72
README.md
72
README.md
@ -1,16 +1,15 @@
|
||||
status: alpha (not ready for production)
|
||||
|
||||
# Hello, Lightning!
|
||||
|
||||
Cli lightning network server, based on LDK (rust-lightning).
|
||||
Provides HTTP-RPC interface.
|
||||
Provides DUMB-RPC (telnet friendly) and HTTP interface.
|
||||
|
||||
## Example:
|
||||
|
||||
* build it (or download binary from releases)
|
||||
* build it
|
||||
* run it: `java -jar ./out/artifacts/hello_main_jar/hello.main.jar`
|
||||
* now HTTP server listens on port 8310
|
||||
* run nodejs control process from `./cli/` directory (`npm i && npm start`)
|
||||
* now DUMB-RPC/HTTP server listens on port 8310
|
||||
* start the server with `start.sh` script
|
||||
* ...or run nodejs control process from `./cli/` directory (`npm i && npm start`)
|
||||
|
||||
## Philosophy
|
||||
|
||||
@ -18,24 +17,16 @@ Barebone Java-based server cant do much, out of the box it can only do lightning
|
||||
All the functionality should be implemented on upper level (like, GUI application, or nodejs cli script, etc), that
|
||||
includes: providing blockchain data, managing onchain coins to open channels (via PSBT), keeping a list of peers to keep connections etc
|
||||
|
||||
So currently repo has ldk-server (compiled from kotlin, considered a lower level), which is supposed to run and provide RPC, and a cli nodejs
|
||||
process which controls it (considered upper level). Cli process utilizes following apis:
|
||||
|
||||
* https://github.com/Blockstream/esplora/blob/master/API.md to fetch blockchain data
|
||||
* https://1ml.com/api to fetch ip addresses for other ln nodes pubkeys
|
||||
|
||||
Whole setup is thus quite lightweight.
|
||||
|
||||
## Security
|
||||
|
||||
Server is intended to run in a secure environment. Thus, on-disk storage is not encrypted, and RPC server
|
||||
handles connections without TLS (plain HTTP). Also, even though RPC listens on 127.0.0.1, it has no auth.
|
||||
handles connections without TLS (plain HTTP)
|
||||
|
||||
## TODO
|
||||
|
||||
* ~~port methods from https://github.com/BlueWallet/rn-ldk/blob/master/android/src/main/java/com/rnldk/RnLdkModule.kt while adding DUMB-RPC interface for them~~
|
||||
* create a GUI app (Electron?)
|
||||
* ~~create a cli control process~~
|
||||
* create a cli controll process
|
||||
* ...
|
||||
* Profit!
|
||||
|
||||
@ -43,26 +34,26 @@ handles connections without TLS (plain HTTP). Also, even though RPC listens on 1
|
||||
|
||||
* [x] start
|
||||
* [x] stop
|
||||
* [x] transactionconfirmed
|
||||
* [x] transactionunconfirmed
|
||||
* [x] getrelevanttxids
|
||||
* [x] updatebestblock
|
||||
* [x] connectpeer
|
||||
* [x] disconnectbynodeid
|
||||
* [x] sendpayment
|
||||
* [x] addinvoice
|
||||
* [x] listpeers
|
||||
* [x] getnodeid
|
||||
* [x] closechannelcooperatively
|
||||
* [x] closechannelforce
|
||||
* [x] openchannelstep1
|
||||
* [x] openchannelstep2
|
||||
* [x] listusablechannels
|
||||
* [x] listcChannels
|
||||
* [x] setrefundaddressscript
|
||||
* [x] setfeerate
|
||||
* [x] getmaturingbalance
|
||||
* [x] getmaturingheight
|
||||
* [x] transactionConfirmed
|
||||
* [x] transactionUnconfirmed
|
||||
* [x] getRelevantTxids
|
||||
* [x] updateBestBlock
|
||||
* [x] connectPeer
|
||||
* [x] disconnectByNodeId
|
||||
* [x] sendPayment
|
||||
* [x] addInvoice
|
||||
* [x] listPeers
|
||||
* [x] getNodeId
|
||||
* [x] closeChannelCooperatively
|
||||
* [x] closeChannelForce
|
||||
* [x] openChannelStep1
|
||||
* [x] openChannelStep2
|
||||
* [x] listUsableChannels
|
||||
* [x] listChannels
|
||||
* [x] setRefundAddressScript
|
||||
* [x] setFeerate
|
||||
* [x] getMaturingBalance
|
||||
* [x] getMaturingHeight
|
||||
* [x] savenetworkgraph
|
||||
* [x] geteventsfundinggenerationready
|
||||
* [x] geteventschannelclosed
|
||||
@ -76,15 +67,6 @@ handles connections without TLS (plain HTTP). Also, even though RPC listens on 1
|
||||
* [x] geteventspaymentreceived
|
||||
* [x] geteventspaymentforwarded
|
||||
|
||||
## Storage
|
||||
|
||||
Data is written in `~/.hellolightning` (non-configurable).
|
||||
There are files with states per each channel, and one for the channel manager.
|
||||
Upon the first launch of cli control script, given java process is running, HelloLightning will be seeded
|
||||
with a secure entropy, which is then stored in `seed.txt`.
|
||||
All events that must be passed from lower level (lightning) to upper level (control script) are served through
|
||||
their respective RPCs, and also stored as json files.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
175
cli/package-lock.json
generated
175
cli/package-lock.json
generated
@ -1278,59 +1278,12 @@
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"base-x": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
|
||||
"integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"bech32": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
|
||||
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bip174": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz",
|
||||
"integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ=="
|
||||
},
|
||||
"bip32": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz",
|
||||
"integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==",
|
||||
"requires": {
|
||||
"@types/node": "10.12.18",
|
||||
"bs58check": "^2.1.1",
|
||||
"create-hash": "^1.2.0",
|
||||
"create-hmac": "^1.1.7",
|
||||
"tiny-secp256k1": "^1.1.3",
|
||||
"typeforce": "^1.11.5",
|
||||
"wif": "^2.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "10.12.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
|
||||
"integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"bip39": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz",
|
||||
@ -1349,25 +1302,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"bitcoinjs-lib": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.0.0.tgz",
|
||||
"integrity": "sha512-KYx81rVE4LDbZcHfE375NCX4CDeZuz7HECZm/KAmqKMY2jpD3ZcUnI7Fm+QX5EMF/xmtzzfrNL/BNxo8o0iOQg==",
|
||||
"requires": {
|
||||
"bech32": "^2.0.0",
|
||||
"bip174": "^2.0.1",
|
||||
"bs58check": "^2.1.2",
|
||||
"create-hash": "^1.1.0",
|
||||
"typeforce": "^1.11.3",
|
||||
"varuint-bitcoin": "^1.1.2",
|
||||
"wif": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
},
|
||||
"boxen": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
|
||||
@ -1411,11 +1345,6 @@
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"brorand": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
|
||||
},
|
||||
"browser-process-hrtime": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
|
||||
@ -1444,24 +1373,6 @@
|
||||
"fast-json-stable-stringify": "2.x"
|
||||
}
|
||||
},
|
||||
"bs58": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
|
||||
"integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=",
|
||||
"requires": {
|
||||
"base-x": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"bs58check": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz",
|
||||
"integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==",
|
||||
"requires": {
|
||||
"bs58": "^4.0.0",
|
||||
"create-hash": "^1.1.0",
|
||||
"safe-buffer": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"bser": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
|
||||
@ -1925,20 +1836,6 @@
|
||||
"integrity": "sha512-P/nDMPIYdb2PyqCQwhTXNi5JFjX1AsDVR0y6FrHw752izJIAJ+Pn5lugqyBq4tXeRSZBMBb2ZGvRGB1djtELEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||
"requires": {
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
"hash.js": "^1.0.0",
|
||||
"hmac-drbg": "^1.0.1",
|
||||
"inherits": "^2.0.4",
|
||||
"minimalistic-assert": "^1.0.1",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"emittery": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz",
|
||||
@ -2363,11 +2260,6 @@
|
||||
"flat-cache": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
@ -2592,25 +2484,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hash.js": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
||||
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"hmac-drbg": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
|
||||
"requires": {
|
||||
"hash.js": "^1.0.3",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"html-encoding-sniffer": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
|
||||
@ -3657,16 +3530,6 @@
|
||||
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
|
||||
"dev": true
|
||||
},
|
||||
"minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||
},
|
||||
"minimalistic-crypto-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
@ -3688,11 +3551,6 @@
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.15.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
|
||||
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ=="
|
||||
},
|
||||
"natural-compare": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
@ -4486,18 +4344,6 @@
|
||||
"integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==",
|
||||
"dev": true
|
||||
},
|
||||
"tiny-secp256k1": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz",
|
||||
"integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==",
|
||||
"requires": {
|
||||
"bindings": "^1.3.0",
|
||||
"bn.js": "^4.11.8",
|
||||
"create-hmac": "^1.1.7",
|
||||
"elliptic": "^6.4.0",
|
||||
"nan": "^2.13.2"
|
||||
}
|
||||
},
|
||||
"tmpl": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||
@ -4650,11 +4496,6 @@
|
||||
"is-typedarray": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"typeforce": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz",
|
||||
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
|
||||
@ -4752,14 +4593,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"varuint-bitcoin": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz",
|
||||
"integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"w3c-hr-time": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
|
||||
@ -4837,14 +4670,6 @@
|
||||
"string-width": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"wif": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz",
|
||||
"integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=",
|
||||
"requires": {
|
||||
"bs58check": "<3.0.0"
|
||||
}
|
||||
},
|
||||
"word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
|
||||
@ -37,9 +37,7 @@
|
||||
"author": "Jakub Synowiec <jsynowiec@users.noreply.github.com>",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"bip32": "2.0.6",
|
||||
"bip39": "^3.0.4",
|
||||
"bitcoinjs-lib": "^6.0.0",
|
||||
"cli-table": "^0.3.6",
|
||||
"cross-fetch": "^3.1.4",
|
||||
"tslib": "~2.3.1"
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
/* eslint-disable no-empty,@typescript-eslint/no-inferrable-types,@typescript-eslint/no-var-requires */
|
||||
import fetch from 'cross-fetch';
|
||||
import * as bip39 from 'bip39';
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const HDNode = require('bip32');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
|
||||
@ -13,8 +11,7 @@ export default class Ldk {
|
||||
private _nodeConnectionDetailsCache: any = {};
|
||||
|
||||
logToGeneralLog(...args: any[]) {
|
||||
let str = new Date().toUTCString();
|
||||
args.map(arg => str += ' ' + JSON.stringify(arg));
|
||||
const str = JSON.stringify(args);
|
||||
this.logs.push(str)
|
||||
}
|
||||
|
||||
@ -57,7 +54,7 @@ export default class Ldk {
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
async updateFeerate() {
|
||||
private async updateFeerate() {
|
||||
this.logToGeneralLog('updating feerate');
|
||||
try {
|
||||
const response = await fetch('https://blockstream.info/api/fee-estimates');
|
||||
@ -70,9 +67,8 @@ export default class Ldk {
|
||||
if (json[blockFast] && json[blockMedium] && json[blockSlow]) {
|
||||
const feerateFast = Math.round(json[blockFast]);
|
||||
const feerateMedium = Math.round(json[blockMedium]);
|
||||
// const feerateSlow = Math.round(json[blockSlow]);
|
||||
const feerateSlow = 1; // less secure but should help us avoid force-closures
|
||||
await this.setFeerate(Math.max(feerateFast, 2), Math.max(feerateMedium, 1), Math.max(feerateSlow, 1));
|
||||
const feerateSlow = Math.round(json[blockSlow]);
|
||||
await this.setFeerate(Math.max(feerateFast, 2), Math.max(feerateMedium, 2), Math.max(feerateSlow, 2));
|
||||
} else {
|
||||
throw new Error('Invalid feerate data:' + JSON.stringify(json));
|
||||
}
|
||||
@ -91,9 +87,9 @@ export default class Ldk {
|
||||
*/
|
||||
private async setFeerate(newFeerateFast: number, newFeerateMedium: number, newFeerateSlow: number): Promise<boolean> {
|
||||
this.logToGeneralLog('setting feerate', { newFeerateFast, newFeerateMedium, newFeerateSlow });
|
||||
const fast = Math.max(newFeerateFast * 250, 253);
|
||||
const medium = Math.max(newFeerateMedium * 250, 253);
|
||||
const slow = Math.max(newFeerateSlow * 250, 253);
|
||||
const fast = newFeerateFast * 250;
|
||||
const medium = newFeerateMedium * 250;
|
||||
const slow = newFeerateSlow * 250;
|
||||
const response = await fetch(`http://127.0.0.1:8310/setfeerate/${fast}/${medium}/${slow}`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
@ -234,24 +230,6 @@ export default class Ldk {
|
||||
return this._processResult(text)
|
||||
}
|
||||
|
||||
async broadcastTxsIfNecessary() {
|
||||
const txs = await this.getTxsBroadcast();
|
||||
for (const tx of txs) {
|
||||
this.logToGeneralLog('should broadcast', tx)
|
||||
const response = await fetch('https://blockstream.info/api/tx', {
|
||||
method: 'POST',
|
||||
body: tx.txhex
|
||||
});
|
||||
this.logToGeneralLog('broadcast result: ' + await response.text());
|
||||
}
|
||||
}
|
||||
|
||||
async getTxsBroadcast() {
|
||||
const response = await fetch(`http://127.0.0.1:8310/geteventstxbroadcast`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text)
|
||||
}
|
||||
|
||||
private async getRegisteredTxs() {
|
||||
const response = await fetch(`http://127.0.0.1:8310/geteventsregistertx`);
|
||||
const text = await response.text();
|
||||
@ -339,31 +317,8 @@ export default class Ldk {
|
||||
return this._processResult(text)
|
||||
}
|
||||
|
||||
unwrapFirstExternalAddressFromMnemonics() {
|
||||
if (!this.getSecret()) throw new Error('no secret');
|
||||
const mnemonic = this.getSecret();
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
const root = HDNode.fromSeed(seed);
|
||||
const path = "m/84'/0'/0'/0/0";
|
||||
const child = root.derivePath(path);
|
||||
|
||||
return bitcoin.payments.p2wpkh({
|
||||
pubkey: child.publicKey,
|
||||
}).address;
|
||||
}
|
||||
|
||||
unwrapFirstExternalWifFromMnemonics() {
|
||||
if (!this.getSecret()) throw new Error('no secret');
|
||||
const mnemonic = this.getSecret();
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
const root = HDNode.fromSeed(seed);
|
||||
const path = "m/84'/0'/0'/0/0";
|
||||
const child = root.derivePath(path);
|
||||
|
||||
return child.toWIF();
|
||||
}
|
||||
|
||||
async reconnectPeers(homedir: string) {
|
||||
this.logToGeneralLog('attempting to reconnect peers if needed...');
|
||||
const peers2reconnect = {};
|
||||
|
||||
const listPeers = await this.listPeers();
|
||||
@ -406,30 +361,6 @@ export default class Ldk {
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async version() {
|
||||
const response = await fetch(`http://127.0.0.1:8310/version`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async ldkversion() {
|
||||
const response = await fetch(`http://127.0.0.1:8310/ldkversion`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async getMaturingBalance() {
|
||||
const response = await fetch(`http://127.0.0.1:8310/getmaturingbalance`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async getMaturingHeight() {
|
||||
const response = await fetch(`http://127.0.0.1:8310/getmaturingheight`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
async lookupNodeConnectionDetailsByPubkey(pubkey: string) {
|
||||
// first, trying cache:
|
||||
if (this._nodeConnectionDetailsCache[pubkey] && +new Date() - this._nodeConnectionDetailsCache[pubkey].ts < 4 * 7 * 24 * 3600 * 1000) {
|
||||
@ -456,13 +387,4 @@ export default class Ldk {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async setRefundAddress(address: string) {
|
||||
const script = bitcoin.address.toOutputScript(address);
|
||||
const refundAddressScriptHex = script.toString('hex');
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:8310/setrefundaddressscript/${refundAddressScriptHex}`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,6 @@ const fs = require('fs');
|
||||
let lastBlockchainSync = 0;
|
||||
let lastNetworkGraphSaved = 0;
|
||||
let lastPeersReconnect = 0;
|
||||
let maturingBalance = 0;
|
||||
let maturingHeight = 0;
|
||||
const homedir = require('os').homedir() + '/.hellolightning';
|
||||
const seedfile = `${homedir}/seed.txt`;
|
||||
const ldk = new Ldk();
|
||||
@ -15,7 +13,6 @@ const ldk = new Ldk();
|
||||
async function tick() {
|
||||
console.clear();
|
||||
console.log('Hello, Lightning!');
|
||||
|
||||
console.log(`using ${homedir}`);
|
||||
|
||||
if (!fs.existsSync(homedir)) {
|
||||
@ -31,14 +28,6 @@ async function tick() {
|
||||
await new Promise(resolve => setTimeout(resolve, 5000)); // sleep
|
||||
}
|
||||
|
||||
if (!ldk.getSecret()) {
|
||||
const seedFromDisk = fs.readFileSync(seedfile, { encoding: 'utf8' })
|
||||
ldk.setSecret(seedFromDisk);
|
||||
}
|
||||
|
||||
console.log('seed:', ldk.getSecret());
|
||||
console.log('refund address:', ldk.unwrapFirstExternalAddressFromMnemonics(), 'refund address WIF:', ldk.unwrapFirstExternalWifFromMnemonics());
|
||||
|
||||
let started = true;
|
||||
let nodeid: string;
|
||||
try {
|
||||
@ -52,10 +41,11 @@ async function tick() {
|
||||
if (!started) {
|
||||
console.log('attempting to start a node...');
|
||||
try {
|
||||
await ldk.setRefundAddress(ldk.unwrapFirstExternalAddressFromMnemonics());
|
||||
await ldk.updateFeerate(); // so any refund claim upon startup would use adequate fee
|
||||
await ldk.start(ldk.getEntropyHex());
|
||||
// await ldk.start("00000000000000000000000000000000000000000000000000000000000000f6"); // fixme
|
||||
const seedFromDisk = fs.readFileSync(seedfile, { encoding: 'utf8' })
|
||||
ldk.setSecret(seedFromDisk);
|
||||
// const hex = ldk.getEntropyHex();
|
||||
// await ldk.start(hex);
|
||||
await ldk.start("00000000000000000000000000000000000000000000000000000000000000f6"); // fixme
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
await new Promise(resolve => setTimeout(resolve, 10* 1000)); // sleep
|
||||
@ -64,15 +54,10 @@ async function tick() {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('version:', await ldk.version(), "(ldk binaries version: " + await ldk.ldkversion() + ")");
|
||||
|
||||
if (+new Date() - lastBlockchainSync > 5 * 60 * 1000) { // 5 min
|
||||
lastBlockchainSync = +new Date();
|
||||
await ldk.setRefundAddress(ldk.unwrapFirstExternalAddressFromMnemonics());
|
||||
maturingBalance = await ldk.getMaturingBalance();
|
||||
maturingHeight = await ldk.getMaturingHeight();
|
||||
ldk.checkBlockchain(); // let it run in the background
|
||||
await ldk.broadcastTxsIfNecessary();
|
||||
}
|
||||
|
||||
if (+new Date() - lastNetworkGraphSaved > 1 * 60 * 1000) {
|
||||
@ -80,14 +65,14 @@ async function tick() {
|
||||
ldk.saveNetworkGraph(); // let it run in the background
|
||||
}
|
||||
|
||||
if (+new Date() - lastPeersReconnect > 0.5 * 60 * 1000) {
|
||||
if (+new Date() - lastPeersReconnect > 1 * 60 * 1000) {
|
||||
lastPeersReconnect = +new Date();
|
||||
ldk.reconnectPeers(homedir); // let it run in the background
|
||||
}
|
||||
|
||||
const peers = await ldk.listPeers();
|
||||
const channels = await ldk.listChannels();
|
||||
const activeChannels = await ldk.listUsableChannels();
|
||||
const activeAhannels = await ldk.listUsableChannels();
|
||||
|
||||
let outbound_capacity_msat = 0;
|
||||
let inbound_capacity_msat = 0;
|
||||
@ -99,16 +84,12 @@ async function tick() {
|
||||
|
||||
const table = new Table()
|
||||
table.push(
|
||||
['num peers', 'last sync', 'num channels\n(active/total)', 'channel balance', 'inbound capacity', 'node id']
|
||||
, [peers.length, Math.floor((+new Date() - lastBlockchainSync)/1000) + ' sec ago', activeChannels.length + ' / ' + channels.length, msatToBitcoinString(outbound_capacity_msat), msatToBitcoinString(inbound_capacity_msat), nodeid]
|
||||
['num peers', 'last sync', 'num channels', 'num active channels', 'channel balance', 'inbound capacity', 'node id']
|
||||
, [peers.length, Math.floor((+new Date() - lastBlockchainSync)/1000) + ' sec ago', channels.length, activeAhannels.length, msatToBitcoinString(outbound_capacity_msat), msatToBitcoinString(inbound_capacity_msat), nodeid]
|
||||
)
|
||||
|
||||
if (maturingBalance > 0) {
|
||||
console.log('maturing balance:', maturingBalance, "sat (awaiting height " + maturingHeight + ")");
|
||||
}
|
||||
|
||||
console.log(table.toString())
|
||||
console.log(ldk.getLastLogsLines(20).join("\n"));
|
||||
console.log(ldk.getLastLogsLines(30).join("\n"));
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
curl http://127.0.0.1:8310/openchannelstep1/03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f/500000/1
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
curl http://127.0.0.1:8310/version
|
||||
|
||||
31
gui/.gitignore
vendored
31
gui/.gitignore
vendored
@ -1,31 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.idea
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width"/><meta charSet="utf-8"/><title>404: This page could not be found</title><meta name="next-head-count" content="3"/><link rel="preload" href="/_next/static/css/f2259aca1f14a10459d9.css" as="style"/><link rel="stylesheet" href="/_next/static/css/f2259aca1f14a10459d9.css" data-n-g=""/><noscript data-n-css=""></noscript><link rel="preload" href="/_next/static/chunks/webpack-189c53927ffd3caf09c3.js" as="script"/><link rel="preload" href="/_next/static/chunks/framework-4ae45ca6d0f28c4504d3.js" as="script"/><link rel="preload" href="/_next/static/chunks/main-899697ea82bdc85e7a94.js" as="script"/><link rel="preload" href="/_next/static/chunks/pages/_app-4be02ffccdd9fceacfca.js" as="script"/><link rel="preload" href="/_next/static/chunks/pages/_error-b30902e13465df7c5366.js" as="script"/></head><body><div id="__next"><div style="color:#000;background:#fff;font-family:-apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", "Fira Sans", Avenir, "Helvetica Neue", "Lucida Grande", sans-serif;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body { margin: 0 }</style><h1 style="display:inline-block;border-right:1px solid rgba(0, 0, 0,.3);margin:0;margin-right:20px;padding:10px 23px 10px 0;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block;text-align:left;line-height:49px;height:49px;vertical-align:middle"><h2 style="font-size:14px;font-weight:normal;line-height:inherit;margin:0;padding:0">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"T-qGn4h1yqnREwY2tqUAk","nextExport":true,"isFallback":false,"gip":true}</script><script nomodule="" src="/_next/static/chunks/polyfills-eef578260fd80f8fff94.js"></script><script src="/_next/static/chunks/webpack-189c53927ffd3caf09c3.js" async=""></script><script src="/_next/static/chunks/framework-4ae45ca6d0f28c4504d3.js" async=""></script><script src="/_next/static/chunks/main-899697ea82bdc85e7a94.js" async=""></script><script src="/_next/static/chunks/pages/_app-4be02ffccdd9fceacfca.js" async=""></script><script src="/_next/static/chunks/pages/_error-b30902e13465df7c5366.js" async=""></script><script src="/_next/static/T-qGn4h1yqnREwY2tqUAk/_buildManifest.js" async=""></script><script src="/_next/static/T-qGn4h1yqnREwY2tqUAk/_ssgManifest.js" async=""></script></body></html>
|
||||
@ -1,472 +0,0 @@
|
||||
/* eslint-disable no-empty,@typescript-eslint/no-inferrable-types,@typescript-eslint/no-var-requires */
|
||||
import fetch from 'cross-fetch';
|
||||
import * as bip39 from 'bip39';
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const HDNode = require('bip32');
|
||||
const crypto = require('crypto');
|
||||
|
||||
export default class Ldk {
|
||||
private injectedScript2address: ((scriptHex: string) => Promise<string>) | null = null;
|
||||
private logs: string[] = [];
|
||||
private secret: string = '';
|
||||
private _nodeConnectionDetailsCache: any = {};
|
||||
|
||||
logToGeneralLog(...args: any[]) {
|
||||
let str = new Date().toUTCString();
|
||||
args.map(arg => str += ' ' + JSON.stringify(arg));
|
||||
this.logs.push(str);
|
||||
}
|
||||
|
||||
getLastLogsLines(num: number) {
|
||||
return this.logs.slice(num * -1);
|
||||
}
|
||||
|
||||
private async getHeaderHexByHeight(height: number) {
|
||||
const response2 = await fetch('https://blockstream.info/api/block-height/' + height);
|
||||
const hash = await response2.text();
|
||||
const response3 = await fetch('https://blockstream.info/api/block/' + hash + '/header');
|
||||
return response3.text();
|
||||
}
|
||||
|
||||
private async script2address(scriptHex: string): Promise<string> {
|
||||
if (this.injectedScript2address) {
|
||||
return await this.injectedScript2address(scriptHex);
|
||||
}
|
||||
|
||||
const response = await fetch('https://runkit.io/overtorment/output-script-to-address/branches/master/' + scriptHex);
|
||||
return response.text();
|
||||
}
|
||||
|
||||
private async getCurrentHeight() {
|
||||
const response = await fetch('https://blockstream.info/api/blocks/tip/height');
|
||||
return parseInt(await response.text(), 10);
|
||||
}
|
||||
|
||||
private async updateBestBlock() {
|
||||
this.logToGeneralLog('updating best block');
|
||||
const height = await this.getCurrentHeight();
|
||||
const response2 = await fetch('https://blockstream.info/api/block-height/' + height);
|
||||
const hash = await response2.text();
|
||||
const response3 = await fetch('https://blockstream.info/api/block/' + hash + '/header');
|
||||
const headerHex = await response3.text();
|
||||
this.logToGeneralLog('updateBestBlock():', { headerHex, height });
|
||||
const response = await fetch(`http://127.0.0.1:8310/updatebestblock/${headerHex}/${height}`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
async updateFeerate() {
|
||||
this.logToGeneralLog('updating feerate');
|
||||
try {
|
||||
const response = await fetch('https://blockstream.info/api/fee-estimates');
|
||||
const json = await response.json();
|
||||
|
||||
const blockFast = '2'; // indexes in json object
|
||||
const blockMedium = '6';
|
||||
const blockSlow = '144';
|
||||
|
||||
if (json[blockFast] && json[blockMedium] && json[blockSlow]) {
|
||||
const feerateFast = Math.round(json[blockFast]);
|
||||
const feerateMedium = Math.round(json[blockMedium]);
|
||||
// const feerateSlow = Math.round(json[blockSlow]);
|
||||
const feerateSlow = 1; // less secure but should help us avoid force-closures
|
||||
await this.setFeerate(Math.max(feerateFast, 2), Math.max(feerateMedium, 1), Math.max(feerateSlow, 1));
|
||||
} else {
|
||||
throw new Error('Invalid feerate data:' + JSON.stringify(json));
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('updateFeerate() failed:', error);
|
||||
this.logToGeneralLog('updateFeerate() failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prodives LKD current feerate to use with all onchain transactions (like sweeps after forse-closures)
|
||||
*
|
||||
* @param newFeerateFast {number} Sat/b
|
||||
* @param newFeerateMedium {number} Sat/b
|
||||
* @param newFeerateSlow {number} Sat/b
|
||||
*/
|
||||
private async setFeerate(newFeerateFast: number, newFeerateMedium: number, newFeerateSlow: number): Promise<boolean> {
|
||||
this.logToGeneralLog('setting feerate', { newFeerateFast, newFeerateMedium, newFeerateSlow });
|
||||
const fast = Math.max(newFeerateFast * 250, 253);
|
||||
const medium = Math.max(newFeerateMedium * 250, 253);
|
||||
const slow = Math.max(newFeerateSlow * 250, 253);
|
||||
const response = await fetch(`http://127.0.0.1:8310/setfeerate/${fast}/${medium}/${slow}`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches from network registered outputs, registered transactions and block tip
|
||||
* and feeds this into to native code, if necessary.
|
||||
* Should be called periodically.
|
||||
*/
|
||||
async checkBlockchain(progressCallback?: (progress: number) => void) {
|
||||
this.logToGeneralLog('checkBlockchain() 1/x');
|
||||
if (progressCallback) progressCallback(1 / 8);
|
||||
await this.updateBestBlock();
|
||||
|
||||
this.logToGeneralLog('checkBlockchain() 2/x');
|
||||
if (progressCallback) progressCallback(2 / 8);
|
||||
await this.updateFeerate();
|
||||
|
||||
const confirmedBlocks: any = {};
|
||||
|
||||
// iterating all subscriptions for confirmed txid
|
||||
this.logToGeneralLog('checkBlockchain() 3/x');
|
||||
if (progressCallback) progressCallback(3 / 8);
|
||||
for (const regTx of await this.getRegisteredTxs()) {
|
||||
let json;
|
||||
try {
|
||||
const response = await fetch('https://blockstream.info/api/tx/' + regTx.txid);
|
||||
json = await response.json();
|
||||
} catch (_) {}
|
||||
if (json && json.status && json.status.confirmed && json.status.block_height) {
|
||||
// success! tx confirmed, and we need to notify LDK about it
|
||||
|
||||
let jsonPos;
|
||||
try {
|
||||
const responsePos = await fetch('https://blockstream.info/api/tx/' + regTx.txid + '/merkle-proof');
|
||||
jsonPos = await responsePos.json();
|
||||
} catch (_) {}
|
||||
|
||||
if (jsonPos && jsonPos.merkle) {
|
||||
confirmedBlocks[json.status.block_height + ''] = confirmedBlocks[json.status.block_height + ''] || {};
|
||||
const responseHex = await fetch('https://blockstream.info/api/tx/' + regTx.txid + '/hex');
|
||||
confirmedBlocks[json.status.block_height + ''][jsonPos.pos + ''] = await responseHex.text();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// iterating all scripts for spends
|
||||
this.logToGeneralLog('checkBlockchain() 4/x');
|
||||
if (progressCallback) progressCallback(4 / 8);
|
||||
for (const regOut of await this.getRegisteredOutputs()) {
|
||||
let txs: any[] = [];
|
||||
try {
|
||||
const address = await this.script2address(regOut.script_pubkey);
|
||||
const response = await fetch('https://blockstream.info/api/address/' + address + '/txs');
|
||||
txs = await response.json();
|
||||
} catch (_) {}
|
||||
for (const tx of txs) {
|
||||
if (tx && tx.status && tx.status.confirmed && tx.status.block_height) {
|
||||
// got confirmed tx for that output!
|
||||
|
||||
let jsonPos;
|
||||
try {
|
||||
const responsePos = await fetch('https://blockstream.info/api/tx/' + tx.txid + '/merkle-proof');
|
||||
jsonPos = await responsePos.json();
|
||||
} catch (_) {}
|
||||
|
||||
if (jsonPos && jsonPos.merkle) {
|
||||
const responseHex = await fetch('https://blockstream.info/api/tx/' + tx.txid + '/hex');
|
||||
confirmedBlocks[tx.status.block_height + ''] = confirmedBlocks[tx.status.block_height + ''] || {};
|
||||
confirmedBlocks[tx.status.block_height + ''][jsonPos.pos + ''] = await responseHex.text();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now, got all data packed in `confirmedBlocks[block_number][tx_position]`
|
||||
// lets feed it to LDK:
|
||||
|
||||
this.logToGeneralLog('confirmedBlocks=', confirmedBlocks);
|
||||
|
||||
this.logToGeneralLog('checkBlockchain() 5/x');
|
||||
if (progressCallback) progressCallback(5 / 8);
|
||||
for (const height of Object.keys(confirmedBlocks).sort((a, b) => parseInt(a, 10) - parseInt(b, 10))) {
|
||||
for (const pos of Object.keys(confirmedBlocks[height]).sort((a, b) => parseInt(a, 10) - parseInt(b, 10))) {
|
||||
await this.transactionConfirmed(await this.getHeaderHexByHeight(parseInt(height, 10)), parseInt(height, 10), parseInt(pos, 10), confirmedBlocks[height][pos]);
|
||||
}
|
||||
}
|
||||
|
||||
this.logToGeneralLog('checkBlockchain() 6/x');
|
||||
if (progressCallback) progressCallback(6 / 8);
|
||||
let txidArr = [];
|
||||
try {
|
||||
txidArr = await this.getRelevantTxids();
|
||||
this.logToGeneralLog('getRelevantTxids:', txidArr);
|
||||
} catch (error: any) {
|
||||
this.logToGeneralLog('getRelevantTxids:', error.message);
|
||||
console.warn('getRelevantTxids:', error.message);
|
||||
}
|
||||
|
||||
// we need to check if any of txidArr got unconfirmed, and then feed it back to LDK if they are unconf
|
||||
this.logToGeneralLog('checkBlockchain() 7/x');
|
||||
if (progressCallback) progressCallback(7 / 8);
|
||||
for (const txid of txidArr) {
|
||||
let confirmed = false;
|
||||
try {
|
||||
const response = await fetch('https://blockstream.info/api/tx/' + txid + '/merkle-proof');
|
||||
const tx: any = await response.json();
|
||||
if (tx && tx.block_height) confirmed = true;
|
||||
} catch (_) {
|
||||
confirmed = false;
|
||||
}
|
||||
|
||||
if (!confirmed) await this.transactionUnconfirmed(txid);
|
||||
}
|
||||
|
||||
this.logToGeneralLog('checkBlockchain() done');
|
||||
if (progressCallback) progressCallback(8 / 8);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async getRelevantTxids() {
|
||||
const response = await fetch('http://127.0.0.1:8310/getrelevanttxids');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
private async transactionConfirmed(headerHex: string, height: number, pos: number, transactionHex: string) {
|
||||
const response = await fetch(`http://127.0.0.1:8310/transactionconfirmed/${headerHex}/${height}/${pos}/${transactionHex}`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
private async transactionUnconfirmed(txid: string) {
|
||||
const response = await fetch(`http://127.0.0.1:8310/transactionunconfirmed/${txid}`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
async broadcastTxsIfNecessary() {
|
||||
const txs = await this.getTxsBroadcast();
|
||||
for (const tx of txs) {
|
||||
this.logToGeneralLog('should broadcast', tx);
|
||||
const response = await fetch('https://blockstream.info/api/tx', {
|
||||
method: 'POST',
|
||||
body: tx.txhex,
|
||||
});
|
||||
this.logToGeneralLog('broadcast result: ' + await response.text());
|
||||
}
|
||||
}
|
||||
|
||||
async getTxsBroadcast() {
|
||||
const response = await fetch('http://127.0.0.1:8310/geteventstxbroadcast');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
private async getRegisteredTxs() {
|
||||
const response = await fetch('http://127.0.0.1:8310/geteventsregistertx');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
private async getRegisteredOutputs() {
|
||||
const response = await fetch('http://127.0.0.1:8310/geteventsregisteroutput');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async listPeers() {
|
||||
const response = await fetch('http://127.0.0.1:8310/listpeers');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async listChannels() {
|
||||
const response = await fetch('http://127.0.0.1:8310/listchannels');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async listUsableChannels() {
|
||||
const response = await fetch('http://127.0.0.1:8310/listusablechannels');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async getNodeId() {
|
||||
const response = await fetch('http://127.0.0.1:8310/getnodeid');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async start(entropy) {
|
||||
const tip = await this.getCurrentHeight();
|
||||
const response2 = await fetch('https://blockstream.info/api/block-height/' + tip);
|
||||
const hash = await response2.text();
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:8310/start/${entropy}/${tip}/${hash}`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
private _processResult(text: string) {
|
||||
const json = JSON.parse(text);
|
||||
if (json.error) throw new Error(json.result);
|
||||
return json.result;
|
||||
}
|
||||
|
||||
async generate() {
|
||||
const buf = await this.randomBytes(16);
|
||||
this.secret = '' + bip39.entropyToMnemonic(buf.toString('hex'));
|
||||
}
|
||||
|
||||
getEntropyHex() {
|
||||
let ret = bip39.mnemonicToEntropy(this.secret.replace('', ''));
|
||||
while (ret.length < 64) ret = '0' + ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
getSecret(): string {
|
||||
return this.secret;
|
||||
}
|
||||
|
||||
setSecret(secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
async randomBytes(size): Promise<Buffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
crypto.randomBytes(size, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async saveNetworkGraph() {
|
||||
this.logToGeneralLog('saving network graph to disk...');
|
||||
const response = await fetch('http://127.0.0.1:8310/savenetworkgraph');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
unwrapFirstExternalAddressFromMnemonics() {
|
||||
if (!this.getSecret()) throw new Error('no secret');
|
||||
const mnemonic = this.getSecret();
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
const root = HDNode.fromSeed(seed);
|
||||
const path = "m/84'/0'/0'/0/0";
|
||||
const child = root.derivePath(path);
|
||||
|
||||
return bitcoin.payments.p2wpkh({
|
||||
pubkey: child.publicKey,
|
||||
}).address;
|
||||
}
|
||||
|
||||
unwrapFirstExternalWifFromMnemonics() {
|
||||
if (!this.getSecret()) throw new Error('no secret');
|
||||
const mnemonic = this.getSecret();
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
const root = HDNode.fromSeed(seed);
|
||||
const path = "m/84'/0'/0'/0/0";
|
||||
const child = root.derivePath(path);
|
||||
|
||||
return child.toWIF();
|
||||
}
|
||||
|
||||
async reconnectPeers() {
|
||||
const peers2reconnect = {};
|
||||
|
||||
let listPeers, listChannels;
|
||||
|
||||
try {
|
||||
listPeers = await this.listPeers();
|
||||
listChannels = await this.listChannels();
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// do we have any channels that need reconnection with peers..?
|
||||
for (const channel of listChannels) {
|
||||
if (!listPeers.includes(channel.counterparty_node_id)) peers2reconnect[channel.counterparty_node_id] = channel.counterparty_node_id;
|
||||
}
|
||||
|
||||
// do we have any peers stored in file that must be connected..?
|
||||
let storedPeers = [];
|
||||
try {
|
||||
const storedPeersTxt = localStorage.getItem(`peers.json`)
|
||||
if (storedPeersTxt) storedPeers = JSON.parse(storedPeersTxt);
|
||||
} catch (_) {}
|
||||
for (const storedPeer of storedPeers) {
|
||||
if (!listPeers.includes(storedPeer)) peers2reconnect[storedPeer] = storedPeer;
|
||||
}
|
||||
|
||||
const peers2save = {};
|
||||
// dumb dedup:
|
||||
for (const peer of storedPeers.concat(listPeers).concat(Object.keys(peers2reconnect))) {
|
||||
peers2save[peer] = peer;
|
||||
}
|
||||
localStorage.setItem(`peers.json`, JSON.stringify(Object.keys(peers2save)));
|
||||
|
||||
// finally. conencting to the ones that need connection:
|
||||
for (const peer of Object.keys(peers2reconnect)) {
|
||||
this.logToGeneralLog(`connecting to ${peer}`);
|
||||
const details = await this.lookupNodeConnectionDetailsByPubkey(peer);
|
||||
this.logToGeneralLog(`(${details.pubkey}@${details.host}:${details.port})`);
|
||||
await this.connectPeer(details.pubkey, details.host , details.port);
|
||||
}
|
||||
}
|
||||
|
||||
public async connectPeer(pubkey, host, port) {
|
||||
const response = await fetch(`http://127.0.0.1:8310/connectpeer/${pubkey}/${host}/${port}`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async version() {
|
||||
const response = await fetch('http://127.0.0.1:8310/version');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async ldkversion() {
|
||||
const response = await fetch('http://127.0.0.1:8310/ldkversion');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async getMaturingBalance() {
|
||||
const response = await fetch('http://127.0.0.1:8310/getmaturingbalance');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
public async getMaturingHeight() {
|
||||
const response = await fetch('http://127.0.0.1:8310/getmaturingheight');
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
|
||||
async lookupNodeConnectionDetailsByPubkey(pubkey: string) {
|
||||
// first, trying cache:
|
||||
if (this._nodeConnectionDetailsCache[pubkey] && +new Date() - this._nodeConnectionDetailsCache[pubkey].ts < 4 * 7 * 24 * 3600 * 1000) {
|
||||
// cache hit
|
||||
return this._nodeConnectionDetailsCache[pubkey];
|
||||
}
|
||||
|
||||
// doing actual fetch and filling cache:
|
||||
const response = await fetch(`http://127.0.0.1:8310/node/${pubkey}`);
|
||||
const json = await response.json();
|
||||
if (json && json.addresses && Array.isArray(json.addresses)) {
|
||||
for (const address of json.addresses) {
|
||||
if (address.network === 'tcp') {
|
||||
const ret = {
|
||||
pubkey,
|
||||
host: address.addr.split(':')[0],
|
||||
port: parseInt(address.addr.split(':')[1]),
|
||||
};
|
||||
|
||||
this._nodeConnectionDetailsCache[pubkey] = Object.assign({}, ret, { ts: +new Date() });
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async setRefundAddress(address: string) {
|
||||
const script = bitcoin.address.toOutputScript(address);
|
||||
const refundAddressScriptHex = script.toString('hex');
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:8310/setrefundaddressscript/${refundAddressScriptHex}`);
|
||||
const text = await response.text();
|
||||
return this._processResult(text);
|
||||
}
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
const SHA256 = require('crypto-js/sha256');
|
||||
const ENCHEX = require('crypto-js/enc-hex');
|
||||
const ENCUTF8 = require('crypto-js/enc-utf8');
|
||||
const AES = require('crypto-js/aes');
|
||||
|
||||
const ENCRYPTED_SEED = 'ENCRYPTED_SEED';
|
||||
|
||||
export default class Util {
|
||||
encryptionMarker = 'encrypted://';
|
||||
encryptionKey: string = '';
|
||||
|
||||
constructor(entropy: string) {
|
||||
if (!entropy) throw new Error('entropy not provided');
|
||||
|
||||
this.encryptionKey = this.hashIt(this.hashIt('encryption' + entropy));
|
||||
}
|
||||
|
||||
hashIt(arg: string) {
|
||||
return ENCHEX.stringify(SHA256(arg));
|
||||
}
|
||||
|
||||
encrypt(clearData: string): string {
|
||||
return this.encryptionMarker + AES.encrypt(clearData, this.encryptionKey).toString();
|
||||
}
|
||||
|
||||
decrypt(encryptedData: string | null, encryptionKey: string | null = null): string {
|
||||
if (encryptedData === null) return '';
|
||||
if (!encryptedData.startsWith(this.encryptionMarker)) return encryptedData;
|
||||
const bytes = AES.decrypt(encryptedData.replace(this.encryptionMarker, ''), encryptionKey || this.encryptionKey);
|
||||
return bytes.toString(ENCUTF8);
|
||||
}
|
||||
|
||||
storeEncryptedSeed(encryptedSeed: string) {
|
||||
return localStorage.setItem(ENCRYPTED_SEED, encryptedSeed); // cold
|
||||
}
|
||||
|
||||
retrieveEncryptedSeed() {
|
||||
return localStorage.getItem(ENCRYPTED_SEED); // cold
|
||||
}
|
||||
|
||||
storeHotSeed(seed: string) {
|
||||
window[ENCRYPTED_SEED] = seed;
|
||||
}
|
||||
|
||||
getHotSeed() {
|
||||
return window[ENCRYPTED_SEED];
|
||||
}
|
||||
|
||||
isSeeded() {
|
||||
return !!localStorage.getItem(ENCRYPTED_SEED);
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
import Head from 'next/head';
|
||||
|
||||
const name = 'Hello, Lightning!';
|
||||
export const siteTitle = 'Hello, Lightning!';
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
index?: boolean,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<meta
|
||||
name="description"
|
||||
content={name}
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content={`https://og-image.vercel.app/${encodeURI(
|
||||
siteTitle,
|
||||
)}.png?theme=light&md=0&fontSize=75px&images=https%3A%2F%2Fassets.zeit.co%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg`}
|
||||
/>
|
||||
<meta name="og:title" content={siteTitle} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</Head>
|
||||
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
BIN
gui/favicon.png
BIN
gui/favicon.png
Binary file not shown.
|
Before Width: | Height: | Size: 1006 B |
@ -1,5 +0,0 @@
|
||||
import { HelloScreen } from './helloScreen';
|
||||
|
||||
export interface DefaultScreenProps {
|
||||
changeScreen: (newScreen: HelloScreen) => void;
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
export enum HelloScreen {
|
||||
Gsom,
|
||||
NoWalletDetected,
|
||||
CreateWriteDownSeed,
|
||||
CreateChooseEncryptPassword,
|
||||
UnlockWallet,
|
||||
}
|
||||
2
gui/next-env.d.ts
vendored
2
gui/next-env.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
basePath: '/gui',
|
||||
assetPrefix: '/gui/',
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
const { resolve } = require('path');
|
||||
const { readdir } = require('fs').promises;
|
||||
const fs = require('fs');
|
||||
|
||||
async function getFiles(dir) {
|
||||
const dirents = await readdir(dir, { withFileTypes: true });
|
||||
const files = await Promise.all(dirents.map((dirent) => {
|
||||
const res = resolve(dir, dirent.name);
|
||||
return dirent.isDirectory() ? getFiles(res) : res;
|
||||
}));
|
||||
return Array.prototype.concat(...files);
|
||||
}
|
||||
|
||||
|
||||
fs.writeFileSync('StaticFiles.kt', '/* AUTOGENERATED. DO NOT EDIT. */ \n\n' +
|
||||
'var files: HashMap<String, String> = HashMap<String, String>()\n' +
|
||||
'\n' +
|
||||
'\n' +
|
||||
'class StaticFiles {\n' +
|
||||
' fun getHex(key: String): String {\n' +
|
||||
' return files.getOrDefault(key, "")\n' +
|
||||
' }\n\n' +
|
||||
' constructor() {\n');
|
||||
|
||||
|
||||
console.error(__dirname);
|
||||
|
||||
getFiles(__dirname + '/gui')
|
||||
.then(files => {
|
||||
for (const file of files) {
|
||||
const key = file.replace(__dirname, '');
|
||||
if (!(key.endsWith('.js') || key.endsWith('.css') || key.endsWith('.html'))) continue;
|
||||
console.log(key);
|
||||
const hex = fs.readFileSync(file).toString('hex');
|
||||
// console.log(hex);
|
||||
|
||||
fs.appendFileSync('StaticFiles.kt', ` files.put("${key}", "${hex}")\n`);
|
||||
}
|
||||
fs.appendFileSync('StaticFiles.kt', ' }\n' + '}\n');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
9233
gui/package-lock.json
generated
9233
gui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,33 +0,0 @@
|
||||
{
|
||||
"name": "hellolightninggui",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"tslint": "tslint --fix -p . -c tslint.json ",
|
||||
"build": "next build",
|
||||
"pack2kotlin": "node pack2kotlin.js && mv StaticFiles.kt ../src/main/kotlin/",
|
||||
"export": "rm -r -f .next; rm -r -f _next; next build && next export -o gui && npm run pack2kotlin",
|
||||
"start": "next start"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^17.0.8",
|
||||
"bootstrap": "^5.0.0-beta3",
|
||||
"next": "^10.0.0",
|
||||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"swr": "^0.5.6",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-airbnb": "^5.11.2",
|
||||
"tslint-react-hooks": "^2.2.2",
|
||||
"typescript": "^4.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"bip32": "2.0.6",
|
||||
"bip39": "^3.0.4",
|
||||
"bitcoinjs-lib": "^6.0.1",
|
||||
"cross-fetch": "^3.1.4",
|
||||
"crypto-js": "^4.1.1"
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
import 'bootstrap/dist/css/bootstrap.css';
|
||||
import { AppProps } from 'next/app';
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
import Head from 'next/head';
|
||||
import Layout, {siteTitle} from '../components/layout';
|
||||
import Gsom from '../screens/gsom';
|
||||
import NoWalletDetected from '../screens/noWalletDetected';
|
||||
import {HelloScreen} from '../models/helloScreen';
|
||||
import {useEffect, useState} from 'react';
|
||||
import CreateWriteSeedDown from '../screens/createWriteSeedDown';
|
||||
import Util from '../classes/util';
|
||||
import UnlockWallet from '../screens/unlockWallet';
|
||||
|
||||
export default function Index() {
|
||||
console.log('rendering Index');
|
||||
const [screen, setScreen] = useState<HelloScreen>(HelloScreen.NoWalletDetected);
|
||||
|
||||
useEffect(() => {
|
||||
const ut = new Util('dummy');
|
||||
if (ut.isSeeded()) {
|
||||
if (!ut.getHotSeed())
|
||||
setScreen(HelloScreen.UnlockWallet);
|
||||
else
|
||||
setScreen(HelloScreen.Gsom);
|
||||
} else {
|
||||
setScreen(HelloScreen.NoWalletDetected);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderScreen = () => {
|
||||
console.log('currentScreen = ', screen);
|
||||
switch (screen) {
|
||||
case HelloScreen.Gsom: return (<Gsom changeScreen={setScreen}/>);
|
||||
case HelloScreen.NoWalletDetected: return (<NoWalletDetected changeScreen={setScreen}/>);
|
||||
case HelloScreen.CreateWriteDownSeed: return (<CreateWriteSeedDown changeScreen={setScreen}/>);
|
||||
case HelloScreen.UnlockWallet: return (<UnlockWallet changeScreen={setScreen}/>);
|
||||
default:
|
||||
console.warn('default', screen);
|
||||
return (<Gsom changeScreen={setScreen}/>);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout index>
|
||||
<Head>
|
||||
<title>{siteTitle}</title>
|
||||
</Head>
|
||||
|
||||
<div className="d-flex flex-column min-vh-100 justify-content-center align-items-center">
|
||||
<h1>👋 ⚡️</h1>
|
||||
{renderScreen()}
|
||||
</div>
|
||||
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
'postcss-flexbugs-fixes',
|
||||
[
|
||||
'postcss-preset-env',
|
||||
{
|
||||
autoprefixer: {
|
||||
flexbox: 'no-2009'
|
||||
},
|
||||
stage: 3,
|
||||
features: {
|
||||
'custom-properties': false
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1006 B |
@ -1,60 +0,0 @@
|
||||
import {HelloScreen} from '../models/helloScreen';
|
||||
import {DefaultScreenProps} from '../models/defaultScreenProps';
|
||||
import Ldk from '../classes/ldk';
|
||||
import {useEffect, useState} from 'react';
|
||||
import Util from '../classes/util';
|
||||
|
||||
export default function CreateWriteSeedDown(props: DefaultScreenProps) {
|
||||
const [seed, setSeed] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
(async () => {
|
||||
const ldk = new Ldk();
|
||||
await ldk.generate();
|
||||
setSeed(ldk.getSecret());
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<span>
|
||||
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<h2>Write down seed words:</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
{seed}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<button type="button" className="btn btn-primary" onClick={async () => {
|
||||
let password = '';
|
||||
for (;;) {
|
||||
const pass1 = prompt("Please enter your payment password", "");
|
||||
const pass2 = prompt("Please repeat your password", "");
|
||||
if (pass1 === pass2 && pass1 != null) {
|
||||
password = pass1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const ut = new Util(password);
|
||||
const encryptedSeed = ut.encrypt(seed)
|
||||
alert("Wallet successfully seeded!");
|
||||
ut.storeHotSeed(seed);
|
||||
ut.storeEncryptedSeed(encryptedSeed)
|
||||
props.changeScreen(HelloScreen.Gsom);
|
||||
}}>next</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -1,160 +0,0 @@
|
||||
import useSWR from 'swr';
|
||||
import {DefaultScreenProps} from '../models/defaultScreenProps';
|
||||
import Ldk from '../classes/ldk';
|
||||
import Util from '../classes/util';
|
||||
|
||||
let lastBlockchainSync = 0;
|
||||
let lastNetworkGraphSaved = +new Date();
|
||||
let lastPeersReconnect = 0;
|
||||
let maturingBalance = 0;
|
||||
let maturingHeight = 0;
|
||||
|
||||
const fetcher = async (arg1) => {
|
||||
const ldk = new Ldk();
|
||||
const util = new Util('dummy');
|
||||
ldk.setSecret(util.getHotSeed());
|
||||
|
||||
if (+new Date() - lastBlockchainSync > 5 * 60 * 1000) { // 5 min
|
||||
console.log('syncing blockchain...')
|
||||
lastBlockchainSync = +new Date();
|
||||
await ldk.setRefundAddress(ldk.unwrapFirstExternalAddressFromMnemonics());
|
||||
maturingBalance = await ldk.getMaturingBalance();
|
||||
maturingHeight = await ldk.getMaturingHeight();
|
||||
ldk.checkBlockchain(); // let it run in the background
|
||||
await ldk.broadcastTxsIfNecessary();
|
||||
}
|
||||
|
||||
if (+new Date() - lastNetworkGraphSaved > 1 * 60 * 1000) {
|
||||
lastNetworkGraphSaved = +new Date();
|
||||
ldk.saveNetworkGraph(); // let it run in the background
|
||||
}
|
||||
|
||||
if (+new Date() - lastPeersReconnect > 0.5 * 60 * 1000) {
|
||||
lastPeersReconnect = +new Date();
|
||||
ldk.reconnectPeers(); // let it run in the background
|
||||
}
|
||||
|
||||
|
||||
const uri = `http://localhost:8310/${arg1}`;
|
||||
try {
|
||||
const res = await fetch(uri);
|
||||
const json = await res.json();
|
||||
if (json && json.result && !json.error) return json.result;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default function Gsom(props: DefaultScreenProps) {
|
||||
const { data: listpeers }: { data?: any, error?: any } = useSWR('listpeers', fetcher, { refreshInterval: 2 * 1000, refreshWhenHidden: true, refreshWhenOffline: true });
|
||||
const { data: listchannels }: { data?: any, error?: any } = useSWR('listchannels', fetcher, { refreshInterval: 5 * 1000, refreshWhenHidden: true, refreshWhenOffline: true });
|
||||
const { data: getnodeid }: { data?: any, error?: any } = useSWR('getnodeid', fetcher, { refreshInterval: 5 * 1000, refreshWhenHidden: true, refreshWhenOffline: true });
|
||||
|
||||
if (!getnodeid) {
|
||||
const ldk = new Ldk();
|
||||
const util = new Util('dummy');
|
||||
|
||||
ldk.setSecret(util.getHotSeed())
|
||||
console.log();
|
||||
|
||||
;(async () => {
|
||||
try {
|
||||
await ldk.setRefundAddress(ldk.unwrapFirstExternalAddressFromMnemonics());
|
||||
await ldk.updateFeerate(); // so any refund claim upon startup would use adequate fee
|
||||
await ldk.start(ldk.getEntropyHex());
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
const renderPeersList = () => {
|
||||
const listItems = (listpeers || []).map((number) =>
|
||||
<li key={number}>{number}</li>
|
||||
);
|
||||
return (
|
||||
<ul>{listItems}</ul>
|
||||
);
|
||||
};
|
||||
|
||||
const renderChannelsList = () => {
|
||||
const listItems = (listchannels || []).map((cha) =>
|
||||
<li key={cha.channel_id}>{cha?.counterparty_node_id || '?'} {cha?.is_usable ? '[usable]' : ''} {cha?.is_funding_locked ? '' : '[funding not locked]'}</li>
|
||||
|
||||
);
|
||||
return (
|
||||
<ul>{listItems}</ul>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
|
||||
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
Total balance<br/>
|
||||
0 sat<br/>
|
||||
$0.00<br/>
|
||||
</div>
|
||||
<div className="col">
|
||||
<button type="button" className="btn btn-primary">send</button>
|
||||
</div>
|
||||
<div className="col">
|
||||
<button type="button" className="btn btn-primary">receive</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div className="col">
|
||||
channels:<br/>
|
||||
{renderChannelsList()}
|
||||
Peers:<br/>
|
||||
{renderPeersList()}
|
||||
<button type="button" className="btn btn-outline-primary">manage channels</button><br/>
|
||||
<button type="button" className="btn btn-outline-primary" onClick={async () => {
|
||||
const uri = prompt('input node uri');
|
||||
if (!uri) return;
|
||||
|
||||
const pubkey = uri.split('@')[0];
|
||||
const [host, port] = uri.split('@')[1]?.split(':')
|
||||
if (!pubkey || !host || !port) return;
|
||||
|
||||
const ldk = new Ldk();
|
||||
await ldk.connectPeer(pubkey, host, port);
|
||||
}}>connect peer</button><br/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
<div>
|
||||
{listpeers ? (
|
||||
<div style={{ fontSize: 20 }}>
|
||||
<span>Peers: {JSON.stringify(listpeers)}</span>
|
||||
</div>
|
||||
) : null}
|
||||
{listchannels ? (
|
||||
<div style={{ fontSize: 20 }}>
|
||||
<span>Channels: {JSON.stringify(listchannels)}</span>
|
||||
</div>
|
||||
) : null}
|
||||
{getnodeid ? (
|
||||
<div style={{ fontSize: 20 }}>
|
||||
<span>Node id: {JSON.stringify(getnodeid)}</span>
|
||||
</div>
|
||||
) : (<span>not started..?</span>)}
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
*/
|
||||
@ -1,32 +0,0 @@
|
||||
import { HelloScreen } from '../models/helloScreen';
|
||||
import { DefaultScreenProps } from '../models/defaultScreenProps';
|
||||
|
||||
export default function NoWalletDetected(props: DefaultScreenProps) {
|
||||
return (
|
||||
<div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<h2>No wallet detected</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<button type="button" className="btn btn-primary" onClick={async () => {
|
||||
props.changeScreen(HelloScreen.CreateWriteDownSeed);
|
||||
}}>Create</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<button type="button" className="btn light" onClick={() => {
|
||||
alert('todo');
|
||||
}}>Restore from seed</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
import useSWR from 'swr';
|
||||
import { DefaultScreenProps } from '../models/defaultScreenProps';
|
||||
import Util from '../classes/util';
|
||||
import {HelloScreen} from '../models/helloScreen';
|
||||
|
||||
export default function UnlockWallet(props: DefaultScreenProps) {
|
||||
return (
|
||||
<div>
|
||||
Wallet is locked, please unlock it
|
||||
<br/>
|
||||
<button type="button" className="btn btn-primary" onClick={async () => {
|
||||
let password = '';
|
||||
for (;;) {
|
||||
password = prompt("Please enter your payment password", "");
|
||||
if (password) {
|
||||
const ut = new Util(password);
|
||||
try {
|
||||
const decrypted = ut.decrypt(ut.retrieveEncryptedSeed())
|
||||
if (decrypted) {
|
||||
ut.storeHotSeed(decrypted);
|
||||
props.changeScreen(HelloScreen.Gsom);
|
||||
break;
|
||||
} else {
|
||||
alert('incorrect password');
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Incorrect password: " + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}}>next</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
{
|
||||
"extends": ["tslint-config-airbnb", "tslint-react-hooks"],
|
||||
"rules": {
|
||||
"import-name": false,
|
||||
"ter-arrow-parens": false,
|
||||
"align": false,
|
||||
"max-line-length": [true, 240],
|
||||
"function-name": [
|
||||
true,
|
||||
{
|
||||
"function-regex": "^[a-zA-Z$][\\w\\d]+$",
|
||||
"method-regex": "^[a-z$][\\w\\d]+$",
|
||||
"private-method-regex": "^[a-z$][\\w\\d]+$",
|
||||
"protected-method-regex": "^[a-z$][\\w\\d]+$",
|
||||
"static-method-regex": "^[a-z$][\\w\\d]+$"
|
||||
}
|
||||
],
|
||||
"variable-name": {
|
||||
"options": ["ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
2
openchannelstep1.sh
Executable file
2
openchannelstep1.sh
Executable file
@ -0,0 +1,2 @@
|
||||
curl http://127.0.0.1:8310/openchannelstep1/030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f/500000
|
||||
|
||||
@ -8,7 +8,6 @@ class ClientHandler(client: Socket) {
|
||||
private val reader: Scanner = Scanner(client.getInputStream())
|
||||
private val writer: OutputStream = client.getOutputStream()
|
||||
private val executor: Executor = Executor()
|
||||
private val server: Server = Server()
|
||||
private var running: Boolean = false
|
||||
|
||||
fun run() {
|
||||
@ -29,20 +28,9 @@ class ClientHandler(client: Socket) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (text.startsWith("GET /gui")) {
|
||||
val resp = server.serve(text);
|
||||
write(resp)
|
||||
shutdown();
|
||||
continue;
|
||||
}
|
||||
|
||||
var corsHeader = "access-control-allow-origin: http://localhost:8310\n"
|
||||
if (ARG_DISABLE_CORS) corsHeader = "access-control-allow-origin: *\n"
|
||||
|
||||
if (text.startsWith("GET /")) {
|
||||
println(text);
|
||||
write("HTTP/1.0 200 OK\n" +
|
||||
corsHeader +
|
||||
"Content-type: text/html; charset=UTF-8\n")
|
||||
val text2 = text.split(' ')
|
||||
val values = text2[1].split('/')
|
||||
@ -71,7 +59,8 @@ class ClientHandler(client: Socket) {
|
||||
// TODO: Implement exception handling
|
||||
println("Exception handling '" + text + "': ")
|
||||
println(ex)
|
||||
write(helperJsonResponseFailure("Exception handling '" + text + "': " + ex.toString()))
|
||||
write("Exception handling '" + text + "': ")
|
||||
write(ex.toString())
|
||||
shutdown()
|
||||
} finally {
|
||||
|
||||
@ -87,7 +76,7 @@ class ClientHandler(client: Socket) {
|
||||
private fun shutdown() {
|
||||
running = false
|
||||
client.close()
|
||||
// println("${client.inetAddress.hostAddress} closed the connection")
|
||||
println("${client.inetAddress.hostAddress} closed the connection")
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class Executor {
|
||||
@ -10,17 +9,6 @@ class Executor {
|
||||
"start" -> {
|
||||
if (arg1 == null || arg2 == null || arg3 == null) return helperJsonResponseFailure("incorrect arguments")
|
||||
println("starting LDK... using " + arg1 + " " + arg2 + " " + arg3)
|
||||
|
||||
if (started) return helperJsonResponseFailure("already started")
|
||||
started = true;
|
||||
|
||||
homedir += "/" + sha256(sha256(arg1)).substring(0, 8);
|
||||
println("using " + homedir)
|
||||
val directory = File(homedir)
|
||||
if (!directory.exists()) {
|
||||
directory.mkdir()
|
||||
}
|
||||
|
||||
var serializedChannelManager = ""
|
||||
var serializedMonitors = ""
|
||||
var monitors = arrayOf<String>()
|
||||
@ -44,7 +32,7 @@ class Executor {
|
||||
return helperJsonResponseSuccess("ok")
|
||||
}
|
||||
"ldkversion" -> return helperJsonResponseSuccess((org.ldk.impl.version.get_ldk_java_bindings_version() + ", " + org.ldk.impl.bindings.get_ldk_c_bindings_version() + ", " + org.ldk.impl.bindings.get_ldk_version()))
|
||||
"version" -> return helperJsonResponseSuccess("1.2.3")
|
||||
"version" -> return "1.0.0"
|
||||
"connectpeer" -> {
|
||||
if (arg1 == null || arg2 == null || arg3 == null) return helperJsonResponseFailure("incorrect arguments")
|
||||
var retValue = "";
|
||||
@ -178,11 +166,6 @@ class Executor {
|
||||
eventsPaymentPathFailed = arrayOf<String>()
|
||||
return helperJsonResponseSuccess(ret)
|
||||
}
|
||||
"geteventspaymentfailed" -> {
|
||||
val ret = eventsPaymentFailed.joinToString(separator = ",", prefix = "[", postfix = "]")
|
||||
eventsPaymentFailed = arrayOf<String>()
|
||||
return helperJsonResponseSuccess(ret)
|
||||
}
|
||||
"geteventspaymentreceived" -> {
|
||||
val ret = eventsPaymentReceived.joinToString(separator = ",", prefix = "[", postfix = "]")
|
||||
eventsPaymentReceived = arrayOf<String>()
|
||||
@ -284,11 +267,6 @@ class Executor {
|
||||
})
|
||||
return retValue
|
||||
}
|
||||
"node" -> {
|
||||
if (arg1 == null) return helperJsonResponseFailure("incorrect arguments")
|
||||
val resp = URL("https://1ml.com/node/" + arg1 + "/json").readText()
|
||||
return resp;
|
||||
}
|
||||
"transactionconfirmed" -> {
|
||||
if (arg1 == null || arg2 == null || arg3 == null || arg4 == null) return helperJsonResponseFailure("incorrect arguments")
|
||||
var retValue = ""
|
||||
|
||||
@ -2,15 +2,9 @@ import org.ldk.batteries.ChannelManagerConstructor
|
||||
import org.ldk.batteries.NioPeerHandler
|
||||
import org.ldk.structs.*
|
||||
import java.io.File
|
||||
import java.net.InetAddress
|
||||
import java.net.ServerSocket
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
// Globals. Ugly, but ok
|
||||
|
||||
var ARG_DISABLE_CORS = false;
|
||||
var ARG_NO_DISPLAY = false;
|
||||
var started = false;
|
||||
var homedir = ""
|
||||
val prefix_channel_monitor = "channel_monitor_"
|
||||
val prefix_channel_manager = "channel_manager"
|
||||
@ -40,22 +34,11 @@ var eventsRegisterOutput: Array<String> = arrayOf<String>()
|
||||
var eventsTxBroadcast: Array<String> = arrayOf<String>()
|
||||
var eventsPaymentSent: Array<String> = arrayOf<String>()
|
||||
var eventsPaymentPathFailed: Array<String> = arrayOf<String>()
|
||||
var eventsPaymentFailed: Array<String> = arrayOf<String>()
|
||||
var eventsPaymentReceived: Array<String> = arrayOf<String>()
|
||||
var eventsPaymentForwarded: Array<String> = arrayOf<String>()
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
println("Hello Lightning!")
|
||||
args.iterator().forEach {
|
||||
if (it == "--disable-cors" || it == "--no-cors") {
|
||||
ARG_DISABLE_CORS = true
|
||||
println("CORS disabled")
|
||||
}
|
||||
if (it == "--no-display") {
|
||||
ARG_NO_DISPLAY = true
|
||||
println("no display")
|
||||
}
|
||||
}
|
||||
homedir = System.getProperty("user.home") + "/.hellolightning";
|
||||
println("using " + homedir)
|
||||
|
||||
@ -64,13 +47,12 @@ fun main(args: Array<String>) {
|
||||
directory.mkdir()
|
||||
}
|
||||
|
||||
val server = ServerSocket(8310, 0, InetAddress.getLoopbackAddress())
|
||||
val server = ServerSocket(8310)
|
||||
println("Server is running on port ${server.localPort}")
|
||||
if (!ARG_NO_DISPLAY) openInBrowser("http://localhost:8310/gui/");
|
||||
|
||||
while (true) {
|
||||
val client = server.accept()
|
||||
// println("Client connected: ${client.inetAddress.hostAddress}")
|
||||
println("Client connected: ${client.inetAddress.hostAddress}")
|
||||
// Run client in it's own thread.
|
||||
thread { ClientHandler(client).run() }
|
||||
}
|
||||
|
||||
@ -47,9 +47,9 @@ fun getNodeId(promise: Promise) {
|
||||
}
|
||||
|
||||
fun setFeerate(newFeerateFast: Int, newFeerateMedium: Int, newFeerateSlow: Int, promise: Promise) {
|
||||
if (newFeerateFast < 253) return promise.reject("newFeerateFast is too small");
|
||||
if (newFeerateMedium < 253) return promise.reject("newFeerateMedium is too small");
|
||||
if (newFeerateSlow < 253) return promise.reject("newFeerateSlow is too small");
|
||||
if (newFeerateFast < 300) return promise.reject("newFeerateFast is too small");
|
||||
if (newFeerateMedium < 300) return promise.reject("newFeerateMedium is too small");
|
||||
if (newFeerateSlow < 300) return promise.reject("newFeerateSlow is too small");
|
||||
feerate_fast = newFeerateFast;
|
||||
feerate_medium = newFeerateMedium;
|
||||
feerate_slow = newFeerateSlow;
|
||||
@ -89,7 +89,7 @@ fun channel2channelObject(it: ChannelDetails): String {
|
||||
channelObject += "\"is_public\":" + it._is_public + ",";
|
||||
val fundingTxoTxid = it._funding_txo?._txid;
|
||||
if (fundingTxoTxid is ByteArray) {
|
||||
channelObject += "\"funding_txo_txid\":" + "\"" + byteArrayToHex(fundingTxoTxid.reversedArray()) + "\",";
|
||||
channelObject += "\"funding_txo_txid\":" + "\"" + byteArrayToHex(fundingTxoTxid) + "\",";
|
||||
}
|
||||
val fundingTxoIndex = it._funding_txo?._index;
|
||||
if (fundingTxoIndex != null) {
|
||||
|
||||
@ -53,10 +53,9 @@ fun handleEvent(event: Event) {
|
||||
|
||||
if (txResult is Result_TransactionNoneZ.Result_TransactionNoneZ_OK) {
|
||||
// success building the transaction, passing it to outer code to broadcast
|
||||
val params = WritableMap();
|
||||
params.putString("txhex", byteArrayToHex(txResult.res))
|
||||
storeEvent("$homedir/events_tx_broadcast", params)
|
||||
eventsTxBroadcast = eventsTxBroadcast.plus(params.toString())
|
||||
// val params = Arguments.createMap();
|
||||
// params.putString("txhex", byteArrayToHex(txResult.res))
|
||||
// this.sendEvent(MARKER_BROADCAST, params)
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,12 +67,8 @@ fun handleEvent(event: Event) {
|
||||
eventsPaymentSent = eventsPaymentSent.plus(params.toString())
|
||||
}
|
||||
|
||||
if (event is Event.PaymentPathSuccessful) {
|
||||
println("ReactNativeLDK: " + "payment path successful");
|
||||
}
|
||||
|
||||
if (event is Event.PaymentPathFailed) {
|
||||
println("ReactNativeLDK: " + "payment path failed, payment_hash: " + byteArrayToHex(event.payment_hash));
|
||||
println("ReactNativeLDK: " + "payment failed, payment_hash: " + byteArrayToHex(event.payment_hash));
|
||||
val params = WritableMap()
|
||||
params.putString("payment_hash", byteArrayToHex(event.payment_hash));
|
||||
params.putString("rejected_by_dest", event.rejected_by_dest.toString());
|
||||
@ -81,15 +76,6 @@ fun handleEvent(event: Event) {
|
||||
eventsPaymentPathFailed = eventsPaymentPathFailed.plus(params.toString())
|
||||
}
|
||||
|
||||
if (event is Event.PaymentFailed) {
|
||||
println("ReactNativeLDK: " + "payment failed, payment_hash: " + byteArrayToHex(event.payment_hash));
|
||||
val params = WritableMap()
|
||||
params.putString("payment_hash", byteArrayToHex(event.payment_hash));
|
||||
params.putString("payment_id", byteArrayToHex(event.payment_id));
|
||||
storeEvent("$homedir/events_payment_failed", params)
|
||||
eventsPaymentFailed = eventsPaymentFailed.plus(params.toString())
|
||||
}
|
||||
|
||||
if (event is Event.PaymentReceived) {
|
||||
println("ReactNativeLDK: " + "payment received, payment_hash: " + byteArrayToHex(event.payment_hash));
|
||||
var paymentPreimage: ByteArray? = null;
|
||||
@ -225,10 +211,8 @@ fun start(
|
||||
|
||||
// INITIALIZE THE LOGGER #######################################################################
|
||||
// What it's used for: LDK logging
|
||||
val logger = Logger.new_impl { arg: Record ->
|
||||
if (arg._level == org.ldk.enums.Level.LDKLevel_Gossip) return@new_impl;
|
||||
if (arg._level == org.ldk.enums.Level.LDKLevel_Trace) return@new_impl;
|
||||
println("ReactNativeLDK: " + arg._args)
|
||||
val logger = Logger.new_impl { arg: String? ->
|
||||
println("ReactNativeLDK: " + arg)
|
||||
// val params = Arguments.createMap()
|
||||
// params.putString("line", arg)
|
||||
// sendEvent(MARKER_LOG, params)
|
||||
@ -289,7 +273,7 @@ fun start(
|
||||
override fun register_tx(txid: ByteArray, script_pubkey: ByteArray) {
|
||||
println("ReactNativeLDK: register_tx");
|
||||
val params = WritableMap()
|
||||
params.putString("txid", byteArrayToHex(txid.reversedArray()))
|
||||
params.putString("txid", byteArrayToHex(txid))
|
||||
params.putString("script_pubkey", byteArrayToHex(script_pubkey))
|
||||
storeEvent("$homedir/events_register_tx", params)
|
||||
eventsRegisterTx = eventsRegisterTx.plus(params.toString())
|
||||
@ -365,33 +349,17 @@ fun start(
|
||||
router = NetworkGraph.of(hexStringToByteArray("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f").reversedArray())
|
||||
}
|
||||
|
||||
/*val route_handler = NetGraphMsgHandler.of(
|
||||
val route_handler = NetGraphMsgHandler.of(
|
||||
router,
|
||||
Option_AccessZ.none(),
|
||||
logger
|
||||
)*/
|
||||
)
|
||||
|
||||
// INITIALIZE THE CHANNELMANAGER ###############################################################
|
||||
// What it's used for: managing channel state
|
||||
|
||||
|
||||
val scorer = MultiThreadedLockableScore.of(Scorer.with_default().as_Score())
|
||||
|
||||
// this is gona be fee policy for __incoming__ channels. they are set upfront globally:
|
||||
val uc = UserConfig.with_default()
|
||||
val newChannelConfig = ChannelConfig.with_default()
|
||||
newChannelConfig.set_forwarding_fee_proportional_millionths(10000);
|
||||
newChannelConfig.set_forwarding_fee_base_msat(1000);
|
||||
|
||||
val handshake = ChannelHandshakeConfig.with_default();
|
||||
handshake.set_minimum_depth(1);
|
||||
uc.set_own_channel_config(handshake);
|
||||
|
||||
uc.set_channel_options(newChannelConfig);
|
||||
val newLim = ChannelHandshakeLimits.with_default()
|
||||
newLim.set_force_announced_channel_preference(false)
|
||||
uc.set_peer_channel_config_limits(newLim)
|
||||
//
|
||||
val scorer = LockableScore.of(Scorer.with_default().as_Score())
|
||||
|
||||
try {
|
||||
if (serializedChannelManagerHex != "") {
|
||||
@ -399,7 +367,6 @@ fun start(
|
||||
channel_manager_constructor = ChannelManagerConstructor(
|
||||
hexStringToByteArray(serializedChannelManagerHex),
|
||||
channelMonitors,
|
||||
uc,
|
||||
keys_manager?.as_KeysInterface(),
|
||||
fee_estimator,
|
||||
chain_monitor,
|
||||
@ -414,6 +381,17 @@ fun start(
|
||||
nio_peer_handler = channel_manager_constructor!!.nio_peer_handler;
|
||||
} else {
|
||||
// fresh start
|
||||
|
||||
// this is gona be fee policy for __incoming__ channels. they are set upfront globally:
|
||||
val uc = UserConfig.with_default()
|
||||
val newChannelConfig = ChannelConfig.with_default()
|
||||
newChannelConfig.set_forwarding_fee_proportional_millionths(10000);
|
||||
newChannelConfig.set_forwarding_fee_base_msat(1000);
|
||||
uc.set_channel_options(newChannelConfig);
|
||||
val newLim = ChannelHandshakeLimits.with_default()
|
||||
newLim.set_force_announced_channel_preference(false)
|
||||
uc.set_peer_channel_config_limits(newLim)
|
||||
//
|
||||
channel_manager_constructor = ChannelManagerConstructor(
|
||||
Network.LDKNetwork_Bitcoin,
|
||||
uc,
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
val staticFiles = StaticFiles();
|
||||
|
||||
class Server {
|
||||
fun serve(text: String): String {
|
||||
println(text);
|
||||
var ret = "";
|
||||
|
||||
val text2 = text.split(' ')
|
||||
val values = text2[1].split('/')
|
||||
|
||||
var file2serve = "";
|
||||
if (values.elementAtOrNull(2) == null || values.elementAtOrNull(2) == "") {
|
||||
file2serve = "/index.html"; // default
|
||||
} else {
|
||||
for (i in 2..6) {
|
||||
if (values.elementAtOrNull(i) == null) break;
|
||||
file2serve += "/" + values[i] // adding directories to path
|
||||
if (file2serve.endsWith(".js") || file2serve.endsWith(".css") || file2serve.endsWith(".html") || file2serve.endsWith(".png")) break;
|
||||
}
|
||||
}
|
||||
|
||||
// now, got a filename to look for. lets look in bundled files:
|
||||
|
||||
val hex = staticFiles.getHex("/gui" + file2serve);
|
||||
if (hex != "") {
|
||||
val bar = hexStringToByteArray(hex).toString(Charsets.UTF_8)
|
||||
if (file2serve.endsWith(".css")) {
|
||||
ret += "HTTP/1.0 200 OK\n" + "Content-type: text/css; charset=UTF-8\n\n";
|
||||
} else if (file2serve.endsWith(".js")) {
|
||||
ret += "HTTP/1.0 200 OK\n" + "Content-type: application/javascript; charset=UTF-8\n\n";
|
||||
} else {
|
||||
ret += "HTTP/1.0 200 OK\n" + "Content-type: text/html; charset=UTF-8\n\n";
|
||||
}
|
||||
// TODO: png and other binaries
|
||||
ret += bar;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// not in bundled files
|
||||
|
||||
return "HTTP/1.0 404 OK\n" + "Content-type: text/html; charset=UTF-8\n\n" + "does not exist: " + file2serve;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -1,9 +1,4 @@
|
||||
import java.awt.Desktop
|
||||
import java.io.File
|
||||
import java.math.BigInteger
|
||||
import java.net.URI
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
|
||||
fun hexStringToByteArray(strArg: String): ByteArray {
|
||||
val HEX_CHARS = "0123456789ABCDEF"
|
||||
@ -47,20 +42,4 @@ fun storeEvent(eventsPath: String, params: WritableMap) {
|
||||
}
|
||||
|
||||
File(eventsPath + "/" + System.currentTimeMillis() + ".json").writeText(params.toString())
|
||||
}
|
||||
|
||||
fun sha256(input:String): String {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
return BigInteger(1, md.digest(input.toByteArray())).toString(16).padStart(32, '0')
|
||||
}
|
||||
|
||||
fun openInBrowser(uri: String) {
|
||||
val osName by lazy(LazyThreadSafetyMode.NONE) { System.getProperty("os.name").lowercase(Locale.getDefault()) }
|
||||
val desktop = Desktop.getDesktop()
|
||||
when {
|
||||
Desktop.isDesktopSupported() && desktop.isSupported(Desktop.Action.BROWSE) -> desktop.browse(URI(uri))
|
||||
"mac" in osName -> Runtime.getRuntime().exec("open $uri")
|
||||
"nix" in osName || "nux" in osName -> Runtime.getRuntime().exec("xdg-open $uri")
|
||||
else -> throw RuntimeException("cannot open $uri")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user