mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-26 08:55:56 +01:00
Lnd WIP (#32)
FIX: HD wallet send WIP: lnd wallets add/import/remove works
This commit is contained in:
parent
316667f9c3
commit
bd71479e75
17 changed files with 1343 additions and 57 deletions
|
@ -37,7 +37,6 @@ export class BlueButton extends Component {
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
borderWidth: 0.7,
|
borderWidth: 0.7,
|
||||||
borderColor: 'transparent',
|
borderColor: 'transparent',
|
||||||
borderLeftColor: 'transparent',
|
|
||||||
}}
|
}}
|
||||||
buttonStyle={Object.assign(
|
buttonStyle={Object.assign(
|
||||||
{
|
{
|
||||||
|
@ -184,7 +183,18 @@ export class BlueCard extends Component {
|
||||||
|
|
||||||
export class BlueText extends Component {
|
export class BlueText extends Component {
|
||||||
render() {
|
render() {
|
||||||
return <Text {...this.props} style={{ color: BlueApp.settings.foregroundColor }} />;
|
return (
|
||||||
|
<Text
|
||||||
|
{...this.props}
|
||||||
|
style={Object.assign(
|
||||||
|
{
|
||||||
|
color: BlueApp.settings.foregroundColor,
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line
|
||||||
|
this.props.style,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class BlueTextCentered extends Component {
|
export class BlueTextCentered extends Component {
|
||||||
|
@ -599,6 +609,27 @@ export class BlueTransactionPendingIcon extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class BlueTransactionOnchainIcon extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View {...this.props} style={stylesBlueIcon.container}>
|
||||||
|
<View style={stylesBlueIcon.boxIncomming}>
|
||||||
|
<View style={stylesBlueIcon.ballIncomming}>
|
||||||
|
<Icon
|
||||||
|
{...this.props}
|
||||||
|
name="link"
|
||||||
|
size={16}
|
||||||
|
type="font-awesome"
|
||||||
|
color="#37c0a1"
|
||||||
|
iconStyle={{ left: 0, top: 7, transform: [{ rotate: '-45deg' }] }}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class BlueTransactionOutgoingIcon extends Component {
|
export class BlueTransactionOutgoingIcon extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
@ -728,6 +759,66 @@ export class BlueSendButtonIcon extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ManageFundsBigButton extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
{...this.props}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 30,
|
||||||
|
left: (width - 190) / 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
width: 190,
|
||||||
|
height: 40,
|
||||||
|
position: 'relative',
|
||||||
|
backgroundColor: '#ccddf9',
|
||||||
|
borderBottomRightRadius: 15,
|
||||||
|
borderBottomLeftRadius: 15,
|
||||||
|
borderTopRightRadius: 15,
|
||||||
|
borderTopLeftRadius: 15,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
left: 20,
|
||||||
|
top: 5,
|
||||||
|
borderBottomLeftRadius: 15,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
transform: [{ rotate: '90deg' }],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon {...this.props} name="link" size={16} type="font-awesome" color="#2f5fb3" iconStyle={{ left: 0, top: 0 }} />
|
||||||
|
</View>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: '#2f5fb3',
|
||||||
|
fontSize: (isIpad && 10) || 16,
|
||||||
|
fontWeight: '500',
|
||||||
|
left: 25,
|
||||||
|
top: 12,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
manage funds
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class BluePlusIconDimmed extends Component {
|
export class BluePlusIconDimmed extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -69,9 +69,9 @@ it('can generate Segwit HD (BIP49)', async () => {
|
||||||
assert.ok(hd2.validateMnemonic());
|
assert.ok(hd2.validateMnemonic());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('HD (BIP49)can create TX', async () => {
|
it('HD (BIP49) can create TX', async () => {
|
||||||
if (!process.env.HD_MNEMONIC) {
|
if (!process.env.HD_MNEMONIC) {
|
||||||
console.log('process.env.HD_MNEMONIC not set, skipped');
|
console.warn('process.env.HD_MNEMONIC not set, skipped');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 90 * 1000;
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 90 * 1000;
|
||||||
|
@ -85,7 +85,7 @@ it('HD (BIP49)can create TX', async () => {
|
||||||
let txhex = hd.createTx(hd.utxo, 0.000014, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK');
|
let txhex = hd.createTx(hd.utxo, 0.000014, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK');
|
||||||
assert.equal(
|
assert.equal(
|
||||||
txhex,
|
txhex,
|
||||||
'01000000000102ee7a13faf14dd004c6fa403c3073fbb6e0d7389ffa45e879fd96b5e21fd8989d00000000171600142f18e8406c9d210f30c901b24e5feeae78784eb7ffffffff22cde2709a2774a008fd0513e94edde4fdc71195ce0fd408e524df10f386fb67000000001716001468dde644410cc789d91a7f36b823f38369755a1cffffffff02780500000000000017a914a3a65daca3064280ae072b9d6773c027b30abace87dc0500000000000017a914850f4dbc255654de2c12c6f6d79cf9cb756cad038702473044022025e2a280e77691804ef3aa8039dceb5b7e454fb97edd2088f32858e86115bb030220553c21f7c9026a833ad9582a119cd6b24227fc45ed84fd18115ae71e5a8975f5012102edd141c5a27a726dda66be10a38b0fd3ccbb40e7c380034aaa43a1656d5f4dd60247304402207c9b7b0b7767e7bb37388fbfb865402ca58d2d7b88a7110244fc5d7881ae3cce022037874f10db854df4bfdc9ef2b02a9e2919a238eac6aad82bd82e528585084e3b0121030db3c49461a5e539e97bab62ab2b8f88151d1c2376493cf73ef1d02ef60637fd00000000',
|
'010000000001029d98d81fe2b596fd79e845fa9f38d7e0b6fb73303c40fac604d04df1fa137aee00000000171600142f18e8406c9d210f30c901b24e5feeae78784eb7ffffffff67fb86f310df24e508d40fce9511c7fde4dd4ee91305fd08a074279a70e2cd22000000001716001468dde644410cc789d91a7f36b823f38369755a1cffffffff02780500000000000017a914a3a65daca3064280ae072b9d6773c027b30abace87dc0500000000000017a914850f4dbc255654de2c12c6f6d79cf9cb756cad038702483045022100dc8390a9fd34c31259fa47f9fc182f20d991110ecfd5b58af1cf542fe8de257a022004c2d110da7b8c4127675beccc63b46fd65c706951f090fd381fa3b21d3c5c08012102edd141c5a27a726dda66be10a38b0fd3ccbb40e7c380034aaa43a1656d5f4dd60247304402207c0aef8313d55e72474247daad955979f62e56d1cbac5f2d14b8b022c6ce112602205d9aa3804f04624b12ab8a5ab0214b529c531c2f71c27c6f18aba6502a6ea0a80121030db3c49461a5e539e97bab62ab2b8f88151d1c2376493cf73ef1d02ef60637fd00000000',
|
||||||
);
|
);
|
||||||
|
|
||||||
let bitcoin = require('bitcoinjs-lib');
|
let bitcoin = require('bitcoinjs-lib');
|
||||||
|
@ -124,7 +124,7 @@ it('Segwit HD (BIP49) can fetch UTXO', async function() {
|
||||||
let hd = new HDSegwitP2SHWallet();
|
let hd = new HDSegwitP2SHWallet();
|
||||||
hd.usedAddresses = ['1Ez69SnzzmePmZX3WpEzMKTrcBF2gpNQ55', '1BiTCHeYzJNMxBLFCMkwYXNdFEdPJP53ZV']; // hacking internals
|
hd.usedAddresses = ['1Ez69SnzzmePmZX3WpEzMKTrcBF2gpNQ55', '1BiTCHeYzJNMxBLFCMkwYXNdFEdPJP53ZV']; // hacking internals
|
||||||
await hd.fetchUtxo();
|
await hd.fetchUtxo();
|
||||||
assert.equal(hd.utxo.length, 8);
|
assert.equal(hd.utxo.length, 9);
|
||||||
assert.ok(hd.utxo[0].confirmations);
|
assert.ok(hd.utxo[0].confirmations);
|
||||||
assert.ok(hd.utxo[0].txid);
|
assert.ok(hd.utxo[0].txid);
|
||||||
assert.ok(hd.utxo[0].vout);
|
assert.ok(hd.utxo[0].vout);
|
||||||
|
|
|
@ -1,12 +1,143 @@
|
||||||
/* global it */
|
/* global it, describe, jasmine */
|
||||||
|
import Frisbee from 'frisbee';
|
||||||
import { LightningCustodianWallet } from './class';
|
import { LightningCustodianWallet } from './class';
|
||||||
let assert = require('assert');
|
let assert = require('assert');
|
||||||
|
|
||||||
it('can generate auth secret', () => {
|
describe('LightningCustodianWallet', () => {
|
||||||
let l1 = new LightningCustodianWallet();
|
let l1 = new LightningCustodianWallet();
|
||||||
let l2 = new LightningCustodianWallet();
|
|
||||||
l1.generate();
|
|
||||||
l2.generate();
|
|
||||||
|
|
||||||
assert.ok(l1.getSecret() !== l2.getSecret(), 'generated credentials should not be the same');
|
it('can create, auth and getbtc', async () => {
|
||||||
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||||
|
assert.ok(l1.refill_addressess.length === 0);
|
||||||
|
assert.ok(l1._refresh_token_created_ts === 0);
|
||||||
|
assert.ok(l1._access_token_created_ts === 0);
|
||||||
|
l1.balance = 'FAKE';
|
||||||
|
|
||||||
|
await l1.createAccount();
|
||||||
|
await l1.authorize();
|
||||||
|
await l1.fetchBtcAddress();
|
||||||
|
await l1.fetchBalance();
|
||||||
|
await l1.fetchInfo();
|
||||||
|
await l1.fetchTransactions();
|
||||||
|
await l1.fetchPendingTransactions();
|
||||||
|
|
||||||
|
assert.ok(l1.access_token);
|
||||||
|
assert.ok(l1.refresh_token);
|
||||||
|
assert.ok(l1._refresh_token_created_ts > 0);
|
||||||
|
assert.ok(l1._access_token_created_ts > 0);
|
||||||
|
assert.ok(l1.refill_addressess.length > 0);
|
||||||
|
assert.ok(l1.balance === 0);
|
||||||
|
assert.ok(l1.info_raw);
|
||||||
|
assert.ok(l1.pending_transactions_raw.length === 0);
|
||||||
|
assert.ok(l1.transactions_raw.length === 0);
|
||||||
|
assert.ok(l1.transactions_raw.length === l1.getTransactions().length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can refresh token', async () => {
|
||||||
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||||
|
let oldRefreshToken = l1.refresh_token;
|
||||||
|
let oldAccessToken = l1.access_token;
|
||||||
|
await l1.refreshAcessToken();
|
||||||
|
assert.ok(oldRefreshToken !== l1.refresh_token);
|
||||||
|
assert.ok(oldAccessToken !== l1.access_token);
|
||||||
|
assert.ok(l1.access_token);
|
||||||
|
assert.ok(l1.refresh_token);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can use existing login/pass', async () => {
|
||||||
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||||
|
if (!process.env.BLITZHUB) {
|
||||||
|
console.error('process.env.BLITZHUB not set, skipped');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let l2 = new LightningCustodianWallet();
|
||||||
|
l2.setSecret(process.env.BLITZHUB);
|
||||||
|
await l2.authorize();
|
||||||
|
await l2.fetchPendingTransactions();
|
||||||
|
await l2.fetchTransactions();
|
||||||
|
assert.ok(l2.pending_transactions_raw.length === 0);
|
||||||
|
assert.ok(l2.transactions_raw.length > 0);
|
||||||
|
assert.ok(l2.transactions_raw.length === l2.getTransactions().length);
|
||||||
|
await l2.fetchBalance();
|
||||||
|
assert.ok(l2.getBalance() > 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can decode & check invoice', async () => {
|
||||||
|
if (!process.env.BLITZHUB) {
|
||||||
|
console.error('process.env.BLITZHUB not set, skipped');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30 * 1000;
|
||||||
|
let l2 = new LightningCustodianWallet();
|
||||||
|
l2.setSecret(process.env.BLITZHUB);
|
||||||
|
await l2.authorize();
|
||||||
|
|
||||||
|
let invoice =
|
||||||
|
'lnbc1u1pdcqpt3pp5ltuevvq2g69kdrzcegrs9gfqjer45rwjc0w736qjl92yvwtxhn6qdp8dp6kuerjv4j9xct5daeks6tnyp3xc6t50f582cscqp2zrkghzl535xjav52ns0rpskcn20takzdr2e02wn4xqretlgdemg596acq5qtfqhjk4jpr7jk8qfuuka2k0lfwjsk9mchwhxcgxzj3tsp09gfpy';
|
||||||
|
let decoded = await l2.decodeInvoice(invoice);
|
||||||
|
|
||||||
|
assert.ok(decoded.payment_hash);
|
||||||
|
assert.ok(decoded.description);
|
||||||
|
assert.ok(decoded.num_satoshis);
|
||||||
|
|
||||||
|
await l2.checkRouteInvoice(invoice);
|
||||||
|
|
||||||
|
// checking that bad invoice cant be decoded
|
||||||
|
invoice = 'gsom';
|
||||||
|
let error = false;
|
||||||
|
try {
|
||||||
|
await l2.decodeInvoice(invoice);
|
||||||
|
} catch (Err) {
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
assert.ok(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can pay invoice', async () => {
|
||||||
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||||
|
if (!process.env.BLITZHUB) {
|
||||||
|
console.error('process.env.BLITZHUB not set, skipped');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!process.env.STRIKE) {
|
||||||
|
console.error('process.env.STRIKE not set, skipped');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = new Frisbee({
|
||||||
|
baseURI: 'https://api.strike.acinq.co',
|
||||||
|
});
|
||||||
|
|
||||||
|
api.auth(process.env.STRIKE + ':');
|
||||||
|
|
||||||
|
const res = await api.post('/api/v1/charges', {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: 'amount=1¤cy=btc&description=acceptance+test',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.body || !res.body.payment_request) {
|
||||||
|
throw new Error('Strike problem: ' + JSON.stringify(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
let invoice = res.body.payment_request;
|
||||||
|
|
||||||
|
let l2 = new LightningCustodianWallet();
|
||||||
|
l2.setSecret(process.env.BLITZHUB);
|
||||||
|
await l2.authorize();
|
||||||
|
|
||||||
|
let decoded = await l2.decodeInvoice(invoice);
|
||||||
|
assert.ok(decoded.payment_hash);
|
||||||
|
assert.ok(decoded.description);
|
||||||
|
|
||||||
|
await l2.checkRouteInvoice(invoice);
|
||||||
|
|
||||||
|
let start = +new Date();
|
||||||
|
await l2.payInvoice(invoice);
|
||||||
|
let end = +new Date();
|
||||||
|
if ((end - start) / 1000 > 9) {
|
||||||
|
console.warn('payInvoice took', (end - start) / 1000, 'sec');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
SegwitP2SHWallet,
|
SegwitP2SHWallet,
|
||||||
SegwitBech32Wallet,
|
SegwitBech32Wallet,
|
||||||
} from './';
|
} from './';
|
||||||
|
import { LightningCustodianWallet } from './lightning-custodian-wallet';
|
||||||
let encryption = require('../encryption');
|
let encryption = require('../encryption');
|
||||||
|
|
||||||
export class AppStorage {
|
export class AppStorage {
|
||||||
|
@ -147,6 +148,9 @@ export class AppStorage {
|
||||||
case new HDLegacyBreadwalletWallet().type:
|
case new HDLegacyBreadwalletWallet().type:
|
||||||
unserializedWallet = HDLegacyBreadwalletWallet.fromJson(key);
|
unserializedWallet = HDLegacyBreadwalletWallet.fromJson(key);
|
||||||
break;
|
break;
|
||||||
|
case new LightningCustodianWallet().type:
|
||||||
|
unserializedWallet = LightningCustodianWallet.fromJson(key);
|
||||||
|
break;
|
||||||
case 'legacy':
|
case 'legacy':
|
||||||
default:
|
default:
|
||||||
unserializedWallet = LegacyWallet.fromJson(key);
|
unserializedWallet = LegacyWallet.fromJson(key);
|
||||||
|
|
|
@ -22,6 +22,10 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
|
||||||
return 'HD SegWit (BIP49 P2SH)';
|
return 'HD SegWit (BIP49 P2SH)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowSend() {
|
||||||
|
return this.getBalance() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
generate() {
|
generate() {
|
||||||
let c = 32;
|
let c = 32;
|
||||||
let totalhex = '';
|
let totalhex = '';
|
||||||
|
@ -303,7 +307,7 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
|
||||||
|
|
||||||
for (let unspent of json.unspent_outputs) {
|
for (let unspent of json.unspent_outputs) {
|
||||||
// a lil transform for signer module
|
// a lil transform for signer module
|
||||||
unspent.txid = unspent.tx_hash;
|
unspent.txid = unspent.tx_hash_big_endian;
|
||||||
unspent.vout = unspent.tx_output_n;
|
unspent.vout = unspent.tx_output_n;
|
||||||
unspent.amount = unspent.value;
|
unspent.amount = unspent.value;
|
||||||
|
|
||||||
|
|
|
@ -397,10 +397,15 @@ export class LegacyWallet extends AbstractWallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
getLatestTransactionTime() {
|
getLatestTransactionTime() {
|
||||||
for (let tx of this.getTransactions()) {
|
if (this.getTransactions().length === 0) {
|
||||||
return tx.received;
|
return 0;
|
||||||
}
|
}
|
||||||
return 0;
|
let max = 0;
|
||||||
|
for (let tx of this.getTransactions()) {
|
||||||
|
max = Math.max(new Date(tx.received) * 1, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(max).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
getRandomBlockcypherToken() {
|
getRandomBlockcypherToken() {
|
||||||
|
|
|
@ -1,55 +1,464 @@
|
||||||
import { LegacyWallet } from './legacy-wallet';
|
import { LegacyWallet } from './legacy-wallet';
|
||||||
import Frisbee from 'frisbee';
|
import Frisbee from 'frisbee';
|
||||||
|
let BigNumber = require('bignumber.js');
|
||||||
|
|
||||||
export class LightningCustodianWallet extends LegacyWallet {
|
export class LightningCustodianWallet extends LegacyWallet {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
this.init();
|
||||||
this.type = 'lightningCustodianWallet';
|
this.type = 'lightningCustodianWallet';
|
||||||
this.pendingTransactions = [];
|
this.refresh_token = '';
|
||||||
this.token = false;
|
this.access_token = '';
|
||||||
this.tokenRefreshedOn = 0;
|
this._refresh_token_created_ts = 0;
|
||||||
|
this._access_token_created_ts = 0;
|
||||||
|
this.refill_addressess = [];
|
||||||
|
this.pending_transactions_raw = [];
|
||||||
|
this.info_raw = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAddress() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
timeToRefreshBalance() {
|
||||||
|
// blitzhub calls are cheap, so why not refresh constantly
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeToRefreshTransaction() {
|
||||||
|
// blitzhub calls are cheap, so why not refresh the list constantly
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(param) {
|
||||||
|
let obj = super.fromJson(param);
|
||||||
|
obj.init();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
this._api = new Frisbee({
|
this._api = new Frisbee({
|
||||||
baseURI: 'https://api.blockcypher.com/v1/btc/main/addrs/',
|
baseURI: 'https://api.blitzhub.io/',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessTokenExpired() {
|
||||||
|
return (+new Date() - this._access_token_created_ts) / 1000 >= 3600 * 2; // 2h
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshTokenExpired() {
|
||||||
|
return (+new Date() - this._refresh_token_created_ts) / 1000 >= 3600 * 24 * 7; // 7d
|
||||||
|
}
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
// nop
|
||||||
|
}
|
||||||
|
|
||||||
getTypeReadable() {
|
getTypeReadable() {
|
||||||
return 'Lightning (custodian)';
|
return 'Lightning (custodian)';
|
||||||
}
|
}
|
||||||
|
|
||||||
async createAccount() {}
|
async createAccount() {
|
||||||
|
let response = await this._api.post('/create', {
|
||||||
|
body: { partnerid: 'bluewallet', test: true },
|
||||||
|
headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
let json = response.body;
|
||||||
|
if (typeof json === 'undefined') {
|
||||||
|
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
async authorize() {}
|
if (json && json.error) {
|
||||||
|
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||||
|
}
|
||||||
|
|
||||||
async getToken() {}
|
if (!json.login || !json.password) {
|
||||||
|
throw new Error('API unexpected response: ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
async getBtcAddress() {}
|
this.secret = 'blitzhub://' + json.login + ':' + json.password;
|
||||||
|
|
||||||
async newBtcAddress() {}
|
console.log(response.body);
|
||||||
|
}
|
||||||
|
|
||||||
async getPendngBalance() {}
|
async payInvoice(invoice) {
|
||||||
|
let response = await this._api.post('/payinvoice', {
|
||||||
|
body: { invoice: invoice },
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer' + ' ' + this.access_token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let json = response.body;
|
||||||
|
if (typeof json === 'undefined') {
|
||||||
|
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.originalResponse));
|
||||||
|
}
|
||||||
|
|
||||||
async decodeInvoice() {}
|
if (json && json.error) {
|
||||||
|
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||||
|
}
|
||||||
|
|
||||||
async checkRoute() {}
|
console.log(response.body);
|
||||||
|
|
||||||
async payInvoice() {}
|
this.last_paid_invoice_result = json;
|
||||||
|
|
||||||
async sendCoins() {}
|
if (json.payment_preimage) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkRouteInvoice(invoice) {
|
||||||
|
let response = await this._api.get('/checkrouteinvoice?invoice=' + invoice, {
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer' + ' ' + this.access_token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let json = response.body;
|
||||||
|
if (typeof json === 'undefined') {
|
||||||
|
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json && json.error) {
|
||||||
|
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses login & pass stored in `this.secret` to authorize
|
||||||
|
* and set internal `access_token` & `refresh_token`
|
||||||
|
*
|
||||||
|
* @return {Promise.<void>}
|
||||||
|
*/
|
||||||
|
async authorize() {
|
||||||
|
let login = this.secret.replace('blitzhub://', '').split(':')[0];
|
||||||
|
let password = this.secret.replace('blitzhub://', '').split(':')[1];
|
||||||
|
console.log('auth uses login:pass', login, password);
|
||||||
|
let response = await this._api.post('/auth?type=auth', {
|
||||||
|
body: { login: login, password: password },
|
||||||
|
headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
let json = response.body;
|
||||||
|
if (typeof json === 'undefined') {
|
||||||
|
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json && json.error) {
|
||||||
|
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json.access_token || !json.refresh_token) {
|
||||||
|
throw new Error('API unexpected response: ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh_token = json.refresh_token;
|
||||||
|
this.access_token = json.access_token;
|
||||||
|
this._refresh_token_created_ts = +new Date();
|
||||||
|
this._access_token_created_ts = +new Date();
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkLogin() {
|
||||||
|
if (this.accessTokenExpired() && this.refreshTokenExpired()) {
|
||||||
|
// all tokens expired, only option is to login with login and password
|
||||||
|
return this.authorize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.accessTokenExpired()) {
|
||||||
|
// only access token expired, so only refreshing it
|
||||||
|
let refreshedOk = true;
|
||||||
|
try {
|
||||||
|
await this.refreshAcessToken();
|
||||||
|
} catch (Err) {
|
||||||
|
refreshedOk = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!refreshedOk) {
|
||||||
|
// something went wrong, lets try to login regularly
|
||||||
|
return this.authorize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshAcessToken() {
|
||||||
|
let response = await this._api.post('/auth?type=refresh_token', {
|
||||||
|
body: { refresh_token: this.refresh_token },
|
||||||
|
headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
let json = response.body;
|
||||||
|
if (typeof json === 'undefined') {
|
||||||
|
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json && json.error) {
|
||||||
|
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json.access_token || !json.refresh_token) {
|
||||||
|
throw new Error('API unexpected response: ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh_token = json.refresh_token;
|
||||||
|
this.access_token = json.access_token;
|
||||||
|
this._refresh_token_created_ts = +new Date();
|
||||||
|
this._access_token_created_ts = +new Date();
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchBtcAddress() {
|
||||||
|
let response = await this._api.get('/getbtc', {
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer' + ' ' + this.access_token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let json = response.body;
|
||||||
|
if (typeof json === 'undefined') {
|
||||||
|
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json && json.error) {
|
||||||
|
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refill_addressess = [];
|
||||||
|
|
||||||
|
for (let arr of json) {
|
||||||
|
this.refill_addressess.push(arr.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
|
}
|
||||||
|
|
||||||
getTransactions() {
|
getTransactions() {
|
||||||
return [];
|
let txs = [];
|
||||||
|
this.pending_transactions_raw = this.pending_transactions_raw || [];
|
||||||
|
this.transactions_raw = this.transactions_raw || [];
|
||||||
|
txs = txs.concat(this.pending_transactions_raw, this.transactions_raw.slice().reverse()); // slice so array is cloned
|
||||||
|
// transforming to how wallets/list screen expects it
|
||||||
|
for (let tx of txs) {
|
||||||
|
tx.value = tx.amount * 100000000;
|
||||||
|
tx.received = new Date(tx.time * 1000).toString();
|
||||||
|
tx.memo = 'Refill';
|
||||||
|
}
|
||||||
|
return txs;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchPendingTransactions() {
|
||||||
|
let response = await this._api.get('/getpending', {
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer' + ' ' + this.access_token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let json = response.body;
|
||||||
|
if (typeof json === 'undefined') {
|
||||||
|
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json && json.error) {
|
||||||
|
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pending_transactions_raw = json;
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchTransactions() {
|
async fetchTransactions() {
|
||||||
return [];
|
// TODO: iterate over all available pages
|
||||||
}
|
const limit = 10;
|
||||||
|
let queryRes = '';
|
||||||
|
let offset = 0;
|
||||||
|
queryRes += '?limit=' + limit;
|
||||||
|
queryRes += '&offset=' + offset;
|
||||||
|
|
||||||
async getTransaction() {}
|
let response = await this._api.get('/gettxs' + queryRes, {
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer' + ' ' + this.access_token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let json = response.body;
|
||||||
|
if (typeof json === 'undefined') {
|
||||||
|
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json && json.error) {
|
||||||
|
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof json.btc_txs === 'undefined' || typeof json.paid_invoices === 'undefined' || typeof json.sended_coins === 'undefined') {
|
||||||
|
throw new Error('API unexpected response: ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.transactions_raw = [].concat(json.btc_txs || [], json.paid_invoices || [], json.sended_coins || []);
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
|
}
|
||||||
|
|
||||||
getBalance() {
|
getBalance() {
|
||||||
return 0;
|
return new BigNumber(this.balance).div(100000000).toString(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getInfo() {}
|
async fetchBalance() {
|
||||||
|
await this.checkLogin();
|
||||||
|
|
||||||
|
let response = await this._api.get('/balance', {
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer' + ' ' + this.access_token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let json = response.body;
|
||||||
|
if (typeof json === 'undefined') {
|
||||||
|
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json && json.error) {
|
||||||
|
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json.BTC || typeof json.BTC.AvailableBalance === 'undefined') {
|
||||||
|
throw new Error('API unexpected response: ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.balance_raw = json;
|
||||||
|
this.balance = json.BTC.AvailableBalance;
|
||||||
|
this._lastBalanceFetch = +new Date();
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example return:
|
||||||
|
* { destination: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f',
|
||||||
|
* payment_hash: 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4',
|
||||||
|
* num_satoshisnum_satoshis: '100',
|
||||||
|
* timestamp: '1535116657',
|
||||||
|
* expiry: '3600',
|
||||||
|
* description: 'hundredSatoshis blitzhub',
|
||||||
|
* description_hash: '',
|
||||||
|
* fallback_addr: '',
|
||||||
|
* cltv_expiry: '10',
|
||||||
|
* route_hints: [] }
|
||||||
|
*
|
||||||
|
* @param invoice BOLT invoice string
|
||||||
|
* @return {Promise.<Object>}
|
||||||
|
*/
|
||||||
|
async decodeInvoice(invoice) {
|
||||||
|
await this.checkLogin();
|
||||||
|
|
||||||
|
let response = await this._api.get('/decodeinvoice?invoice=' + invoice, {
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer' + ' ' + this.access_token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let json = response.body;
|
||||||
|
if (typeof json === 'undefined') {
|
||||||
|
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json && json.error) {
|
||||||
|
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json.payment_hash) {
|
||||||
|
throw new Error('API unexpected response: ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
|
|
||||||
|
return (this.decoded_invoice_raw = json);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchInfo() {
|
||||||
|
let response = await this._api.get('/getinfo', {
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer' + ' ' + this.access_token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let json = response.body;
|
||||||
|
if (typeof json === 'undefined') {
|
||||||
|
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json && json.error) {
|
||||||
|
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json.identity_pubkey) {
|
||||||
|
throw new Error('API unexpected response: ' + JSON.stringify(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.info_raw = json;
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
allowReceive() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pending tx:
|
||||||
|
|
||||||
|
[ { amount: 0.00078061,
|
||||||
|
account: '521172',
|
||||||
|
address: '3F9seBGCJZQ4WJJHwGhrxeGXCGbrm5SNpF',
|
||||||
|
category: 'receive',
|
||||||
|
confirmations: 0,
|
||||||
|
blockhash: '',
|
||||||
|
blockindex: 0,
|
||||||
|
blocktime: 0,
|
||||||
|
txid: '28a74277e47c2d772ee8a40464209c90dce084f3b5de38a2f41b14c79e3bfc62',
|
||||||
|
walletconflicts: [],
|
||||||
|
time: 1535024434,
|
||||||
|
timereceived: 1535024434 } ]
|
||||||
|
|
||||||
|
|
||||||
|
tx:
|
||||||
|
|
||||||
|
[ { amount: 0.00078061,
|
||||||
|
account: '521172',
|
||||||
|
address: '3F9seBGCJZQ4WJJHwGhrxeGXCGbrm5SNpF',
|
||||||
|
category: 'receive',
|
||||||
|
confirmations: 5,
|
||||||
|
blockhash: '0000000000000000000edf18e9ece18e449c6d8eed1f729946b3531c32ee9f57',
|
||||||
|
blockindex: 693,
|
||||||
|
blocktime: 1535024914,
|
||||||
|
txid: '28a74277e47c2d772ee8a40464209c90dce084f3b5de38a2f41b14c79e3bfc62',
|
||||||
|
walletconflicts: [],
|
||||||
|
time: 1535024434,
|
||||||
|
timereceived: 1535024434 } ]
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
22
currency.js
22
currency.js
|
@ -1,6 +1,7 @@
|
||||||
import Frisbee from 'frisbee';
|
import Frisbee from 'frisbee';
|
||||||
import { AsyncStorage } from 'react-native';
|
import { AsyncStorage } from 'react-native';
|
||||||
import { AppStorage } from './class';
|
import { AppStorage } from './class';
|
||||||
|
let BigNumber = require('bignumber.js');
|
||||||
|
|
||||||
let lang = {};
|
let lang = {};
|
||||||
// let btcusd = 6500; // default
|
// let btcusd = 6500; // default
|
||||||
|
@ -52,6 +53,27 @@ async function startUpdater() {
|
||||||
return updateExchangeRate();
|
return updateExchangeRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function satoshiToLocalCurrency(satoshi) {
|
||||||
|
if (!lang[STRUCT.BTC_USD]) return satoshi;
|
||||||
|
|
||||||
|
let b = new BigNumber(satoshi);
|
||||||
|
b = b
|
||||||
|
.div(100000000)
|
||||||
|
.mul(lang[STRUCT.BTC_USD])
|
||||||
|
.toString(10);
|
||||||
|
b = parseFloat(b).toFixed(2);
|
||||||
|
|
||||||
|
return '$' + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function satoshiToBTC(satoshi) {
|
||||||
|
let b = new BigNumber(satoshi);
|
||||||
|
b = b.div(100000000);
|
||||||
|
return b.toString(10) + ' BTC';
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.updateExchangeRate = updateExchangeRate;
|
module.exports.updateExchangeRate = updateExchangeRate;
|
||||||
module.exports.startUpdater = startUpdater;
|
module.exports.startUpdater = startUpdater;
|
||||||
module.exports.STRUCT = STRUCT;
|
module.exports.STRUCT = STRUCT;
|
||||||
|
module.exports.satoshiToLocalCurrency = satoshiToLocalCurrency;
|
||||||
|
module.exports.satoshiToBTC = satoshiToBTC;
|
||||||
|
|
51
package-lock.json
generated
51
package-lock.json
generated
|
@ -10304,6 +10304,52 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-0.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-0.17.1.tgz",
|
||||||
"integrity": "sha1-qyI2NB/ZhNrIhkICrlUzG8Ji9gw="
|
"integrity": "sha1-qyI2NB/ZhNrIhkICrlUzG8Ji9gw="
|
||||||
},
|
},
|
||||||
|
"react-native-material-buttons": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-material-buttons/-/react-native-material-buttons-0.5.0.tgz",
|
||||||
|
"integrity": "sha1-qys+P8P1AMpxP1Hp11l4r/YCFSo=",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "15.6.1",
|
||||||
|
"react-native-material-ripple": "0.7.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react-native-material-ripple": {
|
||||||
|
"version": "0.7.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-material-ripple/-/react-native-material-ripple-0.7.5.tgz",
|
||||||
|
"integrity": "sha1-4q9REGgFMvFK6jw6Q4JHvi/+9lk=",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "15.6.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-native-material-dropdown": {
|
||||||
|
"version": "0.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-material-dropdown/-/react-native-material-dropdown-0.11.1.tgz",
|
||||||
|
"integrity": "sha1-wP5DSo5heUHvkQukTS8HyPN1hP4=",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "15.6.1",
|
||||||
|
"react-native-material-buttons": "0.5.0",
|
||||||
|
"react-native-material-ripple": "0.8.0",
|
||||||
|
"react-native-material-textfield": "0.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-native-material-ripple": {
|
||||||
|
"version": "0.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-material-ripple/-/react-native-material-ripple-0.8.0.tgz",
|
||||||
|
"integrity": "sha1-uMJOb96iryoh6EaLH0CzVIMBni8=",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "15.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-native-material-textfield": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-material-textfield/-/react-native-material-textfield-0.12.0.tgz",
|
||||||
|
"integrity": "sha1-P7oZ12q4n2cFLIHgghUvwkPYKj8=",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "15.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-native-qrcode": {
|
"react-native-qrcode": {
|
||||||
"version": "0.2.6",
|
"version": "0.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-qrcode/-/react-native-qrcode-0.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-qrcode/-/react-native-qrcode-0.2.6.tgz",
|
||||||
|
@ -10437,11 +10483,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-native-simple-radio-button": {
|
|
||||||
"version": "2.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-native-simple-radio-button/-/react-native-simple-radio-button-2.7.2.tgz",
|
|
||||||
"integrity": "sha512-BdlllHsC/gYJtxPJ2tshDWN8CzmlGg1G9uB+Lu4FRGvGkwhvMtJ/uNShMbvxu134xosH/feri6HQgLGlIT202Q=="
|
|
||||||
},
|
|
||||||
"react-native-snap-carousel": {
|
"react-native-snap-carousel": {
|
||||||
"version": "3.7.2",
|
"version": "3.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-snap-carousel/-/react-native-snap-carousel-3.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-snap-carousel/-/react-native-snap-carousel-3.7.2.tgz",
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
"react-native-elements": "^0.18.5",
|
"react-native-elements": "^0.18.5",
|
||||||
"react-native-flexi-radio-button": "^0.2.2",
|
"react-native-flexi-radio-button": "^0.2.2",
|
||||||
"react-native-level-fs": "^3.0.0",
|
"react-native-level-fs": "^3.0.0",
|
||||||
|
"react-native-material-dropdown": "^0.11.1",
|
||||||
"react-native-qrcode": "^0.2.6",
|
"react-native-qrcode": "^0.2.6",
|
||||||
"react-native-snap-carousel": "^3.7.2",
|
"react-native-snap-carousel": "^3.7.2",
|
||||||
"react-navigation": "^1.0.0-beta.23",
|
"react-navigation": "^1.0.0-beta.23",
|
||||||
|
|
152
screen/lnd/manageFunds.js
Normal file
152
screen/lnd/manageFunds.js
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
/* global alert */
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { TouchableOpacity, View } from 'react-native';
|
||||||
|
import { Dropdown } from 'react-native-material-dropdown';
|
||||||
|
import { BlueSpacingVariable, BlueLoading, SafeBlueArea, BlueCard, BlueHeaderDefaultSub } from '../../BlueComponents';
|
||||||
|
import { ListItem } from 'react-native-elements';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
|
||||||
|
/** @type {AppStorage} */
|
||||||
|
let BlueApp = require('../../BlueApp');
|
||||||
|
|
||||||
|
let data = [];
|
||||||
|
|
||||||
|
export default class ManageFunds extends Component {
|
||||||
|
static navigationOptions = {
|
||||||
|
tabBarVisible: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
let fromSecret;
|
||||||
|
if (props.navigation.state.params.fromSecret) fromSecret = props.navigation.state.params.fromSecret;
|
||||||
|
let fromWallet = false;
|
||||||
|
|
||||||
|
for (let w of BlueApp.getWallets()) {
|
||||||
|
if (w.getSecret() === fromSecret) {
|
||||||
|
fromWallet = w;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromWallet) {
|
||||||
|
console.log(fromWallet.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
fromWallet,
|
||||||
|
fromSecret,
|
||||||
|
isLoading: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
data = [];
|
||||||
|
for (let c = 0; c < BlueApp.getWallets().length; c++) {
|
||||||
|
let w = BlueApp.getWallets()[c];
|
||||||
|
if (w.type !== new LightningCustodianWallet().type) {
|
||||||
|
data.push({
|
||||||
|
value: c,
|
||||||
|
label: w.getLabel() + ' (' + w.getBalance() + ' BTC)',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.isLoading) {
|
||||||
|
return <BlueLoading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
|
||||||
|
<BlueSpacingVariable />
|
||||||
|
<BlueHeaderDefaultSub leftText={'manage funds'} onClose={() => this.props.navigation.goBack()} />
|
||||||
|
|
||||||
|
<BlueCard>
|
||||||
|
{(() => {
|
||||||
|
if (this.state.isRefill) {
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Dropdown
|
||||||
|
label="Choose a source wallet"
|
||||||
|
data={data}
|
||||||
|
onChangeText={async value => {
|
||||||
|
/** @type {LightningCustodianWallet} */
|
||||||
|
let fromWallet = this.state.fromWallet;
|
||||||
|
let toAddress = false;
|
||||||
|
if (fromWallet.refill_addressess.length > 0) {
|
||||||
|
toAddress = fromWallet.refill_addressess[0];
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await fromWallet.fetchBtcAddress();
|
||||||
|
toAddress = fromWallet.refill_addressess[0];
|
||||||
|
} catch (Err) {
|
||||||
|
return alert(Err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let wallet = BlueApp.getWallets()[value];
|
||||||
|
if (wallet) {
|
||||||
|
console.log(wallet.getSecret());
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log({ toAddress });
|
||||||
|
this.props.navigation.navigate('SendDetails', {
|
||||||
|
memo: 'Refill Lightning wallet balance',
|
||||||
|
fromSecret: wallet.getSecret(),
|
||||||
|
address: toAddress,
|
||||||
|
});
|
||||||
|
}, 750);
|
||||||
|
} else {
|
||||||
|
return alert('Internal error');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<ListItem
|
||||||
|
titleStyle={{ color: BlueApp.settings.foregroundColor }}
|
||||||
|
component={TouchableOpacity}
|
||||||
|
onPress={a => {
|
||||||
|
this.setState({ isRefill: true });
|
||||||
|
}}
|
||||||
|
title={'Refill'}
|
||||||
|
/>
|
||||||
|
<ListItem
|
||||||
|
titleStyle={{ color: BlueApp.settings.foregroundColor }}
|
||||||
|
component={TouchableOpacity}
|
||||||
|
onPress={a => {
|
||||||
|
alert('Coming soon');
|
||||||
|
}}
|
||||||
|
title={'Withdraw'}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
|
||||||
|
<View />
|
||||||
|
</BlueCard>
|
||||||
|
</SafeBlueArea>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ManageFunds.propTypes = {
|
||||||
|
navigation: PropTypes.shape({
|
||||||
|
goBack: PropTypes.function,
|
||||||
|
navigate: PropTypes.function,
|
||||||
|
state: PropTypes.shape({
|
||||||
|
params: PropTypes.shape({
|
||||||
|
fromSecret: PropTypes.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
};
|
240
screen/lnd/scanLndInvoice.js
Normal file
240
screen/lnd/scanLndInvoice.js
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
/* global alert */
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, Dimensions, ActivityIndicator, Button, View, TouchableOpacity } from 'react-native';
|
||||||
|
import { Camera, Permissions } from 'expo';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
BlueSpacingVariable,
|
||||||
|
BlueFormInput,
|
||||||
|
BlueSpacing20,
|
||||||
|
BlueButton,
|
||||||
|
SafeBlueArea,
|
||||||
|
BlueCard,
|
||||||
|
BlueHeaderDefaultSub,
|
||||||
|
} from '../../BlueComponents';
|
||||||
|
/** @type {AppStorage} */
|
||||||
|
let BlueApp = require('../../BlueApp');
|
||||||
|
let currency = require('../../currency');
|
||||||
|
const { width } = Dimensions.get('window');
|
||||||
|
|
||||||
|
export default class ScanLndInvoice extends React.Component {
|
||||||
|
static navigationOptions = {
|
||||||
|
tabBarVisible: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
isLoading: false,
|
||||||
|
hasCameraPermission: null,
|
||||||
|
type: Camera.Constants.Type.back,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
let fromSecret;
|
||||||
|
if (props.navigation.state.params.fromSecret) fromSecret = props.navigation.state.params.fromSecret;
|
||||||
|
let fromWallet = {};
|
||||||
|
|
||||||
|
for (let w of BlueApp.getWallets()) {
|
||||||
|
if (w.getSecret() === fromSecret) {
|
||||||
|
fromWallet = w;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
fromWallet,
|
||||||
|
fromSecret,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async onBarCodeRead(ret) {
|
||||||
|
if (this.ignoreRead) return;
|
||||||
|
this.ignoreRead = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.ignoreRead = false;
|
||||||
|
}, 6000);
|
||||||
|
|
||||||
|
if (!this.state.fromWallet) {
|
||||||
|
alert('Error: cant find source wallet (this should never happen)');
|
||||||
|
return this.props.navigation.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.data = ret.data.replace('LIGHTNING:', '');
|
||||||
|
console.log(ret.data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {LightningCustodianWallet}
|
||||||
|
*/
|
||||||
|
let w = this.state.fromWallet;
|
||||||
|
let decoded = false;
|
||||||
|
try {
|
||||||
|
decoded = await w.decodeInvoice(ret.data);
|
||||||
|
|
||||||
|
let expiresIn = (decoded.timestamp * 1 + decoded.expiry * 1) * 1000; // ms
|
||||||
|
if (+new Date() > expiresIn) {
|
||||||
|
expiresIn = 'expired';
|
||||||
|
} else {
|
||||||
|
expiresIn = Math.round((expiresIn - +new Date()) / (60 * 1000)) + ' min';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isPaying: true,
|
||||||
|
invoice: ret.data,
|
||||||
|
decoded,
|
||||||
|
expiresIn,
|
||||||
|
});
|
||||||
|
} catch (Err) {
|
||||||
|
alert(Err.message);
|
||||||
|
}
|
||||||
|
} // end
|
||||||
|
|
||||||
|
async componentWillMount() {
|
||||||
|
const { status } = await Permissions.askAsync(Permissions.CAMERA);
|
||||||
|
this.setState({
|
||||||
|
hasCameraPermission: status === 'granted',
|
||||||
|
onCameraReady: function() {
|
||||||
|
alert('onCameraReady');
|
||||||
|
},
|
||||||
|
barCodeTypes: [Camera.Constants.BarCodeType.qr],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async pay() {
|
||||||
|
let decoded = this.state.decoded;
|
||||||
|
|
||||||
|
/** @type {LightningCustodianWallet} */
|
||||||
|
let fromWallet = this.state.fromWallet;
|
||||||
|
|
||||||
|
let expiresIn = (decoded.timestamp * 1 + decoded.expiry * 1) * 1000; // ms
|
||||||
|
if (+new Date() > expiresIn) {
|
||||||
|
return alert('Invoice expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isPayingInProgress: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let start = +new Date();
|
||||||
|
let end;
|
||||||
|
try {
|
||||||
|
await fromWallet.payInvoice(this.state.invoice);
|
||||||
|
end = +new Date();
|
||||||
|
} catch (Err) {
|
||||||
|
console.log(Err.message);
|
||||||
|
return alert('Error');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('payInvoice took', (end - start) / 1000, 'sec');
|
||||||
|
|
||||||
|
alert('Success');
|
||||||
|
this.props.navigation.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.isLoading) {
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, paddingTop: 20 }}>
|
||||||
|
<ActivityIndicator />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.isPaying) {
|
||||||
|
return (
|
||||||
|
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
|
||||||
|
<BlueSpacingVariable />
|
||||||
|
<BlueHeaderDefaultSub leftText={'Pay invoice'} onClose={() => this.props.navigation.goBack()} />
|
||||||
|
<BlueSpacing20 />
|
||||||
|
|
||||||
|
<Text style={{ textAlign: 'center', fontSize: 50, fontWeight: '700', color: '#2f5fb3' }}>
|
||||||
|
{currency.satoshiToLocalCurrency(this.state.decoded.num_satoshis)}
|
||||||
|
</Text>
|
||||||
|
<Text style={{ textAlign: 'center', fontSize: 25, fontWeight: '600', color: '#d4d4d4' }}>
|
||||||
|
{currency.satoshiToBTC(this.state.decoded.num_satoshis)}
|
||||||
|
</Text>
|
||||||
|
<BlueSpacing20 />
|
||||||
|
|
||||||
|
<BlueCard>
|
||||||
|
<BlueFormInput value={this.state.decoded.destination} />
|
||||||
|
<BlueFormInput value={this.state.decoded.description} />
|
||||||
|
<Text style={{ color: '#81868e', fontSize: 12, left: 20, top: 10 }}>Expires in: {this.state.expiresIn}</Text>
|
||||||
|
</BlueCard>
|
||||||
|
|
||||||
|
<BlueSpacing20 />
|
||||||
|
|
||||||
|
{(() => {
|
||||||
|
if (this.state.isPayingInProgress) {
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<ActivityIndicator />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<BlueButton
|
||||||
|
icon={{
|
||||||
|
name: 'bolt',
|
||||||
|
type: 'font-awesome',
|
||||||
|
color: BlueApp.settings.buttonTextColor,
|
||||||
|
}}
|
||||||
|
title={'Pay'}
|
||||||
|
buttonStyle={{ width: 150, left: (width - 150) / 2 - 20 }}
|
||||||
|
onPress={() => {
|
||||||
|
this.pay();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</SafeBlueArea>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hasCameraPermission } = this.state;
|
||||||
|
if (hasCameraPermission === null) {
|
||||||
|
return <View />;
|
||||||
|
} else if (hasCameraPermission === false) {
|
||||||
|
return <Text>No access to camera</Text>;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Camera style={{ flex: 1 }} type={this.state.type} onBarCodeRead={ret => this.onBarCodeRead(ret)}>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
flexDirection: 'row',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={{
|
||||||
|
flex: 0.2,
|
||||||
|
alignSelf: 'flex-end',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
onPress={() => {
|
||||||
|
this.setState({
|
||||||
|
type: this.state.type === Camera.Constants.Type.back ? Camera.Constants.Type.front : Camera.Constants.Type.back,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button style={{ fontSize: 18, marginBottom: 10 }} title="Go back" onPress={() => this.props.navigation.goBack()} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</Camera>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScanLndInvoice.propTypes = {
|
||||||
|
navigation: PropTypes.shape({
|
||||||
|
goBack: PropTypes.function,
|
||||||
|
state: PropTypes.shape({
|
||||||
|
params: PropTypes.shape({
|
||||||
|
fromSecret: PropTypes.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
};
|
|
@ -29,9 +29,12 @@ export default class SendDetails extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
console.log('props.navigation.state.params=', props.navigation.state.params);
|
||||||
let startTime = Date.now();
|
let startTime = Date.now();
|
||||||
let address;
|
let address;
|
||||||
if (props.navigation.state.params) address = props.navigation.state.params.address;
|
if (props.navigation.state.params) address = props.navigation.state.params.address;
|
||||||
|
let memo = false;
|
||||||
|
if (props.navigation.state.params) memo = props.navigation.state.params.memo;
|
||||||
let fromAddress;
|
let fromAddress;
|
||||||
if (props.navigation.state.params) fromAddress = props.navigation.state.params.fromAddress;
|
if (props.navigation.state.params) fromAddress = props.navigation.state.params.fromAddress;
|
||||||
let fromSecret;
|
let fromSecret;
|
||||||
|
@ -52,6 +55,7 @@ export default class SendDetails extends Component {
|
||||||
|
|
||||||
let endTime2 = Date.now();
|
let endTime2 = Date.now();
|
||||||
console.log('getAddress() took', (endTime2 - startTime2) / 1000, 'sec');
|
console.log('getAddress() took', (endTime2 - startTime2) / 1000, 'sec');
|
||||||
|
console.log({ memo });
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
errorMessage: false,
|
errorMessage: false,
|
||||||
|
@ -61,6 +65,7 @@ export default class SendDetails extends Component {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
address: address,
|
address: address,
|
||||||
amount: '',
|
amount: '',
|
||||||
|
memo,
|
||||||
fee: '',
|
fee: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -252,6 +257,7 @@ SendDetails.propTypes = {
|
||||||
address: PropTypes.string,
|
address: PropTypes.string,
|
||||||
fromAddress: PropTypes.string,
|
fromAddress: PropTypes.string,
|
||||||
fromSecret: PropTypes.string,
|
fromSecret: PropTypes.string,
|
||||||
|
memo: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -17,6 +17,9 @@ import sendDetails from './send/details';
|
||||||
import sendScanQrAddress from './send/scanQrAddress';
|
import sendScanQrAddress from './send/scanQrAddress';
|
||||||
import sendCreate from './send/create';
|
import sendCreate from './send/create';
|
||||||
|
|
||||||
|
import ManageFunds from './lnd/manageFunds';
|
||||||
|
import ScanLndInvoice from './lnd/scanLndInvoice';
|
||||||
|
|
||||||
const WalletsNavigator = StackNavigator(
|
const WalletsNavigator = StackNavigator(
|
||||||
{
|
{
|
||||||
WalletsList: {
|
WalletsList: {
|
||||||
|
@ -67,6 +70,15 @@ const WalletsNavigator = StackNavigator(
|
||||||
CreateTransaction: {
|
CreateTransaction: {
|
||||||
screen: sendCreate,
|
screen: sendCreate,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// LND:
|
||||||
|
|
||||||
|
ManageFunds: {
|
||||||
|
screen: ManageFunds,
|
||||||
|
},
|
||||||
|
ScanLndInvoice: {
|
||||||
|
screen: ScanLndInvoice,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerMode: 'none',
|
headerMode: 'none',
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { RadioGroup, RadioButton } from 'react-native-flexi-radio-button';
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { HDSegwitP2SHWallet } from '../../class/hd-segwit-p2sh-wallet';
|
import { HDSegwitP2SHWallet } from '../../class/hd-segwit-p2sh-wallet';
|
||||||
|
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
|
||||||
let EV = require('../../events');
|
let EV = require('../../events');
|
||||||
let A = require('../../analytics');
|
let A = require('../../analytics');
|
||||||
/** @type {AppStorage} */
|
/** @type {AppStorage} */
|
||||||
|
@ -160,14 +161,28 @@ export default class WalletsAdd extends Component {
|
||||||
width: width / 1.5,
|
width: width / 1.5,
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (this.state.activeLightning) {
|
|
||||||
return alert(loc.wallets.add.coming_soon);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.navigation.goBack();
|
this.props.navigation.goBack();
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
let w;
|
let w;
|
||||||
if (this.state.selectedIndex === 1) {
|
|
||||||
|
if (this.state.activeLightning) {
|
||||||
|
// lightning was selected
|
||||||
|
|
||||||
|
return alert('Coming soon');
|
||||||
|
// eslint-disable-next-line
|
||||||
|
for (let t of BlueApp.getWallets()) {
|
||||||
|
if (t.type === new LightningCustodianWallet().type) {
|
||||||
|
// already exist
|
||||||
|
return alert('Only 1 Ligthning wallet allowed for now');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w = new LightningCustodianWallet();
|
||||||
|
w.setLabel(this.state.label || w.getTypeReadable());
|
||||||
|
await w.createAccount();
|
||||||
|
await w.authorize();
|
||||||
|
} else if (this.state.selectedIndex === 1) {
|
||||||
|
// btc was selected
|
||||||
// index 1 radio - segwit single address
|
// index 1 radio - segwit single address
|
||||||
w = new SegwitP2SHWallet();
|
w = new SegwitP2SHWallet();
|
||||||
w.setLabel(this.state.label || loc.wallets.add.label_new_segwit);
|
w.setLabel(this.state.label || loc.wallets.add.label_new_segwit);
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
BlueHeaderDefaultSub,
|
BlueHeaderDefaultSub,
|
||||||
} from '../../BlueComponents';
|
} from '../../BlueComponents';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
|
||||||
let EV = require('../../events');
|
let EV = require('../../events');
|
||||||
let A = require('../../analytics');
|
let A = require('../../analytics');
|
||||||
/** @type {AppStorage} */
|
/** @type {AppStorage} */
|
||||||
|
@ -60,6 +61,26 @@ export default class WalletsImport extends Component {
|
||||||
|
|
||||||
async importMnemonic(text) {
|
async importMnemonic(text) {
|
||||||
try {
|
try {
|
||||||
|
// is it lightning custodian?
|
||||||
|
if (text.indexOf('blitzhub://') !== -1) {
|
||||||
|
// yep its lnd
|
||||||
|
for (let t of BlueApp.getWallets()) {
|
||||||
|
if (t.type === new LightningCustodianWallet().type) {
|
||||||
|
// already exist
|
||||||
|
return alert('Only 1 Ligthning wallet allowed for now');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let lnd = new LightningCustodianWallet();
|
||||||
|
lnd.setSecret(text);
|
||||||
|
await lnd.authorize();
|
||||||
|
await lnd.fetchTransactions();
|
||||||
|
await lnd.fetchBalance();
|
||||||
|
return this._saveWallet(lnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// trying other wallet types
|
||||||
|
|
||||||
let segwitWallet = new SegwitP2SHWallet();
|
let segwitWallet = new SegwitP2SHWallet();
|
||||||
segwitWallet.setSecret(text);
|
segwitWallet.setSecret(text);
|
||||||
if (segwitWallet.getAddress()) {
|
if (segwitWallet.getAddress()) {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { View, Dimensions, Text, ListView } from 'react-native';
|
import { View, TouchableOpacity, Dimensions, Text, ListView } from 'react-native';
|
||||||
import {
|
import {
|
||||||
|
BlueText,
|
||||||
|
BlueTransactionOnchainIcon,
|
||||||
|
ManageFundsBigButton,
|
||||||
BlueLoading,
|
BlueLoading,
|
||||||
SafeBlueArea,
|
SafeBlueArea,
|
||||||
WalletsCarousel,
|
WalletsCarousel,
|
||||||
|
@ -15,7 +18,9 @@ import {
|
||||||
BlueHeaderDefaultMain,
|
BlueHeaderDefaultMain,
|
||||||
is,
|
is,
|
||||||
} from '../../BlueComponents';
|
} from '../../BlueComponents';
|
||||||
|
import { Icon } from 'react-native-elements';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
|
||||||
const BigNumber = require('bignumber.js');
|
const BigNumber = require('bignumber.js');
|
||||||
let EV = require('../../events');
|
let EV = require('../../events');
|
||||||
let A = require('../../analytics');
|
let A = require('../../analytics');
|
||||||
|
@ -83,21 +88,38 @@ export default class WalletsList extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
console.log('refreshFunction()');
|
||||||
let showSend = false;
|
let showSend = false;
|
||||||
let showReceive = false;
|
let showReceive = false;
|
||||||
|
let showManageFundsBig = false;
|
||||||
|
let showManageFundsSmallButton = false;
|
||||||
let wallets = BlueApp.getWallets();
|
let wallets = BlueApp.getWallets();
|
||||||
let wallet = wallets[this.lastSnappedTo || 0];
|
let wallet = wallets[this.lastSnappedTo || 0];
|
||||||
if (wallet) {
|
if (wallet) {
|
||||||
showSend = wallet.allowSend();
|
showSend = wallet.allowSend();
|
||||||
showReceive = wallet.allowReceive();
|
showReceive = wallet.allowReceive();
|
||||||
}
|
}
|
||||||
|
let showRereshButton = (BlueApp.getWallets().length > 0 && true) || false;
|
||||||
|
|
||||||
|
if (wallet && wallet.type === new LightningCustodianWallet().type && !showSend) {
|
||||||
|
showManageFundsBig = true;
|
||||||
|
showManageFundsSmallButton = false;
|
||||||
|
showRereshButton = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wallet && wallet.type === new LightningCustodianWallet().type && wallet.getBalance() > 0) {
|
||||||
|
showRereshButton = false;
|
||||||
|
showManageFundsSmallButton = true;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isTransactionsLoading: false,
|
isTransactionsLoading: false,
|
||||||
showReceiveButton: showReceive,
|
showReceiveButton: showReceive,
|
||||||
showSendButton: showSend,
|
showSendButton: showSend,
|
||||||
showRereshButton: (BlueApp.getWallets().length > 0 && true) || false,
|
showManageFundsBigButton: showManageFundsBig,
|
||||||
|
showManageFundsSmallButton,
|
||||||
|
showRereshButton,
|
||||||
dataSource: ds.cloneWithRows(BlueApp.getTransactions(this.lastSnappedTo || 0)),
|
dataSource: ds.cloneWithRows(BlueApp.getTransactions(this.lastSnappedTo || 0)),
|
||||||
});
|
});
|
||||||
}, 1);
|
}, 1);
|
||||||
|
@ -130,6 +152,8 @@ export default class WalletsList extends Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
showReceiveButton: false,
|
showReceiveButton: false,
|
||||||
|
showManageFundsBigButton: false,
|
||||||
|
showManageFundsSmallButton: false,
|
||||||
showSendButton: false,
|
showSendButton: false,
|
||||||
showRereshButton: false,
|
showRereshButton: false,
|
||||||
// TODO: погуглить че это за ебала ds.cloneWithRows, можно ли быстрее сделать прогрузку транзакций на экран
|
// TODO: погуглить че это за ебала ds.cloneWithRows, можно ли быстрее сделать прогрузку транзакций на экран
|
||||||
|
@ -141,19 +165,38 @@ export default class WalletsList extends Component {
|
||||||
|
|
||||||
let showSend = false;
|
let showSend = false;
|
||||||
let showReceive = false;
|
let showReceive = false;
|
||||||
|
let showManageFundsBig = false;
|
||||||
let wallets = BlueApp.getWallets();
|
let wallets = BlueApp.getWallets();
|
||||||
let wallet = wallets[this.lastSnappedTo || 0];
|
let wallet = wallets[this.lastSnappedTo || 0];
|
||||||
if (wallet) {
|
if (wallet) {
|
||||||
showSend = wallet.allowSend();
|
showSend = wallet.allowSend();
|
||||||
showReceive = wallet.allowReceive();
|
showReceive = wallet.allowReceive();
|
||||||
}
|
}
|
||||||
|
console.log({ showSend });
|
||||||
|
let showRereshButton = true;
|
||||||
|
let showManageFundsSmallButton = true;
|
||||||
|
if (wallet && wallet.type === new LightningCustodianWallet().type && !showSend) {
|
||||||
|
showManageFundsBig = true;
|
||||||
|
showRereshButton = false;
|
||||||
|
showManageFundsSmallButton = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wallet && wallet.type === new LightningCustodianWallet().type) {
|
||||||
|
showRereshButton = false;
|
||||||
|
} else {
|
||||||
|
showManageFundsSmallButton = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log({ showManageFundsBig });
|
||||||
|
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() =>
|
() =>
|
||||||
this.setState({
|
this.setState({
|
||||||
showReceiveButton: showReceive,
|
showReceiveButton: showReceive,
|
||||||
|
showManageFundsBigButton: showManageFundsBig,
|
||||||
|
showManageFundsSmallButton,
|
||||||
showSendButton: showSend,
|
showSendButton: showSend,
|
||||||
showRereshButton: true,
|
showRereshButton,
|
||||||
}),
|
}),
|
||||||
50,
|
50,
|
||||||
); // just to animate it, no real function
|
); // just to animate it, no real function
|
||||||
|
@ -163,6 +206,15 @@ export default class WalletsList extends Component {
|
||||||
this.lazyRefreshWallet(index);
|
this.lazyRefreshWallet(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLightning() {
|
||||||
|
let w = BlueApp.getWallets()[this.lastSnappedTo || 0];
|
||||||
|
if (w && w.type === new LightningCustodianWallet().type) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decides whether wallet with such index shoud be refreshed,
|
* Decides whether wallet with such index shoud be refreshed,
|
||||||
* refreshes if yes and redraws the screen
|
* refreshes if yes and redraws the screen
|
||||||
|
@ -190,8 +242,11 @@ export default class WalletsList extends Component {
|
||||||
this.refreshFunction();
|
this.refreshFunction();
|
||||||
didRefresh = true;
|
didRefresh = true;
|
||||||
} else if (wallets[index].timeToRefreshTransaction()) {
|
} else if (wallets[index].timeToRefreshTransaction()) {
|
||||||
console.log('got TXs with low confirmations, refreshing');
|
console.log(wallets[index].getLabel(), 'thinks its time to refresh TXs');
|
||||||
await wallets[index].fetchTransactions();
|
await wallets[index].fetchTransactions();
|
||||||
|
if (wallets[index].fetchPendingTransactions) {
|
||||||
|
await wallets[index].fetchPendingTransactions();
|
||||||
|
}
|
||||||
this.refreshFunction();
|
this.refreshFunction();
|
||||||
didRefresh = true;
|
didRefresh = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -229,6 +284,37 @@ export default class WalletsList extends Component {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{(() => {
|
||||||
|
if (this.state.showManageFundsSmallButton) {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={{ alignSelf: 'flex-end', right: 10, flexDirection: 'row' }}
|
||||||
|
onPress={() => {
|
||||||
|
let walletIndex = this.lastSnappedTo || 0;
|
||||||
|
|
||||||
|
let c = 0;
|
||||||
|
for (let w of BlueApp.getWallets()) {
|
||||||
|
if (c++ === walletIndex) {
|
||||||
|
console.log('navigating to secret ', w.getSecret());
|
||||||
|
navigate('ManageFunds', { fromSecret: w.getSecret() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BlueText style={{ fontWeight: '600', fontSize: 16 }}>Manage funds</BlueText>
|
||||||
|
<Icon
|
||||||
|
style={{ position: 'relative' }}
|
||||||
|
name="link"
|
||||||
|
type="font-awesome"
|
||||||
|
size={14}
|
||||||
|
color={BlueApp.settings.foregroundColor}
|
||||||
|
iconStyle={{ left: 5, transform: [{ rotate: '90deg' }] }}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
|
||||||
{(() => {
|
{(() => {
|
||||||
if (this.state.isTransactionsLoading) {
|
if (this.state.isTransactionsLoading) {
|
||||||
return <BlueLoading />;
|
return <BlueLoading />;
|
||||||
|
@ -272,7 +358,9 @@ export default class WalletsList extends Component {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{loc.wallets.list.empty_txs1}
|
{(this.isLightning() &&
|
||||||
|
'Lightning wallet should be used for your daily\ntransactions. Fees are unfairly cheap and\nspeed is blazing fast.') ||
|
||||||
|
loc.wallets.list.empty_txs1}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
|
@ -281,7 +369,8 @@ export default class WalletsList extends Component {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{loc.wallets.list.empty_txs2}
|
{(this.isLightning() && '\nTo start using it tap on "manage funds"\nand topup your balance') ||
|
||||||
|
loc.wallets.list.empty_txs2}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -301,6 +390,22 @@ export default class WalletsList extends Component {
|
||||||
return (
|
return (
|
||||||
<BlueListItem
|
<BlueListItem
|
||||||
avatar={(() => {
|
avatar={(() => {
|
||||||
|
if (rowData.category && rowData.category === 'receive') {
|
||||||
|
// is it lightning onchain tx?
|
||||||
|
if (rowData.confirmations < 3) {
|
||||||
|
return (
|
||||||
|
<View style={{ width: 25 }}>
|
||||||
|
<BlueTransactionPendingIcon />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<View style={{ width: 25 }}>
|
||||||
|
<BlueTransactionOnchainIcon />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!rowData.confirmations) {
|
if (!rowData.confirmations) {
|
||||||
return (
|
return (
|
||||||
<View style={{ width: 25 }}>
|
<View style={{ width: 25 }}>
|
||||||
|
@ -324,12 +429,15 @@ export default class WalletsList extends Component {
|
||||||
title={loc.transactionTimeToReadable(rowData.received)}
|
title={loc.transactionTimeToReadable(rowData.received)}
|
||||||
subtitle={
|
subtitle={
|
||||||
(rowData.confirmations < 7 ? loc.transactions.list.conf + ': ' + rowData.confirmations + ' ' : '') +
|
(rowData.confirmations < 7 ? loc.transactions.list.conf + ': ' + rowData.confirmations + ' ' : '') +
|
||||||
this.txMemo(rowData.hash)
|
this.txMemo(rowData.hash) +
|
||||||
|
(rowData.memo || '')
|
||||||
}
|
}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
navigate('TransactionDetails', {
|
if (rowData.hash) {
|
||||||
hash: rowData.hash,
|
navigate('TransactionDetails', {
|
||||||
});
|
hash: rowData.hash,
|
||||||
|
});
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
badge={{
|
badge={{
|
||||||
value: 3,
|
value: 3,
|
||||||
|
@ -395,7 +503,31 @@ export default class WalletsList extends Component {
|
||||||
let c = 0;
|
let c = 0;
|
||||||
for (let w of BlueApp.getWallets()) {
|
for (let w of BlueApp.getWallets()) {
|
||||||
if (c++ === walletIndex) {
|
if (c++ === walletIndex) {
|
||||||
navigate('SendDetails', { fromAddress: w.getAddress(), fromSecret: w.getSecret() });
|
if (w.type === new LightningCustodianWallet().type) {
|
||||||
|
navigate('ScanLndInvoice', { fromSecret: w.getSecret() });
|
||||||
|
} else {
|
||||||
|
navigate('SendDetails', { fromAddress: w.getAddress(), fromSecret: w.getSecret() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
|
||||||
|
{(() => {
|
||||||
|
if (this.state.showManageFundsBigButton) {
|
||||||
|
return (
|
||||||
|
<ManageFundsBigButton
|
||||||
|
onPress={() => {
|
||||||
|
let walletIndex = this.lastSnappedTo || 0;
|
||||||
|
|
||||||
|
let c = 0;
|
||||||
|
for (let w of BlueApp.getWallets()) {
|
||||||
|
if (c++ === walletIndex) {
|
||||||
|
console.log('navigating to secret ', w.getSecret());
|
||||||
|
navigate('ManageFunds', { fromSecret: w.getSecret() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
Loading…
Add table
Reference in a new issue