This commit is contained in:
Sergey Tsegelnyk 2018-03-17 22:39:21 +02:00 committed by Igor Korsakov
parent 490eaf332e
commit f1d68e53e6
28 changed files with 2209 additions and 1489 deletions

View File

@ -1,7 +1,16 @@
{
"parser": "babel-eslint",
"plugins": [
"react", "standard"
"react", "prettier"
],
"extends": ["standard", "standard-react"]
"extends": ["standard", "standard-react", "prettier"],
"rules": {
'prettier/prettier': [
'warn',
{
singleQuote: true,
trailingComma: 'all',
},
]
}
}

76
App.js
View File

@ -1,61 +1,71 @@
/** @type {AppStorage} */
import './shim.js'
import React from 'react'
import PropTypes from 'prop-types'
import { Text, ScrollView, StyleSheet } from 'react-native'
import { DrawerNavigator, SafeAreaView } from 'react-navigation'
import MainBottomTabs from './MainBottomTabs'
import './shim.js';
import React from 'react';
import PropTypes from 'prop-types';
import { Text, ScrollView, StyleSheet } from 'react-native';
import { DrawerNavigator, SafeAreaView } from 'react-navigation';
import MainBottomTabs from './MainBottomTabs';
require('./BlueApp')
require('./BlueApp');
if (!Error.captureStackTrace) { // captureStackTrace is only available when debugging
Error.captureStackTrace = () => {}
if (!Error.captureStackTrace) {
// captureStackTrace is only available when debugging
Error.captureStackTrace = () => {};
}
const pkg = require('./package.json')
const pkg = require('./package.json');
// <DrawerItems {...props} />
const CustomDrawerContentComponent = (props) => (
const CustomDrawerContentComponent = props => (
<ScrollView>
<SafeAreaView style={styles.container} forceInset={{ top: 'always', horizontal: 'never' }}>
<Text onPress={() => props.navigation.navigate('AddWallet')} style={styles.heading}> {pkg.name} v{pkg.version}</Text>
<SafeAreaView
style={styles.container}
forceInset={{ top: 'always', horizontal: 'never' }}
>
<Text
onPress={() => props.navigation.navigate('AddWallet')}
style={styles.heading}
>
{' '}
{pkg.name} v{pkg.version}
</Text>
</SafeAreaView>
</ScrollView>
)
);
CustomDrawerContentComponent.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func
})
}
navigate: PropTypes.func,
}),
};
const styles = StyleSheet.create({
container: {
marginTop: 20,
flex: 1
flex: 1,
},
heading: {
textAlign: 'center',
color: 'black',
fontWeight: 'bold',
fontSize: 20
}
})
fontSize: 20,
},
});
/* import scanQrWifLegacyAddress from './screen/wallets/scanQrWifLegacyAddress'
import scanQrWifSegwitP2SHAddress from './screen/wallets/scanQrWifSegwitP2SHAddress' */
const TabsInDrawer = DrawerNavigator({
const TabsInDrawer = DrawerNavigator(
{
MainBottomTabs: {
screen: MainBottomTabs,
navigationOptions: {
drawer: () => ({
label: 'Tabs'
})
}
}
label: 'Tabs',
}),
},
},
/* SecondaryBottomTabs: {
screen: SecondaryBottomTabs,
@ -73,13 +83,13 @@ const TabsInDrawer = DrawerNavigator({
}),
},
}, */
}, {
},
{
contentComponent: CustomDrawerContentComponent,
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle'
drawerToggleRoute: 'DrawerToggle',
},
);
})
export default TabsInDrawer
export default TabsInDrawer;

View File

@ -1,17 +1,17 @@
/* global describe, it */
import { LegacyWallet } from './class'
let assert = require('assert')
import { LegacyWallet } from './class';
let assert = require('assert');
describe('unit - LegacyWallet', function() {
it('serialize and unserialize work correctly', () => {
let a = new LegacyWallet()
a.setLabel('my1')
let key = JSON.stringify(a)
let a = new LegacyWallet();
a.setLabel('my1');
let key = JSON.stringify(a);
let b = LegacyWallet.fromJson(key)
assert(key === JSON.stringify(b))
let b = LegacyWallet.fromJson(key);
assert(key === JSON.stringify(b));
assert.equal(key, JSON.stringify(b))
})
})
assert.equal(key, JSON.stringify(b));
});
});

View File

@ -2,16 +2,15 @@
* @exports {AppStorage}
*/
import {AppStorage} from './class'
let EV = require('./events')
import { AppStorage } from './class';
let EV = require('./events');
let BlueApp = new AppStorage()
let BlueApp = new AppStorage();
(async () => {
await BlueApp.loadFromDisk();
console.log('loaded from disk');
EV(EV.enum.WALLETS_COUNT_CHANGED);
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
})();
;(async () => {
await BlueApp.loadFromDisk()
console.log('loaded from disk')
EV(EV.enum.WALLETS_COUNT_CHANGED)
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED)
})()
module.exports = BlueApp
module.exports = BlueApp;

View File

@ -1,21 +1,34 @@
/** @type {AppStorage} */
import React, { Component } from 'react'
import { SafeAreaView } from 'react-navigation'
import { Button, FormLabel, FormInput, Card, Text, Header, List, ListItem } from 'react-native-elements'
import { ActivityIndicator, ListView, View } from 'react-native'
let BlueApp = require('./BlueApp')
import React, { Component } from 'react';
import { SafeAreaView } from 'react-navigation';
import {
Button,
FormLabel,
FormInput,
Card,
Text,
Header,
List,
ListItem,
} from 'react-native-elements';
import { ActivityIndicator, ListView, View } from 'react-native';
let BlueApp = require('./BlueApp');
export class BlueButton extends Component {
render() {
return (
<Button
{...this.props}
style={{marginTop: 20, borderRadius: 6, borderWidth: 0.7, borderColor: '#ffffff'}}
style={{
marginTop: 20,
borderRadius: 6,
borderWidth: 0.7,
borderColor: '#ffffff',
}}
borderRadius={10}
backgroundColor={BlueApp.settings.buttonBackground}
/>
)
);
}
/* icon={{name: 'home', type: 'octicon'}} */
}
@ -25,9 +38,10 @@ export class SafeBlueArea extends Component {
return (
<SafeAreaView
{...this.props}
forceInset={{ horizontal: 'always' }} style={{flex: 1, backgroundColor: BlueApp.settings.brandingColor}}
forceInset={{ horizontal: 'always' }}
style={{ flex: 1, backgroundColor: BlueApp.settings.brandingColor }}
/>
)
);
}
}
@ -40,18 +54,13 @@ export class BlueCard extends Component {
containerStyle={{ backgroundColor: BlueApp.settings.buttonBackground }}
wrapperStyle={{ backgroundColor: BlueApp.settings.buttonBackground }}
/>
)
);
}
}
export class BlueText extends Component {
render() {
return (
<Text
{...this.props}
style={{color: 'white'}}
/>
)
return <Text {...this.props} style={{ color: 'white' }} />;
}
}
@ -64,29 +73,19 @@ export class BlueListItem extends Component {
titleStyle={{ color: 'white', fontSize: 18 }}
subtitleStyle={{ color: 'white' }}
/>
)
);
}
}
export class BlueFormLabel extends Component {
render() {
return (
<FormLabel
{...this.props}
labelStyle={{color: 'white'}}
/>
)
return <FormLabel {...this.props} labelStyle={{ color: 'white' }} />;
}
}
export class BlueFormInput extends Component {
render() {
return (
<FormInput
{...this.props}
inputStyle={{color: 'white'}}
/>
)
return <FormInput {...this.props} inputStyle={{ color: 'white' }} />;
}
}
@ -97,7 +96,7 @@ export class BlueHeader extends Component {
{...this.props}
backgroundColor={BlueApp.settings.brandingColor}
/>
)
);
}
}
@ -108,7 +107,7 @@ export class BlueSpacing extends Component {
{...this.props}
style={{ height: 60, backgroundColor: BlueApp.settings.brandingColor }}
/>
)
);
}
}
@ -119,17 +118,13 @@ export class BlueSpacing20 extends Component {
{...this.props}
style={{ height: 20, backgroundColor: BlueApp.settings.brandingColor }}
/>
)
);
}
}
export class BlueListView extends Component {
render() {
return (
<ListView
{...this.props}
/>
)
return <ListView {...this.props} />;
}
}
@ -140,7 +135,7 @@ export class BlueList extends Component {
{...this.props}
containerStyle={{ backgroundColor: BlueApp.settings.brandingColor }}
/>
)
);
}
}
@ -151,7 +146,7 @@ export class BlueView extends Component {
{...this.props}
containerStyle={{ backgroundColor: BlueApp.settings.brandingColor }}
/>
)
);
}
}
@ -163,6 +158,6 @@ export class BlueLoading extends Component {
<ActivityIndicator />
</View>
</SafeBlueArea>
)
);
}
}

View File

@ -1,39 +1,39 @@
import { TabNavigator } from 'react-navigation'
import { TabNavigator } from 'react-navigation';
import transactions from './screen/transactions'
import wallets from './screen/wallets'
import send from './screen/send'
import settins from './screen/settings'
import receive from './screen/receive'
import transactions from './screen/transactions';
import wallets from './screen/wallets';
import send from './screen/send';
import settins from './screen/settings';
import receive from './screen/receive';
/**
*
* @type {AppStorage}
*/
let BlueApp = require('./BlueApp')
let BlueApp = require('./BlueApp');
const Tabs = TabNavigator(
{
Wallets: {
screen: wallets,
path: 'wallets'
path: 'wallets',
},
Transactions: {
screen: transactions,
path: 'trans'
path: 'trans',
},
Send: {
screen: send,
path: 'cart'
path: 'cart',
},
Receive: {
screen: receive,
path: 'receive'
path: 'receive',
},
Settings: {
screen: settins,
path: 'settings'
}
path: 'settings',
},
},
{
tabBarPosition: 'bottom',
@ -42,9 +42,9 @@ const Tabs = TabNavigator(
activeTintColor: 'white',
activeBackgroundColor: '#33bdf1',
inactiveBackgroundColor: BlueApp.settings.brandingColor,
inactiveTintColor: 'white'
}
}
)
inactiveTintColor: 'white',
},
},
);
export default Tabs
export default Tabs;

View File

@ -2,12 +2,12 @@
* @flow
*/
import React from 'react'
import { Button, ScrollView } from 'react-native'
import { SafeAreaView, StackNavigator, TabNavigator } from 'react-navigation'
import React from 'react';
import { Button, ScrollView } from 'react-native';
import { SafeAreaView, StackNavigator, TabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons'
import SampleText from './SampleText'
import Ionicons from 'react-native-vector-icons/Ionicons';
import SampleText from './SampleText';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
@ -15,35 +15,35 @@ const MyNavScreen = ({ navigation, banner }) => (
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
title='Open profile screen'
title="Open profile screen"
/>
<Button
onPress={() => navigation.navigate('NotifSettings')}
title='Open notifications screen'
title="Open notifications screen"
/>
<Button
onPress={() => navigation.navigate('SettingsTab')}
title='Go to settings tab'
title="Go to settings tab"
/>
<Button onPress={() => navigation.goBack(null)} title='Go back' />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
</SafeAreaView>
</ScrollView>
)
);
const MyHomeScreen = ({ navigation }) => (
<MyNavScreen banner='Home Screen' navigation={navigation} />
)
<MyNavScreen banner="Home Screen" navigation={navigation} />
);
const MyNotificationsSettingsScreen = ({ navigation }) => (
<MyNavScreen banner='Notifications Screen' navigation={navigation} />
)
<MyNavScreen banner="Notifications Screen" navigation={navigation} />
);
const MySettingsScreen = ({ navigation }) => (
<MyNavScreen banner='Settings Screen' navigation={navigation} />
)
<MyNavScreen banner="Settings Screen" navigation={navigation} />
);
var bitcoin = require('bitcoinjs-lib')
var myString = bitcoin.ECPair.makeRandom().toWIF()
var bitcoin = require('bitcoinjs-lib');
var myString = bitcoin.ECPair.makeRandom().toWIF();
const TabNav = TabNavigator(
{
@ -59,8 +59,8 @@ const TabNav = TabNavigator(
size={26}
style={{ color: tintColor }}
/>
)
}
),
},
},
SettingsTab: {
screen: MySettingsScreen,
@ -73,27 +73,27 @@ const TabNav = TabNavigator(
size={26}
style={{ color: tintColor }}
/>
)
}
}
),
},
},
},
{
tabBarPosition: 'bottom',
animationEnabled: false,
swipeEnabled: false
}
)
swipeEnabled: false,
},
);
const SecondaryBottomTabs = StackNavigator({
Root: {
screen: TabNav
screen: TabNav,
},
NotifSettings: {
screen: MyNotificationsSettingsScreen,
navigationOptions: {
title: 'Notifications'
}
}
})
title: 'Notifications',
},
},
});
export default SecondaryBottomTabs
export default SecondaryBottomTabs;

458
class.js
View File

@ -1,32 +1,32 @@
/* global fetch */
import { AsyncStorage } from 'react-native'
import Frisbee from 'frisbee'
import { AsyncStorage } from 'react-native';
import Frisbee from 'frisbee';
let useBlockcypherTokens = false
let useBlockcypherTokens = false;
let bitcoin = require('bitcoinjs-lib')
let signer = require('./models/signer')
let BigNumber = require('bignumber.js')
let isaac = require('isaac')
let bitcoin = require('bitcoinjs-lib');
let signer = require('./models/signer');
let BigNumber = require('bignumber.js');
let isaac = require('isaac');
// alternative https://github.com/pointbiz/bitaddress.org/blob/master/src/securerandom.js
class AbstractWallet {
constructor() {
this.type = 'abstract'
this.label = ''
this.secret = '' // private key or recovery phrase
this.balance = 0
this.transactions = []
this._address = false // cache
this.utxo = []
this.type = 'abstract';
this.label = '';
this.secret = ''; // private key or recovery phrase
this.balance = 0;
this.transactions = [];
this._address = false; // cache
this.utxo = [];
}
getTransactions() {
return this.transactions
return this.transactions;
}
getTypeReadable() {
return this.type
return this.type;
}
/**
@ -34,35 +34,35 @@ class AbstractWallet {
* @returns {string}
*/
getLabel() {
return this.label
return this.label;
}
getBalance() {
return this.balance
return this.balance;
}
setLabel(newLabel) {
this.label = newLabel
return this
this.label = newLabel;
return this;
}
getSecret() {
return this.secret
return this.secret;
}
setSecret(newSecret) {
this.secret = newSecret
return this
this.secret = newSecret;
return this;
}
static fromJson(obj) {
let obj2 = JSON.parse(obj)
let temp = new this()
let obj2 = JSON.parse(obj);
let temp = new this();
for (let key2 of Object.keys(obj2)) {
temp[key2] = obj2[key2]
temp[key2] = obj2[key2];
}
return temp
return temp;
}
getAddress() {}
@ -76,34 +76,34 @@ class AbstractWallet {
*/
export class LegacyWallet extends AbstractWallet {
constructor() {
super()
this.type = 'legacy'
super();
this.type = 'legacy';
}
generate() {
function myRng(c) {
let buf = Buffer.alloc(c)
let totalhex = ''
let buf = Buffer.alloc(c);
let totalhex = '';
for (let i = 0; i < c; i++) {
let randomNumber = isaac.random()
randomNumber = Math.floor(randomNumber * 255)
let n = new BigNumber(randomNumber)
let hex = n.toString(16)
let randomNumber = isaac.random();
randomNumber = Math.floor(randomNumber * 255);
let n = new BigNumber(randomNumber);
let hex = n.toString(16);
if (hex.length === 1) {
hex = '0' + hex
hex = '0' + hex;
}
totalhex += hex
totalhex += hex;
}
totalhex = bitcoin.crypto.sha256('oh hai!' + totalhex).toString('hex')
totalhex = bitcoin.crypto.sha256(totalhex).toString('hex')
buf.fill(totalhex, 0, 'hex')
return buf
totalhex = bitcoin.crypto.sha256('oh hai!' + totalhex).toString('hex');
totalhex = bitcoin.crypto.sha256(totalhex).toString('hex');
buf.fill(totalhex, 0, 'hex');
return buf;
}
this.secret = bitcoin.ECPair.makeRandom({ rng: myRng }).toWIF()
this.secret = bitcoin.ECPair.makeRandom({ rng: myRng }).toWIF();
}
getTypeReadable() {
return 'P2 PKH'
return 'P2 PKH';
}
/**
@ -111,145 +111,216 @@ export class LegacyWallet extends AbstractWallet {
* @returns {string}
*/
getAddress() {
if (this._address) return this._address
let address
if (this._address) return this._address;
let address;
try {
let keyPair = bitcoin.ECPair.fromWIF(this.secret)
address = keyPair.getAddress()
let keyPair = bitcoin.ECPair.fromWIF(this.secret);
address = keyPair.getAddress();
} catch (err) {
return false
return false;
}
this._address = address
this._address = address;
return this._address
return this._address;
}
async fetchBalance() {
let response
let token = ((array) => {
let response;
let token = (array => {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]]
[array[i], array[j]] = [array[j], array[i]];
}
return array[0]
})(['0326b7107b4149559d18ce80612ef812', 'a133eb7ccacd4accb80cb1225de4b155', '7c2b1628d27b4bd3bf8eaee7149c577f', 'f1e5a02b9ec84ec4bc8db2349022e5f5', 'e5926dbeb57145979153adc41305b183'])
return array[0];
})([
'0326b7107b4149559d18ce80612ef812',
'a133eb7ccacd4accb80cb1225de4b155',
'7c2b1628d27b4bd3bf8eaee7149c577f',
'f1e5a02b9ec84ec4bc8db2349022e5f5',
'e5926dbeb57145979153adc41305b183',
]);
try {
if (useBlockcypherTokens) {
response = await fetch('https://api.blockcypher.com/v1/btc/main/addrs/' + this.getAddress() + '/balance?token=' + token)
response = await fetch(
'https://api.blockcypher.com/v1/btc/main/addrs/' +
this.getAddress() +
'/balance?token=' +
token,
);
} else {
response = await fetch('https://api.blockcypher.com/v1/btc/main/addrs/' + this.getAddress() + '/balance')
response = await fetch(
'https://api.blockcypher.com/v1/btc/main/addrs/' +
this.getAddress() +
'/balance',
);
}
let json = await response.json()
let json = await response.json();
if (typeof json.final_balance === 'undefined') {
throw new Error('Could not fetch balance from API')
throw new Error('Could not fetch balance from API');
}
this.balance = json.final_balance / 100000000
this.balance = json.final_balance / 100000000;
} catch (err) {
console.warn(err)
console.warn(err);
}
}
async fetchUtxo() {
let response
let token = ((array) => {
let response;
let token = (array => {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]]
[array[i], array[j]] = [array[j], array[i]];
}
return array[0]
})(['0326b7107b4149559d18ce80612ef812', 'a133eb7ccacd4accb80cb1225de4b155', '7c2b1628d27b4bd3bf8eaee7149c577f', 'f1e5a02b9ec84ec4bc8db2349022e5f5', 'e5926dbeb57145979153adc41305b183'])
return array[0];
})([
'0326b7107b4149559d18ce80612ef812',
'a133eb7ccacd4accb80cb1225de4b155',
'7c2b1628d27b4bd3bf8eaee7149c577f',
'f1e5a02b9ec84ec4bc8db2349022e5f5',
'e5926dbeb57145979153adc41305b183',
]);
try {
// TODO: hande case when there's more than 2000 UTXOs (do pagination)
// TODO: (2000 is max UTXOs we can fetch in one call)
if (useBlockcypherTokens) {
response = await fetch('https://api.blockcypher.com/v1/btc/main/addrs/' + this.getAddress() + '?unspentOnly=true&limit=2000&token=' + token)
response = await fetch(
'https://api.blockcypher.com/v1/btc/main/addrs/' +
this.getAddress() +
'?unspentOnly=true&limit=2000&token=' +
token,
);
} else {
response = await fetch('https://api.blockcypher.com/v1/btc/main/addrs/' + this.getAddress() + '?unspentOnly=true&limit=2000')
response = await fetch(
'https://api.blockcypher.com/v1/btc/main/addrs/' +
this.getAddress() +
'?unspentOnly=true&limit=2000',
);
}
let json = await response.json()
let json = await response.json();
if (typeof json.final_balance === 'undefined') {
throw new Error('Could not fetch UTXO from API')
throw new Error('Could not fetch UTXO from API');
}
json.txrefs = json.txrefs || [] // case when source address is empty
this.utxo = json.txrefs
json.txrefs = json.txrefs || []; // case when source address is empty
this.utxo = json.txrefs;
json.unconfirmed_txrefs = json.unconfirmed_txrefs || []
this.utxo = this.utxo.concat(json.unconfirmed_txrefs)
json.unconfirmed_txrefs = json.unconfirmed_txrefs || [];
this.utxo = this.utxo.concat(json.unconfirmed_txrefs);
console.log('got utxo: ', this.utxo)
console.log('got utxo: ', this.utxo);
} catch (err) {
console.warn(err)
console.warn(err);
}
}
async fetchTransactions() {
let response
let token = ((array) => {
let response;
let token = (array => {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]]
[array[i], array[j]] = [array[j], array[i]];
}
return array[0]
})(['0326b7107b4149559d18ce80612ef812', 'a133eb7ccacd4accb80cb1225de4b155', '7c2b1628d27b4bd3bf8eaee7149c577f', 'f1e5a02b9ec84ec4bc8db2349022e5f5', 'e5926dbeb57145979153adc41305b183'])
return array[0];
})([
'0326b7107b4149559d18ce80612ef812',
'a133eb7ccacd4accb80cb1225de4b155',
'7c2b1628d27b4bd3bf8eaee7149c577f',
'f1e5a02b9ec84ec4bc8db2349022e5f5',
'e5926dbeb57145979153adc41305b183',
]);
try {
let url
let url;
if (useBlockcypherTokens) {
response = await fetch(url = 'https://api.blockcypher.com/v1/btc/main/addrs/' + this.getAddress() + '/full?token=' + token)
response = await fetch(
(url =
'https://api.blockcypher.com/v1/btc/main/addrs/' +
this.getAddress() +
'/full?token=' +
token),
);
} else {
response = await fetch(url = 'https://api.blockcypher.com/v1/btc/main/addrs/' + this.getAddress() + '/full')
response = await fetch(
(url =
'https://api.blockcypher.com/v1/btc/main/addrs/' +
this.getAddress() +
'/full'),
);
}
console.log(url)
let json = await response.json()
console.log(url);
let json = await response.json();
if (!json.txs) {
throw new Error('Could not fetch transactions from API')
throw new Error('Could not fetch transactions from API');
}
this.transactions = json.txs
this.transactions = json.txs;
// now, calculating value per each transaction...
for (let tx of this.transactions) {
// how much came in...
let value = 0
let value = 0;
for (let out of tx.outputs) {
if (out.addresses.indexOf(this.getAddress()) !== -1) { // found our address in outs of this TX
value += out.value
if (out.addresses.indexOf(this.getAddress()) !== -1) {
// found our address in outs of this TX
value += out.value;
}
}
tx.value = value
tx.value = value;
// end
// how much came out
value = 0
value = 0;
for (let inp of tx.inputs) {
if (inp.addresses.indexOf(this.getAddress()) !== -1) { // found our address in outs of this TX
value -= inp.output_value
if (inp.addresses.indexOf(this.getAddress()) !== -1) {
// found our address in outs of this TX
value -= inp.output_value;
}
}
console.log('came out', value)
tx.value += value
console.log('came out', value);
tx.value += value;
// end
}
} catch (err) {
console.warn(err)
console.warn(err);
}
}
getShortAddress() {
let a = this.getAddress().split('')
return a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8] + a[9] + a[10] + a[11] + a[12] + a[13] + '...' + a[a.length - 6] + a[a.length - 5] + a[a.length - 4] + a[a.length - 3] + a[a.length - 2] + a[a.length - 1]
let a = this.getAddress().split('');
return (
a[0] +
a[1] +
a[2] +
a[3] +
a[4] +
a[5] +
a[6] +
a[7] +
a[8] +
a[9] +
a[10] +
a[11] +
a[12] +
a[13] +
'...' +
a[a.length - 6] +
a[a.length - 5] +
a[a.length - 4] +
a[a.length - 3] +
a[a.length - 2] +
a[a.length - 1]
);
}
async broadcastTx(txhex) {
const api = new Frisbee({
baseURI: 'https://btczen.com',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
let res = await api.get('/broadcast/' + txhex)
console.log('response', res.body)
return res.body
let res = await api.get('/broadcast/' + txhex);
console.log('response', res.body);
return res.body;
/* const api = new Frisbee({
baseURI: 'https://api.blockcypher.com',
@ -274,120 +345,143 @@ export class LegacyWallet extends AbstractWallet {
export class SegwitBech32Wallet extends LegacyWallet {
constructor() {
super()
this.type = 'segwitBech32'
super();
this.type = 'segwitBech32';
}
getTypeReadable() {
return 'P2 WPKH'
return 'P2 WPKH';
}
getAddress() {
if (this._address) return this._address
let address
if (this._address) return this._address;
let address;
try {
let keyPair = bitcoin.ECPair.fromWIF(this.secret)
let pubKey = keyPair.getPublicKeyBuffer()
let scriptPubKey = bitcoin.script.witnessPubKeyHash.output.encode(bitcoin.crypto.hash160(pubKey))
address = bitcoin.address.fromOutputScript(scriptPubKey)
let keyPair = bitcoin.ECPair.fromWIF(this.secret);
let pubKey = keyPair.getPublicKeyBuffer();
let scriptPubKey = bitcoin.script.witnessPubKeyHash.output.encode(
bitcoin.crypto.hash160(pubKey),
);
address = bitcoin.address.fromOutputScript(scriptPubKey);
} catch (err) {
return false
return false;
}
this._address = address
this._address = address;
return this._address
return this._address;
}
}
export class SegwitP2SHWallet extends LegacyWallet {
constructor() {
super()
this.type = 'segwitP2SH'
super();
this.type = 'segwitP2SH';
}
getTypeReadable() {
return 'SegWit (P2SH)'
return 'SegWit (P2SH)';
}
getAddress() {
if (this._address) return this._address
let address
if (this._address) return this._address;
let address;
try {
let keyPair = bitcoin.ECPair.fromWIF(this.secret)
let pubKey = keyPair.getPublicKeyBuffer()
let witnessScript = bitcoin.script.witnessPubKeyHash.output.encode(bitcoin.crypto.hash160(pubKey))
let scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(witnessScript))
address = bitcoin.address.fromOutputScript(scriptPubKey)
let keyPair = bitcoin.ECPair.fromWIF(this.secret);
let pubKey = keyPair.getPublicKeyBuffer();
let witnessScript = bitcoin.script.witnessPubKeyHash.output.encode(
bitcoin.crypto.hash160(pubKey),
);
let scriptPubKey = bitcoin.script.scriptHash.output.encode(
bitcoin.crypto.hash160(witnessScript),
);
address = bitcoin.address.fromOutputScript(scriptPubKey);
} catch (err) {
return false
return false;
}
this._address = address
this._address = address;
return this._address
return this._address;
}
createTx(utxos, amount, fee, address, memo, sequence) {
if (sequence === undefined) {
sequence = 0
sequence = 0;
}
// transforming UTXOs fields to how module expects it
for (let u of utxos) {
u.confirmations = 6 // hack to make module accept 0 confirmations
u.txid = u.tx_hash
u.vout = u.tx_output_n
u.amount = new BigNumber(u.value)
u.amount = u.amount.div(100000000)
u.amount = u.amount.toString(10)
u.confirmations = 6; // hack to make module accept 0 confirmations
u.txid = u.tx_hash;
u.vout = u.tx_output_n;
u.amount = new BigNumber(u.value);
u.amount = u.amount.div(100000000);
u.amount = u.amount.toString(10);
}
console.log('creating tx ', amount, ' with fee ', fee, 'secret=', this.getSecret(), 'from address', this.getAddress())
let amountPlusFee = parseFloat((new BigNumber(amount)).add(fee).toString(10))
console.log(
'creating tx ',
amount,
' with fee ',
fee,
'secret=',
this.getSecret(),
'from address',
this.getAddress(),
);
let amountPlusFee = parseFloat(new BigNumber(amount).add(fee).toString(10));
// to compensate that module substracts fee from amount
return signer.createSegwitTransaction(utxos, address, amountPlusFee, fee, this.getSecret(), this.getAddress(), sequence)
return signer.createSegwitTransaction(
utxos,
address,
amountPlusFee,
fee,
this.getSecret(),
this.getAddress(),
sequence,
);
}
}
export class AppStorage {
constructor() {
/** {Array.<AbstractWallet>} */
this.wallets = []
this.tx_metadata = {}
this.wallets = [];
this.tx_metadata = {};
this.settings = {
brandingColor: '#00aced',
buttonBackground: '#00aced',
buttonDangedBackground: '#F40349'
}
buttonDangedBackground: '#F40349',
};
}
async loadFromDisk() {
try {
let data = await AsyncStorage.getItem('data')
let data = await AsyncStorage.getItem('data');
if (data !== null) {
data = JSON.parse(data)
if (!data.wallets) return false
let wallets = data.wallets
data = JSON.parse(data);
if (!data.wallets) return false;
let wallets = data.wallets;
for (let key of wallets) {
// deciding which type is wallet and instatiating correct object
let tempObj = JSON.parse(key)
let unserializedWallet
let tempObj = JSON.parse(key);
let unserializedWallet;
switch (tempObj.type) {
case 'segwitBech32':
unserializedWallet = SegwitBech32Wallet.fromJson(key)
break
unserializedWallet = SegwitBech32Wallet.fromJson(key);
break;
case 'segwitP2SH':
unserializedWallet = SegwitP2SHWallet.fromJson(key)
break
unserializedWallet = SegwitP2SHWallet.fromJson(key);
break;
case 'legacy':
default:
unserializedWallet = LegacyWallet.fromJson(key)
break
unserializedWallet = LegacyWallet.fromJson(key);
break;
}
// done
this.wallets.push(unserializedWallet)
this.tx_metadata = data.tx_metadata
this.wallets.push(unserializedWallet);
this.tx_metadata = data.tx_metadata;
}
}
} catch (error) {
return false
return false;
}
}
@ -396,43 +490,45 @@ export class AppStorage {
* @param wallet {AbstractWallet}
*/
deleteWallet(wallet) {
let secret = wallet.getSecret()
let tempWallets = []
let secret = wallet.getSecret();
let tempWallets = [];
for (let value of this.wallets) {
if (value.getSecret() === secret) { // the one we should delete
if (value.getSecret() === secret) {
// the one we should delete
// nop
} else { // the one we must keep
tempWallets.push(value)
} else {
// the one we must keep
tempWallets.push(value);
}
}
this.wallets = tempWallets
this.wallets = tempWallets;
}
saveToDisk() {
let walletsToSave = []
let walletsToSave = [];
for (let key of this.wallets) {
walletsToSave.push(JSON.stringify(key))
walletsToSave.push(JSON.stringify(key));
}
let data = {
wallets: walletsToSave,
tx_metadata: this.tx_metadata
}
tx_metadata: this.tx_metadata,
};
return AsyncStorage.setItem('data', JSON.stringify(data))
return AsyncStorage.setItem('data', JSON.stringify(data));
}
async fetchWalletBalances() {
// console.warn('app - fetchWalletBalances()')
for (let wallet of this.wallets) {
await wallet.fetchBalance()
await wallet.fetchBalance();
}
}
async fetchWalletTransactions() {
// console.warn('app - fetchWalletTransactions()')
for (let wallet of this.wallets) {
await wallet.fetchTransactions()
await wallet.fetchTransactions();
}
}
@ -441,15 +537,15 @@ export class AppStorage {
* @returns {Array.<AbstractWallet>}
*/
getWallets() {
return this.wallets
return this.wallets;
}
getTransactions() {
let txs = []
let txs = [];
for (let wallet of this.wallets) {
txs = txs.concat(wallet.transactions)
txs = txs.concat(wallet.transactions);
}
return txs
return txs;
}
saveWallets() {}
@ -459,10 +555,10 @@ export class AppStorage {
listUnconfirmed() {}
getBalance() {
let finalBalance = 0
let finalBalance = 0;
for (let wal of this.wallets) {
finalBalance += wal.balance
finalBalance += wal.balance;
}
return finalBalance
return finalBalance;
}
}

View File

@ -1,26 +1,34 @@
function EV(eventName, arg) {
if (Object.values(EV.enum).indexOf(eventName) === -1) {
return console.warn('Unregistered event', eventName, 'registered events:', EV.enum)
return console.warn(
'Unregistered event',
eventName,
'registered events:',
EV.enum,
);
}
EV.callbacks = EV.callbacks || {} // static variable
EV.callbacks[eventName] = EV.callbacks[eventName] || []
EV.callbacks = EV.callbacks || {}; // static variable
EV.callbacks[eventName] = EV.callbacks[eventName] || [];
if (typeof arg !== 'function') { // then its an argument
console.log('got event', eventName, '...')
if (typeof arg !== 'function') {
// then its an argument
console.log('got event', eventName, '...');
for (let cc of EV.callbacks[eventName]) {
console.log('dispatching event', eventName)
cc(arg)
console.log('dispatching event', eventName);
cc(arg);
}
} else { // its a callback. subscribe it to event
console.log('someone subscribed to', eventName)
EV.callbacks[eventName].push(arg)
} else {
// its a callback. subscribe it to event
console.log('someone subscribed to', eventName);
EV.callbacks[eventName].push(arg);
}
}
EV.enum = {
WALLETS_COUNT_CHANGED: 'WALLETS_COUNT_CHANGED',
TRANSACTIONS_COUNT_CHANGED: 'TRANSACTIONS_COUNT_CHANGED',
CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS: 'CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS'
}
CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS:
'CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS',
};
module.exports = EV
module.exports = EV;

372
package-lock.json generated
View File

@ -295,6 +295,12 @@
"integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=",
"dev": true
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"absolute-path": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz",
@ -2095,6 +2101,12 @@
}
}
},
"boolify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/boolify/-/boolify-1.0.1.tgz",
"integrity": "sha1-tcCeF8rNET0Rt7s+04TMASmU2Gs=",
"dev": true
},
"boom": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
@ -2368,6 +2380,17 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
},
"camelcase-keys": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz",
"integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=",
"dev": true,
"requires": {
"camelcase": "4.1.0",
"map-obj": "2.0.0",
"quick-lru": "1.1.0"
}
},
"capture-stack-trace": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz",
@ -2679,6 +2702,15 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
"integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ=="
},
"common-tags": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.7.2.tgz",
"integrity": "sha512-joj9ZlUOjCrwdbmiLqafeUSgkUM74NqhLsZtSqDmhKudaIY197zTrb8JMl31fMnCUuxwFT23eC/oWvrZzDLRJQ==",
"dev": true,
"requires": {
"babel-runtime": "6.26.0"
}
},
"compare-versions": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.1.0.tgz",
@ -3343,6 +3375,12 @@
"randombytes": "2.0.6"
}
},
"dlv": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.1.tgz",
"integrity": "sha512-b/kUB0D6RgRGG69h5ExsLnUAwfs5Jndfk1pU2ao7/9mVdsxpUBlkFdTkNJThXw1jrLXpUbIIg+h3um5zXi6sFA==",
"dev": true
},
"doctrine": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@ -3702,6 +3740,21 @@
}
}
},
"eslint-config-prettier": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-2.9.0.tgz",
"integrity": "sha512-ag8YEyBXsm3nmOv1Hz991VtNNDMRa+MNy8cY47Pl4bw6iuzqKbJajXdqUpiw13STdLLrznxgm1hj9NhxeOYq0A==",
"requires": {
"get-stdin": "5.0.1"
},
"dependencies": {
"get-stdin": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz",
"integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g="
}
}
},
"eslint-config-standard": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-11.0.0.tgz",
@ -3791,6 +3844,15 @@
"semver": "5.5.0"
}
},
"eslint-plugin-prettier": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.0.tgz",
"integrity": "sha512-floiaI4F7hRkTrFe8V2ItOK97QYrX75DjmdzmVITZoAP6Cn06oEDPQRsO6MlHEP/u2SxI3xQ52Kpjw6j5WGfeQ==",
"requires": {
"fast-diff": "1.1.2",
"jest-docblock": "21.2.0"
}
},
"eslint-plugin-promise": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.7.0.tgz",
@ -4326,6 +4388,11 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
},
"fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
@ -8105,6 +8172,18 @@
"resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz",
"integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM="
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"lodash.merge": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz",
"integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==",
"dev": true
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
@ -8161,11 +8240,33 @@
"resolved": "https://registry.npmjs.org/lodash.times/-/lodash.times-4.3.2.tgz",
"integrity": "sha1-Ph8lZcQxdU1Uq1fy7RdBk5KFyh0="
},
"lodash.unescape": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
"integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=",
"dev": true
},
"lodash.zipobject": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/lodash.zipobject/-/lodash.zipobject-4.1.3.tgz",
"integrity": "sha1-s5n1q6j/YqdG9peb8gshT5ZNvvg="
},
"loglevel": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz",
"integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=",
"dev": true
},
"loglevel-colored-level-prefix": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz",
"integrity": "sha1-akAhj9x64V/HbD0PPmdsRlOIYD4=",
"dev": true,
"requires": {
"chalk": "1.1.3",
"loglevel": "1.6.1"
}
},
"longest": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
@ -8255,6 +8356,24 @@
"resolved": "https://registry.npmjs.org/macos-release/-/macos-release-1.1.0.tgz",
"integrity": "sha512-mmLbumEYMi5nXReB9js3WGsB8UE6cDBWyIO62Z4DNx6GbRhDxHNjA1MlzSpJ2S2KM1wyiPRA0d19uHWYYvMHjA=="
},
"make-plural": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.1.1.tgz",
"integrity": "sha512-triaMVDDYiB+OU1Mz6ht74+z0Bb/bzNESeMwRboSprI3GRWbOvfxEnpWI0eDixQtMPrC2C0revd4wmuck5GcoQ==",
"dev": true,
"requires": {
"minimist": "1.2.0"
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true,
"optional": true
}
}
},
"makeerror": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
@ -8268,6 +8387,12 @@
"resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
"integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8="
},
"map-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz",
"integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=",
"dev": true
},
"map-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
@ -8391,6 +8516,41 @@
"resolved": "https://registry.npmjs.org/merkle-lib/-/merkle-lib-2.0.10.tgz",
"integrity": "sha1-grjbrnXieneFOItz+ddyXQ9vMyY="
},
"messageformat": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/messageformat/-/messageformat-1.1.1.tgz",
"integrity": "sha512-Q0uXcDtF5pEZsVSyhzDOGgZZK6ykN79VY9CwU3Nv0gsqx62BjdJW0MT+63UkHQ4exe3HE33ZlxR2/YwoJarRTg==",
"dev": true,
"requires": {
"glob": "7.0.6",
"make-plural": "4.1.1",
"messageformat-parser": "1.1.0",
"nopt": "3.0.6",
"reserved-words": "0.1.2"
},
"dependencies": {
"glob": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz",
"integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=",
"dev": true,
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
}
}
},
"messageformat-parser": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-1.1.0.tgz",
"integrity": "sha512-Hwem6G3MsKDLS1FtBRGIs8T50P1Q00r3srS6QJePCFbad9fq0nYxwf3rnU2BreApRGhmpKMV7oZI06Sy1c9TPA==",
"dev": true
},
"method-override": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/method-override/-/method-override-2.3.10.tgz",
@ -8933,6 +9093,15 @@
"resolved": "https://registry.npmjs.org/noop-fn/-/noop-fn-1.0.0.tgz",
"integrity": "sha1-XzPUfxPSFQ35PgywNmmemC94/78="
},
"nopt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"dev": true,
"requires": {
"abbrev": "1.1.1"
}
},
"normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@ -9727,6 +9896,150 @@
"resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
"integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks="
},
"prettier": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.11.1.tgz",
"integrity": "sha512-T/KD65Ot0PB97xTrG8afQ46x3oiVhnfGjGESSI9NWYcG92+OUPZKkwHqGWXH2t9jK1crnQjubECW0FuOth+hxw=="
},
"prettier-eslint": {
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-8.8.1.tgz",
"integrity": "sha512-8YMkJZnA+XVfEW6fPet05jpNmSQbD+Htbh/QyOxQcVf2GIUEZsnGP7ZScaM9Mq2Ra2261eCu60E7/TRIy9coXQ==",
"dev": true,
"requires": {
"babel-runtime": "6.26.0",
"common-tags": "1.7.2",
"dlv": "1.1.1",
"eslint": "4.19.0",
"indent-string": "3.2.0",
"lodash.merge": "4.6.1",
"loglevel-colored-level-prefix": "1.0.0",
"prettier": "1.11.1",
"pretty-format": "22.4.0",
"require-relative": "0.8.7",
"typescript": "2.7.2",
"typescript-eslint-parser": "11.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
}
},
"pretty-format": {
"version": "22.4.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-22.4.0.tgz",
"integrity": "sha512-pvCxP2iODIIk9adXlo4S3GRj0BrJiil68kByAa1PrgG97c1tClh9dLMgp3Z6cHFZrclaABt0UH8PIhwHuFLqYA==",
"dev": true,
"requires": {
"ansi-regex": "3.0.0",
"ansi-styles": "3.2.1"
}
}
}
},
"prettier-eslint-cli": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/prettier-eslint-cli/-/prettier-eslint-cli-4.7.1.tgz",
"integrity": "sha512-hQbsGaEVz97oBBcKdsJ46khv0kOGkMyWrXzcFOXW6X8UuetZ/j0yDJkNJgUTVc6PVFbbzBXk+qgd5vos9qzXPQ==",
"dev": true,
"requires": {
"arrify": "1.0.1",
"babel-runtime": "6.26.0",
"boolify": "1.0.1",
"camelcase-keys": "4.2.0",
"chalk": "2.3.0",
"common-tags": "1.7.2",
"eslint": "4.19.0",
"find-up": "2.1.0",
"get-stdin": "5.0.1",
"glob": "7.1.2",
"ignore": "3.3.7",
"indent-string": "3.2.0",
"lodash.memoize": "4.1.2",
"loglevel-colored-level-prefix": "1.0.0",
"messageformat": "1.1.1",
"prettier-eslint": "8.8.1",
"rxjs": "5.5.7",
"yargs": "10.0.3"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
}
},
"chalk": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
"integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "4.5.0"
}
},
"get-stdin": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz",
"integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=",
"dev": true
},
"supports-color": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
"integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
"dev": true,
"requires": {
"has-flag": "2.0.0"
}
},
"yargs": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-10.0.3.tgz",
"integrity": "sha512-DqBpQ8NAUX4GyPP/ijDGHsJya4tYqLQrjPr95HNsr1YwL3+daCfvBwg7+gIC6IdJhR2kATh3hb61vjzMWEtjdw==",
"dev": true,
"requires": {
"cliui": "3.2.0",
"decamelize": "1.2.0",
"find-up": "2.1.0",
"get-caller-file": "1.0.2",
"os-locale": "2.1.0",
"require-directory": "2.1.1",
"require-main-filename": "1.0.1",
"set-blocking": "2.0.0",
"string-width": "2.1.1",
"which-module": "2.0.0",
"y18n": "3.2.1",
"yargs-parser": "8.1.0"
}
},
"yargs-parser": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz",
"integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==",
"dev": true,
"requires": {
"camelcase": "4.1.0"
}
}
}
},
"pretty-format": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-21.2.1.tgz",
@ -9898,6 +10211,12 @@
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
},
"quick-lru": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz",
"integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=",
"dev": true
},
"random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
@ -10716,6 +11035,12 @@
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
},
"require-relative": {
"version": "0.8.7",
"resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
"integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
"dev": true
},
"require-uncached": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
@ -10732,6 +11057,12 @@
"integrity": "sha1-APsVrEkYxBnKgrQ/JMeIguZgOaE=",
"dev": true
},
"reserved-words": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz",
"integrity": "sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE=",
"dev": true
},
"resolve": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz",
@ -10914,6 +11245,23 @@
"rx-lite": "4.0.8"
}
},
"rxjs": {
"version": "5.5.7",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.7.tgz",
"integrity": "sha512-Hxo2ac8gRQjwjtKgukMIwBRbq5+KAeEV5hXM4obYBOAghev41bDQWgFH4svYiU9UnQ5kNww2LgfyBdevCd2HXA==",
"dev": true,
"requires": {
"symbol-observable": "1.0.1"
},
"dependencies": {
"symbol-observable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
"integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=",
"dev": true
}
}
},
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
@ -13086,6 +13434,30 @@
"resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.12.0.tgz",
"integrity": "sha512-fvnkvueAOFLhtAqDgIA/wMP21SMwS/NQESFKZuwVrj5m/Ew6eK2S0z0iB++cwtROPWDOhaT6OUfla8UwMw4Adg=="
},
"typescript": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz",
"integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==",
"dev": true
},
"typescript-eslint-parser": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-11.0.0.tgz",
"integrity": "sha512-/fBHTBRBSorWQGKWOOjeMPkzd3o8cOPtFjTRwU5JLNGgVtmMa3KDkiw0R2n+H6ovo9y3OX30/5usm6YTqY44PQ==",
"dev": true,
"requires": {
"lodash.unescape": "4.0.1",
"semver": "5.4.1"
},
"dependencies": {
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
"dev": true
}
}
},
"ua-parser-js": {
"version": "0.7.17",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",

View File

@ -14,6 +14,7 @@
"eslint-plugin-react": "^7.7.0",
"eslint-plugin-standard": "^3.0.1",
"jest-expo": "^23.0.0",
"prettier-eslint-cli": "^4.7.1",
"react-native-scripts": "1.8.1",
"react-test-renderer": "16.0.0",
"rn-nodeify": "^8.3.0",
@ -29,7 +30,7 @@
"ios": "react-native-scripts ios",
"postinstall": "./node_modules/.bin/rn-nodeify --install buffer,events,process,stream,util,inherits,fs,path --hack",
"test": "nodejs ./node_modules/.bin/mocha tests/* && node node_modules/jest/bin/jest.js",
"lint": "./node_modules/.bin/eslint *.js screen/**/*.js"
"lint": "./node_modules/.bin/eslint *.js screen/**/*.js --fix"
},
"jest": {
"preset": "jest-expo"
@ -39,11 +40,14 @@
"bignumber.js": "^5.0.0",
"bitcoinjs-lib": "^3.3.2",
"buffer": "^4.9.1",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-prettier": "^2.6.0",
"expo": "^23.0.6",
"frisbee": "^1.6.4",
"isaac": "0.0.5",
"mocha": "^5.0.4",
"node-libs-react-native": "^1.0.1",
"prettier": "^1.11.1",
"process": "^0.11.10",
"prop-types": "^15.6.1",
"react": "16.0.0",

View File

@ -1,18 +1,26 @@
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React, { Component } from 'react';
import { ActivityIndicator, View, TextInput } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { SafeAreaView, } from 'react-navigation';
import { Icon, Card, Header, } from 'react-native-elements'
import { SafeAreaView } from 'react-navigation';
import { Icon, Card, Header } from 'react-native-elements';
import QRCode from 'react-native-qrcode';
import { List, Button, ListItem } from 'react-native-elements'
import { List, Button, ListItem } from 'react-native-elements';
import {
BlueLoading, BlueSpacing20, BlueList, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader,
BlueFormInput, BlueSpacing
} from '../../BlueComponents'
BlueLoading,
BlueSpacing20,
BlueList,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
BlueFormInput,
BlueSpacing,
} from '../../BlueComponents';
export default class ReceiveDetails extends Component {
static navigationOptions = {
tabBarLabel: 'Receive',
tabBarIcon: ({ tintColor, focused }) => (
@ -22,62 +30,62 @@ export default class ReceiveDetails extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
let address = props.navigation.state.params.address
let address = props.navigation.state.params.address;
this.state = {
isLoading: true,
address : address
}
console.log(JSON.stringify(address))
address: address,
};
console.log(JSON.stringify(address));
}
async componentDidMount() {
console.log('wallets/details - componentDidMount')
console.log('wallets/details - componentDidMount');
this.setState({
isLoading: false,
})
});
}
render() {
const { navigate } = this.props.navigation;
if (this.state.isLoading) {
return (
<BlueLoading/>
);
return <BlueLoading />;
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{flex: 1, paddingTop: 20}}>
<SafeBlueArea
forceInset={{ horizontal: 'always' }}
style={{ flex: 1, paddingTop: 20 }}
>
<BlueSpacing />
<BlueCard title={"Share this address with payer"} style={{alignItems: 'center', flex: 1}}>
<TextInput style={{marginBottom:20, color:'white'}} editable={true} value={this.state.address} />
<BlueCard
title={'Share this address with payer'}
style={{ alignItems: 'center', flex: 1 }}
>
<TextInput
style={{ marginBottom: 20, color: 'white' }}
editable
value={this.state.address}
/>
<QRCode
value={this.state.address}
size={312}
bgColor='white'
fgColor={BlueApp.settings.brandingColor}/>
bgColor="white"
fgColor={BlueApp.settings.brandingColor}
/>
</BlueCard>
<BlueButton
icon={{ name: 'arrow-left', type: 'octicon' }}
backgroundColor={BlueApp.settings.buttonBackground}
onPress={() =>
this.props.navigation.goBack()
}
onPress={() => this.props.navigation.goBack()}
title="Go back"
/>
</SafeBlueArea>
);
}
}

View File

@ -1,19 +1,27 @@
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React, { Component } from 'react';
import { ActivityIndicator, Text, View } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { SafeAreaView, } from 'react-navigation';
import { Icon, Card, Header, } from 'react-native-elements'
import { List, ListItem, Button } from 'react-native-elements'
import { SafeAreaView } from 'react-navigation';
import { Icon, Card, Header } from 'react-native-elements';
import { List, ListItem, Button } from 'react-native-elements';
import {
BlueLoading, BlueSpacing20, BlueList, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader,
BlueFormInput, BlueSpacing
} from '../../BlueComponents'
let EV = require('../../events')
BlueLoading,
BlueSpacing20,
BlueList,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
BlueFormInput,
BlueSpacing,
} from '../../BlueComponents';
let EV = require('../../events');
export default class ReceiveList extends Component {
static navigationOptions = {
tabBarLabel: 'Receive',
tabBarIcon: ({ tintColor, focused }) => (
@ -23,81 +31,80 @@ export default class ReceiveList extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
this.state = {
isLoading: true,
}
};
this.walletsCount = 0;
EV(EV.enum.WALLETS_COUNT_CHANGED, () => {
return this.componentDidMount()
})
return this.componentDidMount();
});
}
async componentDidMount() {
console.log('receive/list - componentDidMount')
let list = []
console.log('receive/list - componentDidMount');
let list = [];
this.walletsCount = 0;
for (let w of BlueApp.getWallets()) {
list.push({
title: w.getAddress(),
subtitle: w.getLabel(),
})
});
this.walletsCount++;
}
this.setState({
isLoading: false,
list: list
})
list: list,
});
}
render() {
const { navigate } = this.props.navigation;
if (this.state.isLoading) {
return (
<BlueLoading/>
);
return <BlueLoading />;
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
<BlueHeader
backgroundColor={BlueApp.settings.brandingColor}
leftComponent={<Icon name='menu' color="#fff" onPress={() => this.props.navigation.navigate('DrawerToggle') }/>}
centerComponent={{ text: 'Choose a wallet to receive', style: { color: '#fff', fontSize: 25 }}}
leftComponent={
<Icon
name="menu"
color="#fff"
onPress={() => this.props.navigation.navigate('DrawerToggle')}
/>
}
centerComponent={{
text: 'Choose a wallet to receive',
style: { color: '#fff', fontSize: 25 },
}}
/>
<BlueCard containerStyle={{ padding: 0 }}>
{
this.state.list.map((item, i) => (
{this.state.list.map((item, i) => (
<BlueListItem
onPress={() =>
{
navigate('ReceiveDetails', {address: item.title})
}
}
onPress={() => {
navigate('ReceiveDetails', { address: item.title });
}}
key={i}
title={item.title}
subtitle={item.subtitle}
leftIcon={{name: 'bitcoin', type: 'font-awesome', color: 'white'}}
leftIcon={{
name: 'bitcoin',
type: 'font-awesome',
color: 'white',
}}
/>
))
}
))}
</BlueCard>
</SafeBlueArea>
);
}
}

View File

@ -1,21 +1,34 @@
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React, { Component } from 'react';
import { ActivityIndicator, TextInput, View } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { SafeAreaView, } from 'react-navigation';
import { Icon, Card, Header, } from 'react-native-elements'
import { List, Button, ListItem } from 'react-native-elements'
import { FormLabel, FormInput, Text, FormValidationMessage } from 'react-native-elements'
import { SafeAreaView } from 'react-navigation';
import { Icon, Card, Header } from 'react-native-elements';
import { List, Button, ListItem } from 'react-native-elements';
import {
BlueLoading, BlueSpacing20, BlueList, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader,
BlueFormInput, BlueSpacing
} from '../../BlueComponents'
let EV = require('../../events')
let BigNumber = require('bignumber.js')
FormLabel,
FormInput,
Text,
FormValidationMessage,
} from 'react-native-elements';
import {
BlueLoading,
BlueSpacing20,
BlueList,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
BlueFormInput,
BlueSpacing,
} from '../../BlueComponents';
let EV = require('../../events');
let BigNumber = require('bignumber.js');
export default class SendCreate extends Component {
static navigationOptions = {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
@ -24,11 +37,11 @@ export default class SendCreate extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
console.log('send/create constructor')
console.log('send/create constructor');
this.state = {
isLoading: true,
amount: props.navigation.state.params.amount,
@ -37,12 +50,12 @@ export default class SendCreate extends Component {
memo: props.navigation.state.params.memo,
fromAddress: props.navigation.state.params.fromAddress,
broadcastErrorMessage: '',
}
};
let fromWallet = false
let fromWallet = false;
for (let w of BlueApp.getWallets()) {
if (w.getAddress() === this.state.fromAddress) {
fromWallet = w
fromWallet = w;
break;
}
}
@ -50,21 +63,27 @@ export default class SendCreate extends Component {
}
async componentDidMount() {
console.log('send/create - componentDidMount')
console.log('address = ', this.state.address)
console.log('send/create - componentDidMount');
console.log('address = ', this.state.address);
let utxo
let satoshiPerByte
let tx
let utxo;
let satoshiPerByte;
let tx;
try {
await this.state.fromWallet.fetchUtxo()
utxo = this.state.fromWallet.utxo
let startTime = Date.now()
await this.state.fromWallet.fetchUtxo();
utxo = this.state.fromWallet.utxo;
let startTime = Date.now();
tx = this.state.fromWallet.createTx(utxo, this.state.amount, this.state.fee, this.state.address, this.state.memo)
let endTime = Date.now()
console.log('create tx ', (endTime-startTime) / 1000, 'sec')
tx = this.state.fromWallet.createTx(
utxo,
this.state.amount,
this.state.fee,
this.state.address,
this.state.memo,
);
let endTime = Date.now();
console.log('create tx ', (endTime - startTime) / 1000, 'sec');
let bitcoin = require('bitcoinjs-lib');
let txDecoded = bitcoin.Transaction.fromHex(tx);
@ -72,23 +91,23 @@ export default class SendCreate extends Component {
console.log('txid', txid);
console.log('txhex', tx);
BlueApp.tx_metadata = BlueApp.tx_metadata || {}
BlueApp.tx_metadata = BlueApp.tx_metadata || {};
BlueApp.tx_metadata[txid] = {
'txhex' : tx,
'memo': this.state.memo,
}
BlueApp.saveToDisk()
txhex: tx,
memo: this.state.memo,
};
BlueApp.saveToDisk();
let feeSatoshi = new BigNumber(this.state.fee)
feeSatoshi = feeSatoshi.mul(100000000)
satoshiPerByte = feeSatoshi.div( Math.round(tx.length/2) )
satoshiPerByte = Math.round(satoshiPerByte.toString(10))
let feeSatoshi = new BigNumber(this.state.fee);
feeSatoshi = feeSatoshi.mul(100000000);
satoshiPerByte = feeSatoshi.div(Math.round(tx.length / 2));
satoshiPerByte = Math.round(satoshiPerByte.toString(10));
} catch (err) {
console.log(err)
console.log(err);
return this.setState({
isError: true,
errorMessage: JSON.stringify(err.message)
})
errorMessage: JSON.stringify(err.message),
});
}
this.setState({
@ -96,26 +115,29 @@ export default class SendCreate extends Component {
size: Math.round(tx.length / 2),
tx,
satoshiPerByte,
})
});
}
async broadcast() {
let result = await this.state.fromWallet.broadcastTx(this.state.tx)
console.log('broadcast result = ', result)
let result = await this.state.fromWallet.broadcastTx(this.state.tx);
console.log('broadcast result = ', result);
if (typeof result === 'string') {
result = JSON.parse(result)
result = JSON.parse(result);
}
if ((result && result.error)) {
this.setState({broadcastErrorMessage : JSON.stringify(result.error), broadcastSuccessMessage : ''})
if (result && result.error) {
this.setState({
broadcastErrorMessage: JSON.stringify(result.error),
broadcastSuccessMessage: '',
});
} else {
this.setState({broadcastErrorMessage : ''})
this.setState({broadcastSuccessMessage : "Success! TXID: " + JSON.stringify(result.result)})
this.setState({ broadcastErrorMessage: '' });
this.setState({
broadcastSuccessMessage:
'Success! TXID: ' + JSON.stringify(result.result),
});
}
}
render() {
const { navigate } = this.props.navigation;
@ -123,9 +145,16 @@ export default class SendCreate extends Component {
return (
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
<BlueSpacing />
<BlueCard title={"Create Transaction"} style={{alignItems: 'center', flex: 1}}>
<BlueText>Error creating transaction. Invalid address or send amount?</BlueText>
<FormValidationMessage>{this.state.errorMessage}</FormValidationMessage>
<BlueCard
title={'Create Transaction'}
style={{ alignItems: 'center', flex: 1 }}
>
<BlueText>
Error creating transaction. Invalid address or send amount?
</BlueText>
<FormValidationMessage>
{this.state.errorMessage}
</FormValidationMessage>
</BlueCard>
<BlueButton
onPress={() => this.props.navigation.goBack()}
@ -136,68 +165,65 @@ export default class SendCreate extends Component {
}
if (this.state.isLoading) {
return (
<BlueLoading/>
);
return <BlueLoading />;
}
return (
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
<BlueSpacing />
<BlueCard title={"Create Transaction"} style={{alignItems: 'center', flex: 1}}>
<BlueText>This is transaction hex, signed and ready to be broadcast to the network. Continue?</BlueText>
<BlueCard
title={'Create Transaction'}
style={{ alignItems: 'center', flex: 1 }}
>
<BlueText>
This is transaction hex, signed and ready to be broadcast to the
network. Continue?
</BlueText>
<TextInput
style={{borderColor: '#ebebeb', borderWidth: 1, marginTop:20, color:'#ebebeb'}}
style={{
borderColor: '#ebebeb',
borderWidth: 1,
marginTop: 20,
color: '#ebebeb',
}}
maxHeight={70}
multiline={true}
multiline
editable={false}
value={this.state.tx}
/>
<BlueSpacing20 />
<BlueText style={{paddingTop:20}} >To: {this.state.address}</BlueText>
<BlueText style={{ paddingTop: 20 }}>
To: {this.state.address}
</BlueText>
<BlueText>Amount: {this.state.amount} BTC</BlueText>
<BlueText>Fee: {this.state.fee} BTC</BlueText>
<BlueText>TX size: {this.state.size} Bytes</BlueText>
<BlueText>satoshiPerByte: {this.state.satoshiPerByte} Sat/B</BlueText>
<BlueText>Memo: {this.state.memo}</BlueText>
</BlueCard>
<BlueButton
icon={{ name: 'megaphone', type: 'octicon' }}
onPress={() =>
this.broadcast()
}
onPress={() => this.broadcast()}
title="Broadcast"
/>
<BlueButton
icon={{ name: 'arrow-left', type: 'octicon' }}
onPress={() =>
this.props.navigation.goBack()
}
onPress={() => this.props.navigation.goBack()}
title="Go back"
/>
<FormValidationMessage>{this.state.broadcastErrorMessage}</FormValidationMessage>
<Text style={{padding:20, color:"#090"}}>{this.state.broadcastSuccessMessage}</Text>
<FormValidationMessage>
{this.state.broadcastErrorMessage}
</FormValidationMessage>
<Text style={{ padding: 20, color: '#090' }}>
{this.state.broadcastSuccessMessage}
</Text>
</SafeBlueArea>
);
}
}

View File

@ -1,20 +1,32 @@
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React, { Component } from 'react';
import { ActivityIndicator, TextInput, View } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { SafeAreaView, } from 'react-navigation';
import { Icon, Card, Header, } from 'react-native-elements'
import { List, Button, ListItem } from 'react-native-elements'
import { FormLabel, FormInput, Text, FormValidationMessage } from 'react-native-elements'
import { SafeAreaView } from 'react-navigation';
import { Icon, Card, Header } from 'react-native-elements';
import { List, Button, ListItem } from 'react-native-elements';
import {
BlueSpacing20, BlueList, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader,
BlueFormInput, BlueSpacing
} from '../../BlueComponents'
let EV = require('../../events')
FormLabel,
FormInput,
Text,
FormValidationMessage,
} from 'react-native-elements';
import {
BlueSpacing20,
BlueList,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
BlueFormInput,
BlueSpacing,
} from '../../BlueComponents';
let EV = require('../../events');
let BigNumber = require('bignumber.js');
export default class SendDetails extends Component {
static navigationOptions = {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
@ -23,27 +35,28 @@ export default class SendDetails extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
let startTime = Date.now()
let address
if (props.navigation.state.params) address = props.navigation.state.params.address
let fromAddress
if (props.navigation.state.params) fromAddress = props.navigation.state.params.fromAddress
let fromWallet = {}
let startTime = Date.now();
let address;
if (props.navigation.state.params)
address = props.navigation.state.params.address;
let fromAddress;
if (props.navigation.state.params)
fromAddress = props.navigation.state.params.fromAddress;
let fromWallet = {};
let startTime2 = Date.now()
let startTime2 = Date.now();
for (let w of BlueApp.getWallets()) {
if (w.getAddress() === fromAddress) {
fromWallet = w
fromWallet = w;
}
}
let endTime2 = Date.now()
console.log('getAddress() took', (endTime2-startTime2)/1000, 'sec')
let endTime2 = Date.now();
console.log('getAddress() took', (endTime2 - startTime2) / 1000, 'sec');
this.state = {
errorMessage: false,
@ -53,85 +66,82 @@ export default class SendDetails extends Component {
address: address,
amount: '',
fee: '',
}
};
EV(EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, (data) => {
console.log('received event with ', data)
EV(EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, data => {
console.log('received event with ', data);
this.setState({
address: data
})
})
let endTime = Date.now()
console.log('constructor took', (endTime-startTime)/1000, 'sec')
address: data,
});
});
let endTime = Date.now();
console.log('constructor took', (endTime - startTime) / 1000, 'sec');
}
async componentDidMount() {
let startTime = Date.now()
console.log('send/details - componentDidMount')
let startTime = Date.now();
console.log('send/details - componentDidMount');
this.setState({
isLoading: false,
})
let endTime = Date.now()
console.log('componentDidMount took', (endTime-startTime)/1000, 'sec')
});
let endTime = Date.now();
console.log('componentDidMount took', (endTime - startTime) / 1000, 'sec');
}
recalculateAvailableBalance(balance, amount, fee) {
if (!amount) amount = 0
if (!fee) fee = 0
let availableBalance
if (!amount) amount = 0;
if (!fee) fee = 0;
let availableBalance;
try {
availableBalance = new BigNumber(balance)
availableBalance = availableBalance.sub(amount)
availableBalance = availableBalance.sub(fee)
availableBalance = availableBalance.toString(10)
availableBalance = new BigNumber(balance);
availableBalance = availableBalance.sub(amount);
availableBalance = availableBalance.sub(fee);
availableBalance = availableBalance.toString(10);
} catch (err) {
return balance
return balance;
}
console.log(typeof availableBalance, availableBalance)
return availableBalance === 'NaN' && balance || availableBalance
console.log(typeof availableBalance, availableBalance);
return (availableBalance === 'NaN' && balance) || availableBalance;
}
createTransaction() {
if (!this.state.amount) {
this.setState({
errorMessage: 'Amount field is not valid'
})
errorMessage: 'Amount field is not valid',
});
console.log('validation error');
return;
}
if (!this.state.fee) {
this.setState({
errorMessage: 'Fee field is not valid'
})
errorMessage: 'Fee field is not valid',
});
console.log('validation error');
return;
}
if (!this.state.address) {
this.setState({
errorMessage: 'Address field is not valid'
})
errorMessage: 'Address field is not valid',
});
console.log('validation error');
return;
}
this.setState({
errorMessage: ''
})
errorMessage: '',
});
this.props.navigation.navigate('CreateTransaction', {
amount: this.state.amount,
fee: this.state.fee,
address: this.state.address,
memo: this.state.memo,
fromAddress: this.state.fromAddress
})
fromAddress: this.state.fromAddress,
});
}
render() {
const { navigate } = this.props.navigation;
@ -146,7 +156,9 @@ export default class SendDetails extends Component {
if (!this.state.fromWallet.getAddress) {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
<Text>System error: Source wallet not found (this should never happen)</Text>
<Text>
System error: Source wallet not found (this should never happen)
</Text>
</View>
);
}
@ -154,71 +166,74 @@ export default class SendDetails extends Component {
return (
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
<BlueSpacing />
<BlueCard title={"Create Transaction"} style={{alignItems: 'center', flex: 1}}>
<BlueFormInput style={{width: 250}}
onChangeText={(text) => this.setState({address: text})}
placeholder={"receiver address here"}
<BlueCard
title={'Create Transaction'}
style={{ alignItems: 'center', flex: 1 }}
>
<BlueFormInput
style={{ width: 250 }}
onChangeText={text => this.setState({ address: text })}
placeholder={'receiver address here'}
value={this.state.address}
/>
<BlueFormInput onChangeText={(text) => this.setState({amount: text})} keyboardType={"numeric"}
placeholder={"amount to send (in BTC)"}
<BlueFormInput
onChangeText={text => this.setState({ amount: text })}
keyboardType={'numeric'}
placeholder={'amount to send (in BTC)'}
value={this.state.amount + ''}
/>
<BlueFormInput onChangeText={(text) => this.setState({fee: text})} keyboardType={"numeric"}
placeholder={"plus transaction fee (in BTC)"}
<BlueFormInput
onChangeText={text => this.setState({ fee: text })}
keyboardType={'numeric'}
placeholder={'plus transaction fee (in BTC)'}
value={this.state.fee + ''}
/>
<BlueFormInput onChangeText={(text) => this.setState({memo: text})}
placeholder={"memo to self"}
<BlueFormInput
onChangeText={text => this.setState({ memo: text })}
placeholder={'memo to self'}
value={this.state.memo}
/>
<BlueSpacing20 />
<BlueText>Remaining balance: {this.recalculateAvailableBalance(this.state.fromWallet.getBalance(), this.state.amount, this.state.fee)} BTC</BlueText>
<BlueText>
Remaining balance:{' '}
{this.recalculateAvailableBalance(
this.state.fromWallet.getBalance(),
this.state.amount,
this.state.fee,
)}{' '}
BTC
</BlueText>
</BlueCard>
<FormValidationMessage>{this.state.errorMessage}</FormValidationMessage>
<View style={{flex: 1, flexDirection: 'row', paddingTop:20,}}>
<View style={{ flex: 1, flexDirection: 'row', paddingTop: 20 }}>
<View style={{ flex: 0.33 }}>
<BlueButton
onPress={() =>
this.props.navigation.goBack()
}
onPress={() => this.props.navigation.goBack()}
title="Cancel"
/>
</View>
<View style={{ flex: 0.33 }}>
<BlueButton icon={{name: 'qrcode', type: 'font-awesome'}} style={{}}
<BlueButton
icon={{ name: 'qrcode', type: 'font-awesome' }}
style={{}}
title="scan"
onPress={() => this.props.navigation.navigate('ScanQrAddress')}
/>
</View>
<View style={{ flex: 0.33 }}>
<BlueButton
onPress={() =>
this.createTransaction()
}
onPress={() => this.createTransaction()}
title="Create"
/>
</View>
</View>
</SafeBlueArea>
);
}
}

View File

@ -1,16 +1,23 @@
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React, { Component } from 'react';
import { ActivityIndicator, Text, View } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { SafeAreaView, } from 'react-navigation';
import { Icon, Card, Header, } from 'react-native-elements'
import { List, ListItem, Button } from 'react-native-elements'
import { BlueList, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader } from '../../BlueComponents'
let EV = require('../../events')
import { SafeAreaView } from 'react-navigation';
import { Icon, Card, Header } from 'react-native-elements';
import { List, ListItem, Button } from 'react-native-elements';
import {
BlueList,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
} from '../../BlueComponents';
let EV = require('../../events');
export default class SendList extends Component {
static navigationOptions = {
tabBarLabel: 'Send',
tabBarIcon: ({ tintColor, focused }) => (
@ -20,41 +27,38 @@ export default class SendList extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
this.state = {
isLoading: true,
}
};
this.walletsCount = 0;
EV(EV.enum.WALLETS_COUNT_CHANGED, () => {
return this.componentDidMount()
})
return this.componentDidMount();
});
}
async componentDidMount() {
console.log('receive/list - componentDidMount')
let list = []
console.log('receive/list - componentDidMount');
let list = [];
this.walletsCount = 0;
for (let w of BlueApp.getWallets()) {
list.push({
title: w.getAddress(),
subtitle: w.getLabel(),
})
});
this.walletsCount++;
}
this.setState({
isLoading: false,
list: list
})
list: list,
});
}
render() {
const { navigate } = this.props.navigation;
@ -69,34 +73,37 @@ export default class SendList extends Component {
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
<BlueHeader
leftComponent={<Icon name='menu' color="#fff" onPress={() => this.props.navigation.navigate('DrawerToggle') }/>}
centerComponent={{ text: 'Choose a wallet to send from', style: { color: '#fff', fontSize: 25 }}}
leftComponent={
<Icon
name="menu"
color="#fff"
onPress={() => this.props.navigation.navigate('DrawerToggle')}
/>
}
centerComponent={{
text: 'Choose a wallet to send from',
style: { color: '#fff', fontSize: 25 },
}}
/>
<BlueCard containerStyle={{ padding: 0 }}>
{
this.state.list.map((item, i) => (
{this.state.list.map((item, i) => (
<BlueListItem
onPress={() =>
{
navigate('SendDetails', {fromAddress: item.title})
}
}
onPress={() => {
navigate('SendDetails', { fromAddress: item.title });
}}
key={i}
title={item.title}
subtitle={item.subtitle}
leftIcon={{name: 'bitcoin', type: 'font-awesome', color: 'white'}}
leftIcon={{
name: 'bitcoin',
type: 'font-awesome',
color: 'white',
}}
/>
))
}
))}
</BlueCard>
</SafeBlueArea>
);
}
}

View File

@ -1,14 +1,18 @@
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React from 'react';
import { Text, ActivityIndicator, Button, View, TouchableOpacity } from 'react-native';
import {
Text,
ActivityIndicator,
Button,
View,
TouchableOpacity,
} from 'react-native';
import { Camera, Permissions } from 'expo';
import { AppStorage, LegacyWallet } from '../../class'
import { AppStorage, LegacyWallet } from '../../class';
import Ionicons from 'react-native-vector-icons/Ionicons';
let EV = require('../../events')
let EV = require('../../events');
export default class CameraExample extends React.Component {
state = {
isLoading: false,
hasCameraPermission: null,
@ -18,14 +22,13 @@ export default class CameraExample extends React.Component {
async onBarCodeRead(ret) {
if (this.ignoreRead) return;
this.ignoreRead = true;
let that = this
let that = this;
setTimeout(() => {
that.ignoreRead = false;
}, 2000)
this.props.navigation.goBack()
EV(EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, ret.data)
}, 2000);
this.props.navigation.goBack();
EV(EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, ret.data);
} // end
async componentWillMount() {
@ -40,7 +43,6 @@ export default class CameraExample extends React.Component {
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
@ -57,14 +59,18 @@ export default class CameraExample extends React.Component {
} else {
return (
<View style={{ flex: 1 }}>
<Camera style={{ flex: 1 }} type={this.state.type}
onBarCodeRead={this.onBarCodeRead.bind(this)}>
<Camera
style={{ flex: 1 }}
type={this.state.type}
onBarCodeRead={this.onBarCodeRead.bind(this)}
>
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
}}>
}}
>
<TouchableOpacity
style={{
flex: 0.2,
@ -73,17 +79,17 @@ export default class CameraExample extends React.Component {
}}
onPress={() => {
this.setState({
type: this.state.type === Camera.Constants.Type.back
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()
}
title="Go back"
onPress={() => this.props.navigation.goBack()}
/>
</TouchableOpacity>
</View>

View File

@ -1,23 +1,35 @@
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React, { Component } from 'react';
import { ActivityIndicator, TextInput, View } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { SafeAreaView, } from 'react-navigation';
import { Icon, Card, Header, } from 'react-native-elements'
import { List, Button, ListItem } from 'react-native-elements'
import { FormLabel, FormInput, Text, FormValidationMessage } from 'react-native-elements'
import { SafeAreaView } from 'react-navigation';
import { Icon, Card, Header } from 'react-native-elements';
import { List, Button, ListItem } from 'react-native-elements';
import {
BlueLoading, BlueSpacing20, BlueList, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader,
BlueFormInput, BlueSpacing
} from '../../BlueComponents'
let EV = require('../../events')
let BigNumber = require('bignumber.js')
let bitcoinjs = require('bitcoinjs-lib')
FormLabel,
FormInput,
Text,
FormValidationMessage,
} from 'react-native-elements';
import {
BlueLoading,
BlueSpacing20,
BlueList,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
BlueFormInput,
BlueSpacing,
} from '../../BlueComponents';
let EV = require('../../events');
let BigNumber = require('bignumber.js');
let bitcoinjs = require('bitcoinjs-lib');
export default class SendCreate extends Component {
static navigationOptions = {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
@ -26,122 +38,134 @@ export default class SendCreate extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
console.log('send/create constructor')
console.log('send/create constructor');
if (!props.navigation.state.params.feeDelta) {
props.navigation.state.params.feeDelta = '0'
props.navigation.state.params.feeDelta = '0';
}
this.state = {
isLoading: true,
feeDelta: props.navigation.state.params.feeDelta,
newDestinationAddress: props.navigation.state.params.newDestinationAddress,
newDestinationAddress:
props.navigation.state.params.newDestinationAddress,
txid: props.navigation.state.params.txid,
sourceTx: props.navigation.state.params.sourceTx,
fromWallet: props.navigation.state.params.sourceWallet,
}
};
}
async componentDidMount() {
console.log('RBF-create - componentDidMount')
console.log('RBF-create - componentDidMount');
let utxo = []
let utxo = [];
let lastSequence = 0
let totalInputAmountSatoshi = 0
let lastSequence = 0;
let totalInputAmountSatoshi = 0;
for (let input of this.state.sourceTx.inputs) {
if (input.sequence > lastSequence) {
lastSequence = input.sequence
lastSequence = input.sequence;
}
totalInputAmountSatoshi += input.output_value
totalInputAmountSatoshi += input.output_value;
// let amount = new BigNumber(input.output_value)
// amount = amount.div(10000000).toString(10)
utxo.push({
tx_hash: input.prev_hash,
tx_output_n: input.output_index,
value: input.output_value,
})
});
}
// check seq=MAX and fail if it is
if (lastSequence === bitcoinjs.Transaction.DEFAULT_SEQUENCE) {
return this.setState({
isLoading: false,
nonReplaceable : true
})
nonReplaceable: true,
});
// lastSequence = 1
}
let tx_metadata = BlueApp.tx_metadata[this.state.txid]
let tx_metadata = BlueApp.tx_metadata[this.state.txid];
if (tx_metadata) {
if (tx_metadata.last_sequence) {
lastSequence = Math.max(lastSequence, tx_metadata.last_sequence)
lastSequence = Math.max(lastSequence, tx_metadata.last_sequence);
}
}
lastSequence += 1
lastSequence += 1;
let changeAddress
let transferAmount
let totalOutputAmountSatoshi = 0
let changeAddress;
let transferAmount;
let totalOutputAmountSatoshi = 0;
for (let o of this.state.sourceTx.outputs) {
totalOutputAmountSatoshi += o.value
if (o.addresses[0] === this.state.fromWallet.getAddress()) { // change
changeAddress = o.addresses[0]
totalOutputAmountSatoshi += o.value;
if (o.addresses[0] === this.state.fromWallet.getAddress()) {
// change
changeAddress = o.addresses[0];
} else {
transferAmount = new BigNumber(o.value)
transferAmount = transferAmount.div(100000000).toString(10)
transferAmount = new BigNumber(o.value);
transferAmount = transferAmount.div(100000000).toString(10);
}
}
let oldFee = new BigNumber(totalInputAmountSatoshi - totalOutputAmountSatoshi)
oldFee = parseFloat(oldFee.div(100000000).toString(10))
let oldFee = new BigNumber(
totalInputAmountSatoshi - totalOutputAmountSatoshi,
);
oldFee = parseFloat(oldFee.div(100000000).toString(10));
console.log('changeAddress = ', changeAddress)
console.log('utxo', utxo)
console.log('lastSequence', lastSequence)
console.log('totalInputAmountSatoshi', totalInputAmountSatoshi)
console.log('totalOutputAmountSatoshi', totalOutputAmountSatoshi)
console.log('transferAmount', transferAmount)
console.log('oldFee', oldFee)
let newFee = new BigNumber(oldFee)
newFee = newFee.add(this.state.feeDelta).toString(10)
console.log('new Fee', newFee)
console.log('changeAddress = ', changeAddress);
console.log('utxo', utxo);
console.log('lastSequence', lastSequence);
console.log('totalInputAmountSatoshi', totalInputAmountSatoshi);
console.log('totalOutputAmountSatoshi', totalOutputAmountSatoshi);
console.log('transferAmount', transferAmount);
console.log('oldFee', oldFee);
let newFee = new BigNumber(oldFee);
newFee = newFee.add(this.state.feeDelta).toString(10);
console.log('new Fee', newFee);
// creating TX
setTimeout(() => { // more responsive
let tx
setTimeout(() => {
// more responsive
let tx;
try {
tx = this.state.fromWallet.createTx(utxo, transferAmount, newFee, this.state.newDestinationAddress, false, lastSequence)
BlueApp.tx_metadata[this.state.txid] = tx_metadata || {}
BlueApp.tx_metadata[this.state.txid]['last_sequence'] = lastSequence
tx = this.state.fromWallet.createTx(
utxo,
transferAmount,
newFee,
this.state.newDestinationAddress,
false,
lastSequence,
);
BlueApp.tx_metadata[this.state.txid] = tx_metadata || {};
BlueApp.tx_metadata[this.state.txid]['last_sequence'] = lastSequence;
// in case new TX get confirmed, we must save metadata under new txid
let bitcoin = require('bitcoinjs-lib');
let txDecoded = bitcoin.Transaction.fromHex(tx);
let txid = txDecoded.getId();
BlueApp.tx_metadata[txid] = BlueApp.tx_metadata[this.state.txid]
BlueApp.tx_metadata[txid]['txhex'] = tx
BlueApp.tx_metadata[txid] = BlueApp.tx_metadata[this.state.txid];
BlueApp.tx_metadata[txid]['txhex'] = tx;
//
BlueApp.saveToDisk()
console.log('BlueApp.tx_metadata[this.state.txid]', BlueApp.tx_metadata[this.state.txid])
BlueApp.saveToDisk();
console.log(
'BlueApp.tx_metadata[this.state.txid]',
BlueApp.tx_metadata[this.state.txid],
);
} catch (err) {
console.log(err)
console.log(err);
return this.setState({
isError: true,
errorMessage: JSON.stringify(err.message)
})
errorMessage: JSON.stringify(err.message),
});
}
let newFeeSatoshi = new BigNumber(newFee)
newFeeSatoshi = parseInt(newFeeSatoshi.mul(100000000))
let satoshiPerByte = Math.round(newFeeSatoshi / (tx.length/2))
let newFeeSatoshi = new BigNumber(newFee);
newFeeSatoshi = parseInt(newFeeSatoshi.mul(100000000));
let satoshiPerByte = Math.round(newFeeSatoshi / (tx.length / 2));
this.setState({
isLoading: false,
size: Math.round(tx.length / 2),
@ -149,26 +173,31 @@ export default class SendCreate extends Component {
satoshiPerByte: satoshiPerByte,
amount: transferAmount,
fee: newFee,
})
}, 10)
});
}, 10);
}
async broadcast() {
console.log('broadcasting', this.state.tx)
let result = await this.state.fromWallet.broadcastTx(this.state.tx)
console.log('broadcast result = ', result)
console.log('broadcasting', this.state.tx);
let result = await this.state.fromWallet.broadcastTx(this.state.tx);
console.log('broadcast result = ', result);
if (typeof result === 'string') {
result = JSON.parse(result)
result = JSON.parse(result);
}
if ((result && result.error)) {
this.setState({broadcastErrorMessage : JSON.stringify(result.error), broadcastSuccessMessage : ''})
if (result && result.error) {
this.setState({
broadcastErrorMessage: JSON.stringify(result.error),
broadcastSuccessMessage: '',
});
} else {
this.setState({broadcastErrorMessage : ''})
this.setState({broadcastSuccessMessage : 'Success! TXID: ' + JSON.stringify(result.result)})
this.setState({ broadcastErrorMessage: '' });
this.setState({
broadcastSuccessMessage:
'Success! TXID: ' + JSON.stringify(result.result),
});
}
}
render() {
const { navigate } = this.props.navigation;
@ -176,9 +205,16 @@ export default class SendCreate extends Component {
return (
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
<BlueSpacing />
<BlueCard title={"Replace Transaction"} style={{alignItems: 'center', flex: 1}}>
<BlueText>Error creating transaction. Invalid address or send amount?</BlueText>
<FormValidationMessage>{this.state.errorMessage}</FormValidationMessage>
<BlueCard
title={'Replace Transaction'}
style={{ alignItems: 'center', flex: 1 }}
>
<BlueText>
Error creating transaction. Invalid address or send amount?
</BlueText>
<FormValidationMessage>
{this.state.errorMessage}
</FormValidationMessage>
</BlueCard>
<BlueButton
onPress={() => this.props.navigation.goBack()}
@ -189,9 +225,7 @@ export default class SendCreate extends Component {
}
if (this.state.isLoading) {
return (
<BlueLoading/>
);
return <BlueLoading />;
}
if (this.state.nonReplaceable) {
@ -206,73 +240,68 @@ export default class SendCreate extends Component {
<BlueText h4>This transaction is not replaceable</BlueText>
<BlueButton
onPress={() =>
this.props.navigation.goBack()
}
onPress={() => this.props.navigation.goBack()}
title="Back"
/>
</SafeBlueArea>
);
}
return (
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
<BlueSpacing />
<BlueCard title={"Replace Transaction"} style={{alignItems: 'center', flex: 1}}>
<BlueText>This is transaction hex, signed and ready to be broadcast to the network. Continue?</BlueText>
<BlueCard
title={'Replace Transaction'}
style={{ alignItems: 'center', flex: 1 }}
>
<BlueText>
This is transaction hex, signed and ready to be broadcast to the
network. Continue?
</BlueText>
<TextInput
style={{borderColor: '#ebebeb', borderWidth: 1, marginTop:20, color:'#ebebeb'}}
style={{
borderColor: '#ebebeb',
borderWidth: 1,
marginTop: 20,
color: '#ebebeb',
}}
maxHeight={70}
multiline={true}
multiline
editable={false}
value={this.state.tx}
/>
<BlueSpacing20 />
<BlueText style={{paddingTop:20}} >To: {this.state.newDestinationAddress}</BlueText>
<BlueText style={{ paddingTop: 20 }}>
To: {this.state.newDestinationAddress}
</BlueText>
<BlueText>Amount: {this.state.amount} BTC</BlueText>
<BlueText>Fee: {this.state.fee} BTC</BlueText>
<BlueText>TX size: {this.state.size} Bytes</BlueText>
<BlueText>satoshiPerByte: {this.state.satoshiPerByte} Sat/B</BlueText>
</BlueCard>
<BlueButton
icon={{ name: 'megaphone', type: 'octicon' }}
onPress={() =>
this.broadcast()
}
onPress={() => this.broadcast()}
title="Broadcast"
/>
<BlueButton
icon={{ name: 'arrow-left', type: 'octicon' }}
onPress={() =>
this.props.navigation.goBack()
}
onPress={() => this.props.navigation.goBack()}
title="Go back"
/>
<FormValidationMessage>{this.state.broadcastErrorMessage}</FormValidationMessage>
<Text style={{padding:20, color:"#080"}}>{this.state.broadcastSuccessMessage}</Text>
<FormValidationMessage>
{this.state.broadcastErrorMessage}
</FormValidationMessage>
<Text style={{ padding: 20, color: '#080' }}>
{this.state.broadcastSuccessMessage}
</Text>
</SafeBlueArea>
);
}
}

View File

@ -1,23 +1,34 @@
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React, { Component } from 'react';
import { ActivityIndicator, TextInput, View } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { SafeAreaView, } from 'react-navigation';
import { Icon, Card, Header, } from 'react-native-elements'
import { List, Button, ListItem } from 'react-native-elements'
import { FormLabel, FormInput, Text, FormValidationMessage } from 'react-native-elements'
import { SafeAreaView } from 'react-navigation';
import { Icon, Card, Header } from 'react-native-elements';
import { List, Button, ListItem } from 'react-native-elements';
import {
BlueSpacing20, BlueList, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader,
BlueFormInput, BlueSpacing
} from '../../BlueComponents'
let EV = require('../../events')
FormLabel,
FormInput,
Text,
FormValidationMessage,
} from 'react-native-elements';
import {
BlueSpacing20,
BlueList,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
BlueFormInput,
BlueSpacing,
} from '../../BlueComponents';
let EV = require('../../events');
let BigNumber = require('bignumber.js');
let bitcoinjs = require('bitcoinjs-lib')
let bitcoinjs = require('bitcoinjs-lib');
export default class RBF extends Component {
static navigationOptions = {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
@ -26,34 +37,36 @@ export default class RBF extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
let txid
if (props.navigation.state.params) txid = props.navigation.state.params.txid
let txid;
if (props.navigation.state.params)
txid = props.navigation.state.params.txid;
let sourceWallet
let sourceTx
let sourceWallet;
let sourceTx;
for (let w of BlueApp.getWallets()) {
for (let t of w.getTransactions()) {
if (t.hash === txid) {
// found our source wallet
sourceWallet = w
sourceTx = t
console.log(t)
sourceWallet = w;
sourceTx = t;
console.log(t);
}
}
}
let destinationAddress
let destinationAddress;
for (let o of sourceTx.outputs) {
if (o.addresses[0] === sourceWallet.getAddress()) { // change
if (o.addresses[0] === sourceWallet.getAddress()) {
// change
// nop
} else { // DESTINATION address
destinationAddress = o.addresses[0]
console.log('dest = ', destinationAddress)
} else {
// DESTINATION address
destinationAddress = o.addresses[0];
console.log('dest = ', destinationAddress);
}
}
@ -61,7 +74,7 @@ export default class RBF extends Component {
this.state = {
isLoading: false,
nonReplaceable: true,
}
};
return;
}
@ -72,20 +85,19 @@ export default class RBF extends Component {
sourceWallet,
newDestinationAddress: destinationAddress,
feeDelta: '',
}
};
}
async componentDidMount() {
let startTime = Date.now()
console.log('send/details - componentDidMount')
let startTime = Date.now();
console.log('send/details - componentDidMount');
this.setState({
isLoading: false,
})
let endTime = Date.now()
console.log('componentDidMount took', (endTime-startTime)/1000, 'sec')
});
let endTime = Date.now();
console.log('componentDidMount took', (endTime - startTime) / 1000, 'sec');
}
createTransaction() {
this.props.navigation.navigate('CreateRBF', {
feeDelta: this.state.feeDelta,
@ -93,7 +105,7 @@ export default class RBF extends Component {
txid: this.state.txid,
sourceTx: this.state.sourceTx,
sourceWallet: this.state.sourceWallet,
})
});
}
render() {
@ -119,9 +131,7 @@ export default class RBF extends Component {
<BlueText h4>This transaction is not replaceable</BlueText>
<BlueButton
onPress={() =>
this.props.navigation.goBack()
}
onPress={() => this.props.navigation.goBack()}
title="Back"
/>
</SafeBlueArea>
@ -131,76 +141,67 @@ export default class RBF extends Component {
if (!this.state.sourceWallet.getAddress) {
return (
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
<BlueText>System error: Source wallet not found (this should never happen)</BlueText>
<BlueText>
System error: Source wallet not found (this should never happen)
</BlueText>
<BlueButton
onPress={() =>
this.props.navigation.goBack()
}
onPress={() => this.props.navigation.goBack()}
title="Back"
/>
</SafeBlueArea>
);
}
return (
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
<BlueSpacing />
<BlueCard title={"Replace By Fee"} style={{alignItems: 'center', flex: 1}}>
<BlueText>RBF allows you to increase fee on already sent but not confirmed transaction, thus speeding up mining</BlueText>
<BlueCard
title={'Replace By Fee'}
style={{ alignItems: 'center', flex: 1 }}
>
<BlueText>
RBF allows you to increase fee on already sent but not confirmed
transaction, thus speeding up mining
</BlueText>
<BlueSpacing20 />
<BlueText>From wallet '{this.state.sourceWallet.getLabel()}' ({this.state.sourceWallet.getAddress()})</BlueText>
<BlueText>
From wallet '{this.state.sourceWallet.getLabel()}' ({this.state.sourceWallet.getAddress()})
</BlueText>
<BlueSpacing20 />
<BlueFormInput
onChangeText={(text) => this.setState({newDestinationAddress: text})}
placeholder={"receiver address here"}
onChangeText={text =>
this.setState({ newDestinationAddress: text })
}
placeholder={'receiver address here'}
value={this.state.newDestinationAddress}
/>
<BlueFormInput onChangeText={(text) => this.setState({feeDelta: text})} keyboardType={"numeric"}
placeholder={"fee to add (in BTC)"}
<BlueFormInput
onChangeText={text => this.setState({ feeDelta: text })}
keyboardType={'numeric'}
placeholder={'fee to add (in BTC)'}
value={this.state.feeDelta + ''}
/>
</BlueCard>
<View style={{flex: 1, flexDirection: 'row', paddingTop:20,}}>
<View style={{ flex: 1, flexDirection: 'row', paddingTop: 20 }}>
<View style={{ flex: 0.33 }}>
<BlueButton
onPress={() =>
this.props.navigation.goBack()
}
onPress={() => this.props.navigation.goBack()}
title="Cancel"
/>
</View>
<View style={{flex: 0.33}}>
</View>
<View style={{ flex: 0.33 }} />
<View style={{ flex: 0.33 }}>
<BlueButton
onPress={() =>
this.createTransaction()
}
onPress={() => this.createTransaction()}
title="Create"
/>
</View>
</View>
</SafeBlueArea>
);
}
}

View File

@ -1,19 +1,26 @@
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React, { Component } from 'react';
import { ActivityIndicator, View } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { SafeAreaView, } from 'react-navigation';
import { Icon, Card, Header, } from 'react-native-elements'
import { List, Button, ListItem, Text } from 'react-native-elements'
import { SafeAreaView } from 'react-navigation';
import { Icon, Card, Header } from 'react-native-elements';
import { List, Button, ListItem, Text } from 'react-native-elements';
import {
BlueList, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader,
BlueSpacing, BlueLoading, BlueSpacing20
} from '../../BlueComponents'
BlueList,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
BlueSpacing,
BlueLoading,
BlueSpacing20,
} from '../../BlueComponents';
export default class TransactionsDetails extends Component {
static navigationOptions = {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
@ -22,66 +29,68 @@ export default class TransactionsDetails extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
let hash = props.navigation.state.params.hash
let foundTx = {}
let from = []
let to = []
let hash = props.navigation.state.params.hash;
let foundTx = {};
let from = [];
let to = [];
for (let tx of BlueApp.getTransactions()) {
if (tx.hash === hash) {
console.log(tx);
foundTx = tx;
for (let input of foundTx.inputs) {
from = from.concat(input.addresses)
from = from.concat(input.addresses);
}
for (let output of foundTx.outputs) {
to = to.concat(output.addresses)
to = to.concat(output.addresses);
}
}
}
this.state = {
isLoading: true,
tx: foundTx,
from,
to,
}
};
}
async componentDidMount() {
console.log('transactions/details - componentDidMount')
console.log('transactions/details - componentDidMount');
this.setState({
isLoading: false,
})
});
}
render() {
const { navigate } = this.props.navigation;
if (this.state.isLoading) {
return (
<BlueLoading/>
);
return <BlueLoading />;
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{flex: 1, paddingTop: 20}}>
<SafeBlueArea
forceInset={{ horizontal: 'always' }}
style={{ flex: 1, paddingTop: 20 }}
>
<BlueSpacing />
<BlueCard title={"Transaction details"} style={{alignItems: 'center', flex: 1}}>
<BlueCard
title={'Transaction details'}
style={{ alignItems: 'center', flex: 1 }}
>
{(() => {
let memo
let memo;
if (BlueApp.tx_metadata[this.state.tx.hash]) {
if (BlueApp.tx_metadata[this.state.tx.hash]['memo']) {
return (
<View>
<BlueText h4>{BlueApp.tx_metadata[this.state.tx.hash]['memo']}</BlueText>
<BlueText h4>
{BlueApp.tx_metadata[this.state.tx.hash]['memo']}
</BlueText>
<BlueSpacing20 />
</View>
);
@ -90,11 +99,14 @@ export default class TransactionsDetails extends Component {
})()}
<BlueText h4>From:</BlueText>
<BlueText style={{marginBottom: 10}}>{this.state.from.join(', ')}</BlueText>
<BlueText style={{ marginBottom: 10 }}>
{this.state.from.join(', ')}
</BlueText>
<BlueText h4>To:</BlueText>
<BlueText style={{marginBottom: 10}}>{this.state.to.join(', ')}</BlueText>
<BlueText style={{ marginBottom: 10 }}>
{this.state.to.join(', ')}
</BlueText>
<BlueText>Txid: {this.state.tx.hash}</BlueText>
<BlueText>received: {this.state.tx.received}</BlueText>
@ -103,18 +115,17 @@ export default class TransactionsDetails extends Component {
<BlueText>inputs: {this.state.tx.inputs.length}</BlueText>
<BlueText>outputs: {this.state.tx.outputs.length}</BlueText>
<BlueText style={{marginBottom: 10}}> </BlueText>
<BlueText style={{ marginBottom: 10 }} />
</BlueCard>
{(() => {
if (this.state.tx.confirmations === 0) {
return (
<BlueButton
onPress={() =>
this.props.navigation.navigate('RBF', {txid: this.state.tx.hash})
this.props.navigation.navigate('RBF', {
txid: this.state.tx.hash,
})
}
title="Replace-By-Fee (RBF)"
/>
@ -122,20 +133,12 @@ export default class TransactionsDetails extends Component {
}
})()}
<BlueButton
icon={{ name: 'arrow-left', type: 'octicon' }}
onPress={() =>
this.props.navigation.goBack()
}
onPress={() => this.props.navigation.goBack()}
title="Go back"
/>
</SafeBlueArea>
);
}
}

View File

@ -1,17 +1,33 @@
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React, { Component } from 'react';
import { ActivityIndicator, StyleSheet, ListView, Text, View } from 'react-native';
import {
ActivityIndicator,
StyleSheet,
ListView,
Text,
View,
} from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { SafeAreaView, TabNavigator } from 'react-navigation';
import { Card, Header, Icon, List, ListItem } from 'react-native-elements'
import { BlueLoading, BlueList, BlueListView, BlueSpacing, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader } from '../../BlueComponents'
let EV = require('../../events')
import { Card, Header, Icon, List, ListItem } from 'react-native-elements';
import {
BlueLoading,
BlueList,
BlueListView,
BlueSpacing,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
} from '../../BlueComponents';
let EV = require('../../events');
let ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
export default class TransactionsList extends Component {
static navigationOptions = {
tabBarLabel: 'Transactions',
tabBarIcon: ({ tintColor, focused }) => (
@ -21,35 +37,37 @@ export default class TransactionsList extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
this.state = {
isLoading: true,
}
};
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED, this.refreshFunction.bind(this))
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED, this.refreshFunction.bind(this));
}
async componentDidMount() {
console.log('transaction/list- componentDidMount')
this.refreshFunction()
console.log('transaction/list- componentDidMount');
this.refreshFunction();
} // end
refreshFunction() {
this.setState({
this.setState(
{
isLoading: true,
}, () => {
},
() => {
setTimeout(() => {
this.setState({
isLoading: false,
final_balance: BlueApp.getBalance(),
dataSource: ds.cloneWithRows(BlueApp.getTransactions()),
})
}, 1)
})
});
}, 1);
},
);
}
txMemo(hash) {
@ -60,83 +78,115 @@ export default class TransactionsList extends Component {
}
refresh() {
this.setState({
this.setState(
{
isLoading: true,
}, async function () {
let that = this
setTimeout( async function() { // more responsive
let noErr = true
},
async function() {
let that = this;
setTimeout(async function() {
// more responsive
let noErr = true;
try {
await BlueApp.fetchWalletTransactions()
await BlueApp.fetchWalletBalances()
await BlueApp.fetchWalletTransactions();
await BlueApp.fetchWalletBalances();
} catch (err) {
noErr = false
console.warn(err)
noErr = false;
console.warn(err);
}
if (noErr) await BlueApp.saveToDisk() // caching
EV(EV.enum.WALLETS_COUNT_CHANGED) // TODO: some other event type?
if (noErr) await BlueApp.saveToDisk(); // caching
EV(EV.enum.WALLETS_COUNT_CHANGED); // TODO: some other event type?
that.setState({
isLoading: false,
final_balance: BlueApp.getBalance(),
dataSource: ds.cloneWithRows(BlueApp.getTransactions()),
})
}, 10)
})
});
}, 10);
},
);
}
render() {
const { navigate } = this.props.navigation;
if (this.state.isLoading) {
return (
<BlueLoading/>
);
return <BlueLoading />;
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
<Header
backgroundColor={BlueApp.settings.brandingColor}
leftComponent={<Icon name='menu' color="#fff" onPress={() => this.props.navigation.navigate('DrawerToggle') }/>}
centerComponent={{ text: this.state.final_balance + ' BTC', style: { color: '#fff', fontSize: 25 } }}
rightComponent={<Icon name='refresh' color="#fff" onPress={() => this.refresh() }/>}
leftComponent={
<Icon
name="menu"
color="#fff"
onPress={() => this.props.navigation.navigate('DrawerToggle')}
/>
<BlueCard title='My Transactions' >
}
centerComponent={{
text: this.state.final_balance + ' BTC',
style: { color: '#fff', fontSize: 25 },
}}
rightComponent={
<Icon name="refresh" color="#fff" onPress={() => this.refresh()} />
}
/>
<BlueCard title="My Transactions">
<BlueText style={{ marginBottom: 10 }}>
A list of ingoing or outgoing transactions of your wallets
</BlueText>
<BlueList>
<ListView style={{ height: 360 }}
enableEmptySections={true}
<ListView
style={{ height: 360 }}
enableEmptySections
dataSource={this.state.dataSource}
renderRow={(rowData) => {
renderRow={rowData => {
return (
<BlueListItem
avatar={
<Icon color={(() => {return rowData.confirmations && (rowData.value < 0 && "#900" || "#080") || "#ebebeb" })()}
name={(() => {return rowData.value < 0 && 'call-made' || 'call-received'})()} />
}
title={rowData.value/100000000 + " BTC" + this.txMemo(rowData.hash)}
subtitle={rowData.received.replace(['T'], ' ').replace(['Z'], ' ').split('.')[0] + ' | conf: ' + rowData.confirmations + "\nYOLO"}
onPress={() =>
{
navigate('TransactionDetails', {hash: rowData.hash})
}
}
<Icon
color={(() => {
return (
(rowData.confirmations &&
((rowData.value < 0 && '#900') || '#080')) ||
'#ebebeb'
);
})()}
name={(() => {
return (
(rowData.value < 0 && 'call-made') ||
'call-received'
);
})()}
/>
)
}
title={
rowData.value / 100000000 +
' BTC' +
this.txMemo(rowData.hash)
}
subtitle={
rowData.received
.replace(['T'], ' ')
.replace(['Z'], ' ')
.split('.')[0] +
' | conf: ' +
rowData.confirmations +
'\nYOLO'
}
onPress={() => {
navigate('TransactionDetails', { hash: rowData.hash });
}}
/>
);
}}
/>
</BlueList>
</BlueCard>
</SafeBlueArea>
);
}
}

View File

@ -1,13 +1,26 @@
let BlueApp = require('../../BlueApp')
import { AppStorage, LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet } from '../../class'
let BlueApp = require('../../BlueApp');
import {
AppStorage,
LegacyWallet,
SegwitBech32Wallet,
SegwitP2SHWallet,
} from '../../class';
import React, { Component } from 'react';
import { ActivityIndicator, View } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { SafeAreaView, } from 'react-navigation';
import { Button } from 'react-native-elements'
import { Icon, Card, Header} from 'react-native-elements'
import { BlueSpacing, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader } from '../../BlueComponents'
let EV = require('../../events')
import { SafeAreaView } from 'react-navigation';
import { Button } from 'react-native-elements';
import { Icon, Card, Header } from 'react-native-elements';
import {
BlueSpacing,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
} from '../../BlueComponents';
let EV = require('../../events');
/*
<Button
@ -19,7 +32,6 @@ onPress={() => {
/> */
export default class WalletsAdd extends Component {
static navigationOptions = {
tabBarLabel: 'Wallets',
tabBarIcon: ({ tintColor, focused }) => (
@ -29,23 +41,21 @@ export default class WalletsAdd extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
this.state = {
isLoading: true,
}
};
}
async componentDidMount() {
this.setState({
isLoading: false,
})
});
}
render() {
const { navigate } = this.props.navigation;
@ -58,47 +68,53 @@ export default class WalletsAdd extends Component {
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{flex: 1, paddingTop: 40}}>
<SafeBlueArea
forceInset={{ horizontal: 'always' }}
style={{ flex: 1, paddingTop: 40 }}
>
<BlueSpacing />
<BlueCard title="Add Wallet">
<BlueText>You can either scan backup paper wallet (in WIF - Wallet Import Format), or create a new wallet. Segwit wallets supported by default.</BlueText>
<BlueText>
You can either scan backup paper wallet (in WIF - Wallet Import
Format), or create a new wallet. Segwit wallets supported by
default.
</BlueText>
<BlueButton
large icon={{name: 'qrcode', type: 'font-awesome'}} title='Scan'
large
icon={{ name: 'qrcode', type: 'font-awesome' }}
title="Scan"
onPress={() => {
this.props.navigation.navigate('ScanQrWifSegwitP2SHAddress')
this.props.navigation.navigate('ScanQrWifSegwitP2SHAddress');
}}
/>
<BlueButton
large icon={{name: 'bitcoin', type: 'font-awesome'}} title='Create'
large
icon={{ name: 'bitcoin', type: 'font-awesome' }}
title="Create"
onPress={() => {
this.props.navigation.goBack()
this.props.navigation.goBack();
setTimeout(async () => {
let w = new SegwitP2SHWallet()
w.setLabel('New SegWit')
w.generate()
BlueApp.wallets.push(w)
await BlueApp.saveToDisk()
EV(EV.enum.WALLETS_COUNT_CHANGED)
}, 1)
}
}
let w = new SegwitP2SHWallet();
w.setLabel('New SegWit');
w.generate();
BlueApp.wallets.push(w);
await BlueApp.saveToDisk();
EV(EV.enum.WALLETS_COUNT_CHANGED);
}, 1);
}}
/>
</BlueCard>
<BlueButton
icon={{ name: 'arrow-left', type: 'octicon' }}
title='Go Back'
title="Go Back"
onPress={() => {
this.props.navigation.goBack()
}
}
this.props.navigation.goBack();
}}
/>
</SafeBlueArea>
);
}
}

View File

@ -1,22 +1,29 @@
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React, { Component } from 'react';
import { ActivityIndicator, TextInput, View } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { SafeAreaView, } from 'react-navigation';
import { Icon, Card, Header, } from 'react-native-elements'
import { List, Button, ListItem } from 'react-native-elements'
import { FormInput, Text, FormValidationMessage } from 'react-native-elements'
import { SafeAreaView } from 'react-navigation';
import { Icon, Card, Header } from 'react-native-elements';
import { List, Button, ListItem } from 'react-native-elements';
import { FormInput, Text, FormValidationMessage } from 'react-native-elements';
import {
BlueSpacing, BlueFormInput, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader,
BlueFormLabel, BlueListView
} from '../../BlueComponents'
let EV = require('../../events')
BlueSpacing,
BlueFormInput,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
BlueFormLabel,
BlueListView,
} from '../../BlueComponents';
let EV = require('../../events');
let BigNumber = require('bignumber.js');
export default class WalletDetails extends Component {
static navigationOptions = {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
@ -25,44 +32,43 @@ export default class WalletDetails extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
let address = props.navigation.state.params.address
let address = props.navigation.state.params.address;
/** @type {AbstractWallet} */
let wallet
let wallet;
for (let w of BlueApp.getWallets()) {
if (w.getAddress() === address) { // found our wallet
wallet = w
if (w.getAddress() === address) {
// found our wallet
wallet = w;
}
}
this.state = {
confirmDelete: false,
isLoading: true,
wallet
}
wallet,
};
}
async componentDidMount() {
this.setState({
isLoading: false,
})
});
}
async setLabel(text) {
this.state.wallet.label = text
this.state.wallet.label = text;
this.setState({
labelChanged: true
}) /* also, a hack to make screen update new typed text*/
labelChanged: true,
}); /* also, a hack to make screen update new typed text */
}
render() {
const { navigate } = this.props.navigation;
@ -74,49 +80,53 @@ export default class WalletDetails extends Component {
);
}
return (
<SafeBlueArea style={{flex: 1, paddingTop: 20,}}>
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
<BlueSpacing />
<BlueCard title={"Wallet Details"} style={{alignItems: 'center', flex: 1}}>
<BlueCard
title={'Wallet Details'}
style={{ alignItems: 'center', flex: 1 }}
>
<BlueFormLabel>Address:</BlueFormLabel>
<BlueFormInput value={this.state.wallet.getAddress()} editable={false} />
<BlueFormInput
value={this.state.wallet.getAddress()}
editable={false}
/>
<BlueFormLabel>Type:</BlueFormLabel>
<BlueFormInput value={this.state.wallet.getTypeReadable()} editable={false} />
<BlueFormInput
value={this.state.wallet.getTypeReadable()}
editable={false}
/>
<BlueFormLabel>Label:</BlueFormLabel>
<BlueFormInput
value={this.state.wallet.getLabel()}
onChangeText={(text) => {this.setLabel(text)}}
onChangeText={text => {
this.setLabel(text);
}}
/>
</BlueCard>
{(() => {
if (this.state.confirmDelete) {
return (
<View style={{alignItems: 'center',}}>
<View style={{ alignItems: 'center' }}>
<BlueText h4>Are you sure?</BlueText>
<BlueButton
icon={{ name: 'stop', type: 'octicon' }}
onPress={async () =>
{
BlueApp.deleteWallet(this.state.wallet)
await BlueApp.saveToDisk()
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED)
EV(EV.enum.WALLETS_COUNT_CHANGED)
this.props.navigation.goBack()
}
}
onPress={async () => {
BlueApp.deleteWallet(this.state.wallet);
await BlueApp.saveToDisk();
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
EV(EV.enum.WALLETS_COUNT_CHANGED);
this.props.navigation.goBack();
}}
title="Yes, delete"
/>
<BlueButton
onPress={async () => {
this.setState({confirmDelete : false})
this.setState({ confirmDelete: false });
}}
title="No, cancel"
/>
@ -128,45 +138,34 @@ export default class WalletDetails extends Component {
<BlueButton
icon={{ name: 'stop', type: 'octicon' }}
onPress={async () => {
this.setState({confirmDelete : true})
this.setState({ confirmDelete: true });
}}
title="Delete this wallet"
/>
<BlueButton
onPress={() =>
this.props.navigation.navigate('WalletExport', {address: this.state.wallet.getAddress()} )
this.props.navigation.navigate('WalletExport', {
address: this.state.wallet.getAddress(),
})
}
title="Export / backup"
/>
<BlueButton
icon={{ name: 'arrow-left', type: 'octicon' }}
onPress={async () =>
{
onPress={async () => {
if (this.state.labelChanged) {
await BlueApp.saveToDisk()
EV(EV.enum.WALLETS_COUNT_CHANGED) // TODO: some other event type?
}
this.props.navigation.goBack()
}
await BlueApp.saveToDisk();
EV(EV.enum.WALLETS_COUNT_CHANGED); // TODO: some other event type?
}
this.props.navigation.goBack();
}}
title="Go back"
/>
</View>
)
);
}
})()}
</SafeBlueArea>
);
}
}

View File

@ -1,19 +1,32 @@
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React, { Component } from 'react';
import { ActivityIndicator, TextInput, View } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { SafeAreaView, } from 'react-navigation';
import { Icon, Card, Header, } from 'react-native-elements'
import { List, Button, ListItem } from 'react-native-elements'
import { Divider, FormLabel, FormInput, Text, FormValidationMessage } from 'react-native-elements'
import { SafeAreaView } from 'react-navigation';
import { Icon, Card, Header } from 'react-native-elements';
import { List, Button, ListItem } from 'react-native-elements';
import {
Divider,
FormLabel,
FormInput,
Text,
FormValidationMessage,
} from 'react-native-elements';
import QRCode from 'react-native-qrcode';
import { BlueSpacing, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader } from '../../BlueComponents'
let EV = require('../../events')
import {
BlueSpacing,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
} from '../../BlueComponents';
let EV = require('../../events');
let BigNumber = require('bignumber.js');
export default class WalletExport extends Component {
static navigationOptions = {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
@ -22,34 +35,33 @@ export default class WalletExport extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
let address = props.navigation.state.params.address
let wallet
let address = props.navigation.state.params.address;
let wallet;
for (let w of BlueApp.getWallets()) {
if (w.getAddress() === address) { // found our wallet
wallet = w
if (w.getAddress() === address) {
// found our wallet
wallet = w;
}
}
this.state = {
isLoading: true,
wallet
}
wallet,
};
}
async componentDidMount() {
this.setState({
isLoading: false,
})
});
}
render() {
const { navigate } = this.props.navigation;
@ -74,8 +86,10 @@ export default class WalletExport extends Component {
return (
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
<BlueSpacing />
<BlueCard title={"Wallet Export"} style={{alignItems: 'center', flex: 1}}>
<BlueCard
title={'Wallet Export'}
style={{ alignItems: 'center', flex: 1 }}
>
<BlueText>Address: {this.state.wallet.getAddress()}</BlueText>
<BlueText>WIF: {this.state.wallet.getSecret()}</BlueText>
@ -83,22 +97,16 @@ export default class WalletExport extends Component {
value={this.state.wallet.getSecret()}
size={312}
bgColor={'white'}
fgColor={BlueApp.settings.brandingColor}/>
fgColor={BlueApp.settings.brandingColor}
/>
</BlueCard>
<BlueButton
icon={{ name: 'arrow-left', type: 'octicon' }}
onPress={() =>
this.props.navigation.goBack()
}
onPress={() => this.props.navigation.goBack()}
title="Go back"
/>
</SafeBlueArea>
);
}
}

View File

@ -1,21 +1,33 @@
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React, { Component } from 'react';
import { ActivityIndicator, StyleSheet, ListView, Text, View } from 'react-native';
import {
ActivityIndicator,
StyleSheet,
ListView,
Text,
View,
} from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import {
BlueLoading, BlueSpacing20, BlueList, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueListItem, BlueHeader,
BlueFormInput, BlueSpacing
} from '../../BlueComponents'
import { Icon,Header, List, ListItem, Avatar } from 'react-native-elements'
let EV = require('../../events')
BlueLoading,
BlueSpacing20,
BlueList,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
BlueFormInput,
BlueSpacing,
} from '../../BlueComponents';
import { Icon, Header, List, ListItem, Avatar } from 'react-native-elements';
let EV = require('../../events');
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2,});
let ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
export default class WalletsList extends Component {
static navigationOptions = {
tabBarLabel: 'Wallets',
tabBarIcon: ({ tintColor, focused }) => (
@ -25,92 +37,102 @@ export default class WalletsList extends Component {
style={{ color: tintColor }}
/>
),
}
};
constructor(props) {
super(props);
this.state = {
isLoading: true,
}
EV(EV.enum.WALLETS_COUNT_CHANGED, this.refreshFunction.bind(this))
};
EV(EV.enum.WALLETS_COUNT_CHANGED, this.refreshFunction.bind(this));
}
async componentDidMount() {
console.log('wallets/list - componentDidMount')
this.refreshFunction()
console.log('wallets/list - componentDidMount');
this.refreshFunction();
} // end of componendDidMount
refreshFunction() {
this.setState({
this.setState(
{
isLoading: true,
}, () => {
},
() => {
setTimeout(() => {
this.setState({
isLoading: false,
dataSource: ds.cloneWithRows(BlueApp.getWallets()),
})
}, 1)
})
});
}, 1);
},
);
}
render() {
const { navigate } = this.props.navigation;
if (this.state.isLoading) {
return (
<BlueLoading/>
);
return <BlueLoading />;
}
return (
<SafeBlueArea>
<BlueHeader
leftComponent={<Icon name='menu' color="#fff" onPress={() => this.props.navigation.navigate('DrawerToggle') }/>}
centerComponent={{ text: 'Blue Wallet', style: { color: '#fff', fontSize: 25 }}}
leftComponent={
<Icon
name="menu"
color="#fff"
onPress={() => this.props.navigation.navigate('DrawerToggle')}
/>
<BlueCard title='My Bitcoin Wallets' >
}
centerComponent={{
text: 'Blue Wallet',
style: { color: '#fff', fontSize: 25 },
}}
/>
<BlueCard title="My Bitcoin Wallets">
<BlueText style={{ marginBottom: 10 }}>
A wallet represents a pair of a secret (private key) and an address you can share to receive coins.
A wallet represents a pair of a secret (private key) and an address
you can share to receive coins.
</BlueText>
<BlueList>
<ListView
enableEmptySections={true}
enableEmptySections
maxHeight={290}
dataSource={this.state.dataSource}
renderRow={(rowData) => {
renderRow={rowData => {
return (
<BlueListItem
onPress={() =>
{
navigate('WalletDetails', {address : rowData.getAddress()})
onPress={() => {
navigate('WalletDetails', {
address: rowData.getAddress(),
});
}}
leftIcon={{name: 'bitcoin', type: 'font-awesome', color: '#fff'}}
title={rowData.getLabel() + ' | ' + rowData.getBalance() + " BTC" }
leftIcon={{
name: 'bitcoin',
type: 'font-awesome',
color: '#fff',
}}
title={
rowData.getLabel() + ' | ' + rowData.getBalance() + ' BTC'
}
subtitle={rowData.getShortAddress()}
hideChevron={false}
/>
)
}
}
);
}}
/>
</BlueList>
</BlueCard>
<BlueButton
icon={{ name: 'plus-small', type: 'octicon' }}
onPress={() => {
navigate('AddWallet')
navigate('AddWallet');
}}
title="Add Wallet"
/>
</SafeBlueArea>
);
}

View File

@ -1,13 +1,18 @@
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React from 'react';
import { Text, ActivityIndicator, Button, View, TouchableOpacity } from 'react-native';
import {
Text,
ActivityIndicator,
Button,
View,
TouchableOpacity,
} from 'react-native';
import { Camera, Permissions } from 'expo';
import { AppStorage, LegacyWallet } from '../../class'
import { AppStorage, LegacyWallet } from '../../class';
import Ionicons from 'react-native-vector-icons/Ionicons';
let EV = require('../../events')
let EV = require('../../events');
export default class CameraExample extends React.Component {
static navigationOptions = {
tabBarLabel: 'Wallets',
tabBarIcon: ({ tintColor, focused }) => (
@ -17,8 +22,7 @@ export default class CameraExample extends React.Component {
style={{ color: tintColor }}
/>
),
}
};
state = {
isLoading: false,
@ -27,29 +31,36 @@ export default class CameraExample extends React.Component {
};
async onBarCodeRead(ret) {
for (let w of BlueApp.wallets) { // lookig for duplicates
for (let w of BlueApp.wallets) {
// lookig for duplicates
if (w.getSecret() === ret.data) {
return // duplicate, not adding
return; // duplicate, not adding
}
}
let newWallet = new LegacyWallet()
newWallet.setSecret(ret.data)
let newWallet = new LegacyWallet();
newWallet.setSecret(ret.data);
if (newWallet.getAddress() === false) return; // bad WIF
this.setState({
isLoading: true
}, async () => {
newWallet.setLabel('NEW2')
BlueApp.wallets.push(newWallet)
await BlueApp.saveToDisk()
this.props.navigation.navigate('WalletsList')
EV(EV.enum.WALLETS_COUNT_CHANGED)
alert('Imported WIF ' + ret.data + ' with address ' + newWallet.getAddress())
})
this.setState(
{
isLoading: true,
},
async () => {
newWallet.setLabel('NEW2');
BlueApp.wallets.push(newWallet);
await BlueApp.saveToDisk();
this.props.navigation.navigate('WalletsList');
EV(EV.enum.WALLETS_COUNT_CHANGED);
alert(
'Imported WIF ' +
ret.data +
' with address ' +
newWallet.getAddress(),
);
},
);
} // end
async componentWillMount() {
@ -64,7 +75,6 @@ export default class CameraExample extends React.Component {
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
@ -81,14 +91,18 @@ export default class CameraExample extends React.Component {
} else {
return (
<View style={{ flex: 1 }}>
<Camera style={{ flex: 1 }} type={this.state.type}
onBarCodeRead={this.onBarCodeRead.bind(this)}>
<Camera
style={{ flex: 1 }}
type={this.state.type}
onBarCodeRead={this.onBarCodeRead.bind(this)}
>
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
}}>
}}
>
<TouchableOpacity
style={{
flex: 0.2,
@ -97,17 +111,17 @@ export default class CameraExample extends React.Component {
}}
onPress={() => {
this.setState({
type: this.state.type === Camera.Constants.Type.back
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()
}
title="Go back"
onPress={() => this.props.navigation.goBack()}
/>
</TouchableOpacity>
</View>

View File

@ -1,13 +1,18 @@
let BlueApp = require('../../BlueApp')
let BlueApp = require('../../BlueApp');
import React from 'react';
import { Text, ActivityIndicator, Button, View, TouchableOpacity } from 'react-native';
import {
Text,
ActivityIndicator,
Button,
View,
TouchableOpacity,
} from 'react-native';
import { Camera, Permissions } from 'expo';
import { AppStorage, LegacyWallet, SegwitP2SHWallet } from '../../class'
import { AppStorage, LegacyWallet, SegwitP2SHWallet } from '../../class';
import Ionicons from 'react-native-vector-icons/Ionicons';
let EV = require('../../events')
let EV = require('../../events');
export default class CameraExample extends React.Component {
static navigationOptions = {
tabBarLabel: 'Wallets',
tabBarIcon: ({ tintColor, focused }) => (
@ -17,8 +22,7 @@ export default class CameraExample extends React.Component {
style={{ color: tintColor }}
/>
),
}
};
state = {
isLoading: false,
@ -27,27 +31,36 @@ export default class CameraExample extends React.Component {
};
async onBarCodeRead(ret) {
for (let w of BlueApp.wallets) { // lookig for duplicates
for (let w of BlueApp.wallets) {
// lookig for duplicates
if (w.getSecret() === ret.data) {
return // duplicate, not adding
return; // duplicate, not adding
}
}
let newWallet = new SegwitP2SHWallet()
newWallet.setSecret(ret.data)
let newWallet = new SegwitP2SHWallet();
newWallet.setSecret(ret.data);
if (newWallet.getAddress() === false) return; // bad WIF
this.setState({
isLoading: true
}, async () => {
newWallet.setLabel('New SegWit')
BlueApp.wallets.push(newWallet)
await BlueApp.saveToDisk()
this.props.navigation.navigate('WalletsList')
EV(EV.enum.WALLETS_COUNT_CHANGED)
alert('Imported WIF ' + ret.data + ' with address ' + newWallet.getAddress())
})
this.setState(
{
isLoading: true,
},
async () => {
newWallet.setLabel('New SegWit');
BlueApp.wallets.push(newWallet);
await BlueApp.saveToDisk();
this.props.navigation.navigate('WalletsList');
EV(EV.enum.WALLETS_COUNT_CHANGED);
alert(
'Imported WIF ' +
ret.data +
' with address ' +
newWallet.getAddress(),
);
},
);
} // end
async componentWillMount() {
@ -62,7 +75,6 @@ export default class CameraExample extends React.Component {
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
@ -79,14 +91,18 @@ export default class CameraExample extends React.Component {
} else {
return (
<View style={{ flex: 1 }}>
<Camera style={{ flex: 1 }} type={this.state.type}
onBarCodeRead={this.onBarCodeRead.bind(this)}>
<Camera
style={{ flex: 1 }}
type={this.state.type}
onBarCodeRead={this.onBarCodeRead.bind(this)}
>
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
}}>
}}
>
<TouchableOpacity
style={{
flex: 0.2,
@ -95,17 +111,17 @@ export default class CameraExample extends React.Component {
}}
onPress={() => {
this.setState({
type: this.state.type === Camera.Constants.Type.back
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()
}
title="Go back"
onPress={() => this.props.navigation.goBack()}
/>
</TouchableOpacity>
</View>

20
shim.js
View File

@ -1,23 +1,23 @@
/* global __DEV__, localStorage */
if (typeof __dirname === 'undefined') global.__dirname = '/'
if (typeof __filename === 'undefined') global.__filename = ''
if (typeof __dirname === 'undefined') global.__dirname = '/';
if (typeof __filename === 'undefined') global.__filename = '';
if (typeof process === 'undefined') {
global.process = require('process')
global.process = require('process');
} else {
const bProcess = require('process')
const bProcess = require('process');
for (var p in bProcess) {
if (!(p in process)) {
process[p] = bProcess[p]
process[p] = bProcess[p];
}
}
}
process.browser = false
if (typeof Buffer === 'undefined') global.Buffer = require('buffer').Buffer
process.browser = false;
if (typeof Buffer === 'undefined') global.Buffer = require('buffer').Buffer;
// global.location = global.location || { port: 80 }
const isDev = typeof __DEV__ === 'boolean' && __DEV__
process.env['NODE_ENV'] = isDev ? 'development' : 'production'
const isDev = typeof __DEV__ === 'boolean' && __DEV__;
process.env['NODE_ENV'] = isDev ? 'development' : 'production';
if (typeof localStorage !== 'undefined') {
localStorage.debug = isDev ? '*' : ''
localStorage.debug = isDev ? '*' : '';
}