This commit is contained in:
Overtorment 2021-12-22 16:12:34 +00:00
parent 53181dabd6
commit 80882bf984
19 changed files with 6257 additions and 92 deletions

2
.idea/compiler.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
<bytecodeTargetLevel target="17" />
</component>
</project>

2
.idea/misc.xml generated
View File

@ -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="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

465
gui/classes/ldk.ts Normal file
View File

@ -0,0 +1,465 @@
/* 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]);
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 = {};
const listPeers = await this.listPeers();
const listChannels = await this.listChannels();
// 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);
}
}

52
gui/classes/util.ts Normal file
View File

@ -0,0 +1,52 @@
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);
}
}

View File

@ -0,0 +1,5 @@
import { HelloScreen } from './helloScreen';
export interface DefaultScreenProps {
changeScreen: (newScreen: HelloScreen) => void;
}

View File

@ -0,0 +1,7 @@
export enum HelloScreen {
Gsom,
NoWalletDetected,
CreateWriteDownSeed,
CreateChooseEncryptPassword,
UnlockWallet,
}

5409
gui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,8 @@
"dev": "next dev",
"tslint": "tslint --fix -p . -c tslint.json ",
"build": "next build",
"export": "rm -r -f .next; rm -r -f _next; next build && next export -o gui; npm run pack2kotlin",
"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": {
@ -23,5 +23,11 @@
"tslint-react-hooks": "^2.2.2",
"typescript": "^4.2.4"
},
"dependencies": {}
"dependencies": {
"bip32": "2.0.6",
"bip39": "^3.0.4",
"bitcoinjs-lib": "^6.0.1",
"cross-fetch": "^3.1.4",
"crypto-js": "^4.1.1"
}
}

View File

@ -1,22 +1,41 @@
import Head from 'next/head';
import Layout, { siteTitle } from '../components/layout';
import useSWR from 'swr';
const fetcher = async (arg1) => {
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;
}
};
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() {
const { data: listpeers }: { data?: any, error?: any } = useSWR('listpeers', fetcher, { refreshInterval: 5 * 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 });
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>
@ -25,23 +44,10 @@ export default function Index() {
</Head>
<div className="d-flex flex-column min-vh-100 justify-content-center align-items-center">
{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/>
<h1>👋 </h1>
{renderScreen()}
</div>
</Layout>
);
}

View File

@ -0,0 +1,60 @@
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>
);
}

146
gui/screens/gsom.tsx Normal file
View File

@ -0,0 +1,146 @@
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
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 () => {
await ldk.setRefundAddress(ldk.unwrapFirstExternalAddressFromMnemonics());
await ldk.updateFeerate(); // so any refund claim upon startup would use adequate fee
await ldk.start(ldk.getEntropyHex());
})();
}
const renderPeersList = () => {
const listItems = (listpeers || []).map((number) =>
<li key={number}>{number}</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/>
todo<br/>
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>
*/

View File

@ -0,0 +1,32 @@
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>
);
}

View File

@ -0,0 +1,34 @@
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>
);
}

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,5 @@
import java.io.File
import java.net.URL
import kotlin.system.exitProcess
class Executor {
@ -40,7 +41,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.1.3")
"version" -> return helperJsonResponseSuccess("1.2.0")
"connectpeer" -> {
if (arg1 == null || arg2 == null || arg3 == null) return helperJsonResponseFailure("incorrect arguments")
var retValue = "";
@ -174,6 +175,11 @@ 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>()
@ -275,6 +281,11 @@ 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 = ""

View File

@ -39,13 +39,14 @@ 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") {
if (it == "--disable-cors" || it == "--no-cors") {
ARG_DISABLE_CORS = true
println("CORS disabled")
}

View File

@ -68,8 +68,12 @@ 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 failed, payment_hash: " + byteArrayToHex(event.payment_hash));
println("ReactNativeLDK: " + "payment path 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());
@ -77,6 +81,15 @@ 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;
@ -213,6 +226,7 @@ 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;
println("ReactNativeLDK: " + arg._args)
// val params = Arguments.createMap()
// params.putString("line", arg)
@ -360,13 +374,18 @@ fun start(
// What it's used for: managing channel state
val scorer = LockableScore.of(Scorer.with_default().as_Score())
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)

File diff suppressed because one or more lines are too long