Merge branch 'master' into DK-danish-language-support

This commit is contained in:
Marcos Rodriguez Vélez 2019-01-06 11:38:54 -05:00 committed by GitHub
commit d4666e7b94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 2633 additions and 798 deletions

1
App.js
View file

@ -1,7 +1,6 @@
import React from 'react';
import { Linking } from 'react-native';
import { NavigationActions } from 'react-navigation';
import MainBottomTabs from './MainBottomTabs';
export default class App extends React.Component {

View file

@ -6,6 +6,7 @@ import Settings from './screen/settings/settings';
import Selftest from './screen/selftest';
import { BlueHeader } from './BlueComponents';
import MockStorage from './MockStorage';
import { FiatUnit } from './models/fiatUnit';
global.crypto = require('crypto'); // shall be used by tests under nodejs CLI, but not in RN environment
let assert = require('assert');
jest.mock('react-native-custom-qr-codes', () => 'Video');
@ -74,7 +75,7 @@ it('BlueHeader works', () => {
expect(rendered).toBeTruthy();
});
it.skip('Settings work', () => {
it('Settings work', () => {
const rendered = TestRenderer.create(<Settings />).toJSON();
expect(rendered).toBeTruthy();
});
@ -302,10 +303,24 @@ describe('currency', () => {
AsyncStorage.storageCache = {}; // cleanup from other tests
let currency = require('./currency');
await currency.startUpdater();
let cur = AsyncStorage.storageCache[AppStorage.CURRENCY];
let cur = AsyncStorage.storageCache[AppStorage.EXCHANGE_RATES];
cur = JSON.parse(cur);
assert.ok(Number.isInteger(cur[currency.STRUCT.LAST_UPDATED]));
assert.ok(cur[currency.STRUCT.LAST_UPDATED] > 0);
assert.ok(cur[currency.STRUCT.BTC_USD] > 0);
assert.ok(cur['BTC_USD'] > 0);
// now, setting other currency as default
AsyncStorage.storageCache[AppStorage.PREFERRED_CURRENCY] = JSON.stringify(FiatUnit.JPY);
await currency.startUpdater();
cur = JSON.parse(AsyncStorage.storageCache[AppStorage.EXCHANGE_RATES]);
assert.ok(cur['BTC_JPY'] > 0);
// now setting with a proper setter
await currency.setPrefferedCurrency(FiatUnit.EUR);
await currency.startUpdater();
let preferred = await currency.getPreferredCurrency();
assert.equal(preferred.endPointKey, 'EUR');
cur = JSON.parse(AsyncStorage.storageCache[AppStorage.EXCHANGE_RATES]);
assert.ok(cur['BTC_EUR'] > 0);
});
});

View file

@ -536,6 +536,12 @@ const stylesBlueIcon = StyleSheet.create({
backgroundColor: '#d2f8d6',
transform: [{ rotate: '-45deg' }],
},
ballIncommingWithoutRotate: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#d2f8d6',
},
ballReceive: {
width: 30,
height: 30,
@ -550,6 +556,12 @@ const stylesBlueIcon = StyleSheet.create({
backgroundColor: '#f8d2d2',
transform: [{ rotate: '225deg' }],
},
ballOutgoingWithoutRotate: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#f8d2d2',
},
ballTransparrent: {
width: 30,
height: 30,
@ -622,6 +634,20 @@ export class BlueTransactionPendingIcon extends Component {
}
}
export class BlueTransactionExpiredIcon extends Component {
render() {
return (
<View {...this.props}>
<View style={stylesBlueIcon.boxIncomming}>
<View style={stylesBlueIcon.ballOutgoingWithoutRotate}>
<Icon {...this.props} name="hourglass-end" size={16} type="font-awesome" color="#d0021b" iconStyle={{ left: 0, top: 6 }} />
</View>
</View>
</View>
);
}
}
export class BlueTransactionOnchainIcon extends Component {
render() {
return (
@ -648,15 +674,8 @@ export class BlueTransactionOffchainIcon extends Component {
return (
<View {...this.props}>
<View style={stylesBlueIcon.boxIncomming}>
<View style={stylesBlueIcon.ballOutgoing}>
<Icon
{...this.props}
name="bolt"
size={16}
type="font-awesome"
color="#d0021b"
iconStyle={{ left: 0, top: 7, transform: [{ rotate: '155deg' }] }}
/>
<View style={stylesBlueIcon.ballOutgoingWithoutRotate}>
<Icon {...this.props} name="bolt" size={16} type="font-awesome" color="#d0021b" iconStyle={{ left: 0, top: 7 }} />
</View>
</View>
</View>
@ -669,15 +688,8 @@ export class BlueTransactionOffchainIncomingIcon extends Component {
return (
<View {...this.props}>
<View style={stylesBlueIcon.boxIncomming}>
<View style={stylesBlueIcon.ballIncomming}>
<Icon
{...this.props}
name="bolt"
size={16}
type="font-awesome"
color="#37c0a1"
iconStyle={{ left: 0, top: 7, transform: [{ rotate: '45deg' }] }}
/>
<View style={stylesBlueIcon.ballIncommingWithoutRotate}>
<Icon {...this.props} name="bolt" size={16} type="font-awesome" color="#37c0a1" iconStyle={{ left: 0, top: 7 }} />
</View>
</View>
</View>
@ -705,28 +717,26 @@ export class BlueReceiveButtonIcon extends Component {
render() {
return (
<TouchableOpacity {...this.props}>
<View>
<View
style={{
flex: 1,
flexDirection: 'row',
minWidth: 110,
minHeight: 40,
position: 'relative',
backgroundColor: '#ccddf9',
alignItems: 'center',
}}
>
<View
style={{
flex: 1,
minWidth: 130,
backgroundColor: '#ccddf9',
}}
>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
<View
style={{
minWidth: 30,
minHeight: 30,
left: 5,
backgroundColor: 'transparent',
transform: [{ rotate: '-45deg' }],
alignItems: 'center',
marginBottom: -11,
}}
>
<Icon {...this.props} name="arrow-down" size={16} type="font-awesome" color="#2f5fb3" iconStyle={{ left: 5, top: 12 }} />
<Icon {...this.props} name="arrow-down" size={16} type="font-awesome" color="#2f5fb3" />
</View>
<Text
style={{
@ -737,7 +747,7 @@ export class BlueReceiveButtonIcon extends Component {
backgroundColor: 'transparent',
}}
>
{loc.receive.header.toLowerCase()}
{loc.receive.header}
</Text>
</View>
</View>
@ -750,18 +760,15 @@ export class BlueSendButtonIcon extends Component {
render() {
return (
<TouchableOpacity {...this.props}>
<View>
<View
style={{
flex: 1,
flexDirection: 'row',
width: 110,
height: 40,
backgroundColor: '#ccddf9',
alignItems: 'center',
paddingLeft: 15,
}}
>
<View
style={{
flex: 1,
minWidth: 130,
backgroundColor: '#ccddf9',
alignItems: 'center',
}}
>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
<View
style={{
minWidth: 30,
@ -769,9 +776,10 @@ export class BlueSendButtonIcon extends Component {
left: 5,
backgroundColor: 'transparent',
transform: [{ rotate: '225deg' }],
marginBottom: 11,
}}
>
<Icon {...this.props} name="arrow-down" size={16} type="font-awesome" color="#2f5fb3" iconStyle={{ left: 2, top: 6 }} />
<Icon {...this.props} name="arrow-down" size={16} type="font-awesome" color="#2f5fb3" />
</View>
<Text
style={{
@ -781,7 +789,7 @@ export class BlueSendButtonIcon extends Component {
backgroundColor: 'transparent',
}}
>
{loc.send.header.toLowerCase()}
{loc.send.header}
</Text>
</View>
</View>
@ -794,22 +802,21 @@ export class ManageFundsBigButton extends Component {
render() {
return (
<TouchableOpacity {...this.props}>
<View>
<View
style={{
flex: 1,
flexDirection: 'row',
minWidth: 160,
minHeight: 40,
backgroundColor: '#ccddf9',
alignItems: 'center',
}}
>
<View
style={{
flex: 1,
width: 168,
backgroundColor: '#ccddf9',
}}
>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
<View
style={{
minWidth: 30,
minHeight: 30,
right: 5,
backgroundColor: 'transparent',
transform: [{ rotate: '90deg' }],
marginHorizontal: 10,
}}
>
<Icon {...this.props} name="link" size={16} type="font-awesome" color="#2f5fb3" />
@ -1109,47 +1116,65 @@ export class BlueBitcoinAmount extends Component {
amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
onChangeText: PropTypes.func,
disabled: PropTypes.bool,
unit: PropTypes.string,
};
static defaultProps = {
unit: BitcoinUnit.BTC,
};
render() {
const amount = typeof this.props.amount === 'number' ? this.props.amount.toString() : this.props.amount;
return (
<View>
<View style={{ flexDirection: 'row', justifyContent: 'center', paddingTop: 16, paddingBottom: 16 }}>
<TextInput
keyboardType="numeric"
onChangeText={text => this.props.onChangeText(text.replace(',', '.'))}
placeholder="0"
maxLength={10}
editable={!this.props.isLoading && !this.props.disabled}
value={amount}
placeholderTextColor={this.props.disabled ? '#99a0ab' : '#0f5cc0'}
style={{
color: this.props.disabled ? '#99a0ab' : '#0f5cc0',
fontSize: 36,
fontWeight: '600',
}}
/>
<Text
style={{
color: this.props.disabled ? '#99a0ab' : '#0f5cc0',
fontSize: 16,
marginHorizontal: 4,
paddingBottom: 6,
fontWeight: '600',
alignSelf: 'flex-end',
}}
>
{' ' + BitcoinUnit.BTC}
</Text>
<TouchableWithoutFeedback disabled={this.props.pointerEvents === 'none'} onPress={() => this.textInput.focus()}>
<View>
<View style={{ flexDirection: 'row', justifyContent: 'center', paddingTop: 16, paddingBottom: 16 }}>
<TextInput
keyboardType="numeric"
onChangeText={text =>
this.props.onChangeText(
this.props.unit === BitcoinUnit.BTC
? text.replace(new RegExp('[^0-9.]'), '', '.')
: text.replace(new RegExp('[^0-9]'), ''),
)
}
placeholder="0"
maxLength={10}
ref={textInput => (this.textInput = textInput)}
editable={!this.props.isLoading && !this.props.disabled}
value={amount}
placeholderTextColor={this.props.disabled ? '#99a0ab' : '#0f5cc0'}
style={{
color: this.props.disabled ? '#99a0ab' : '#0f5cc0',
fontSize: 36,
fontWeight: '600',
}}
{...this.props}
/>
<Text
style={{
color: this.props.disabled ? '#99a0ab' : '#0f5cc0',
fontSize: 16,
marginHorizontal: 4,
paddingBottom: 6,
fontWeight: '600',
alignSelf: 'flex-end',
}}
>
{' ' + this.props.unit}
</Text>
</View>
<View style={{ alignItems: 'center', marginBottom: 22, marginTop: 4 }}>
<Text style={{ fontSize: 18, color: '#d4d4d4', fontWeight: '600' }}>
{loc.formatBalance(
this.props.unit === BitcoinUnit.BTC ? amount || 0 : loc.formatBalanceWithoutSuffix(amount || 0, BitcoinUnit.BTC),
BitcoinUnit.LOCAL_CURRENCY,
)}
</Text>
</View>
</View>
<View style={{ alignItems: 'center', marginBottom: 22, marginTop: 4 }}>
<Text style={{ fontSize: 18, color: '#d4d4d4', fontWeight: '600' }}>
{loc.formatBalance(amount || 0, BitcoinUnit.LOCAL_CURRENCY)}
</Text>
</View>
</View>
</TouchableWithoutFeedback>
);
}
}

View file

@ -10,6 +10,9 @@ it('can convert witness to address', () => {
address = SegwitBech32Wallet.witnessToAddress('035c618df829af694cb99e664ce1b34f80ad2c3b49bcd0d9c0b1836c66b2d25fd8');
assert.equal(address, 'bc1quhnve8q4tk3unhmjts7ymxv8cd6w9xv8wy29uv');
address = SegwitBech32Wallet.scriptPubKeyToAddress('00144d757460da5fcaf84cc22f3847faaa1078e84f6a');
assert.equal(address, 'bc1qf46hgcx6tl90snxz9uuy0742zpuwsnm27ysdh7');
});
it('can create a Segwit HD (BIP49)', async function() {

View file

@ -6,12 +6,6 @@ let assert = require('assert');
describe('LightningCustodianWallet', () => {
let l1 = new LightningCustodianWallet();
it.skip('can issue wallet credentials', async () => {
let l0 = new LightningCustodianWallet();
await l0.createAccount();
console.log(l0.getSecret());
});
it('can create, auth and getbtc', async () => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
assert.ok(l1.refill_addressess.length === 0);
@ -207,7 +201,12 @@ describe('LightningCustodianWallet', () => {
assert.ok(invoices2[0].description);
assert.equal(invoices2[0].description, 'test memo');
assert.ok(invoices2[0].payment_request);
assert.ok(invoices2[0].timestamp);
assert.ok(invoices2[0].expire_time);
assert.equal(invoices2[0].amt, 1);
for (let inv of invoices2) {
assert.equal(inv.type, 'user_invoice');
}
await lOld.fetchBalance();
let oldBalance = lOld.balance;
@ -248,4 +247,69 @@ describe('LightningCustodianWallet', () => {
assert.equal(lOld.balance - oldBalance, 1);
assert.equal(lNew.balance, 0);
});
it('can pay free amount (tip) invoice', async function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
if (!process.env.BLITZHUB) {
console.error('process.env.BLITZHUB not set, skipped');
return;
}
// fetchig invoice from tippin.me :
const api = new Frisbee({
baseURI: 'https://tippin.me',
});
const res = await api.post('/lndreq/newinvoice.php', {
headers: {
Origin: 'https://tippin.me',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Accept: 'application/json, text/javascript, */*; q=0.01',
},
body: 'userid=1188&username=overtorment&istaco=0&customAmnt=0&customMemo=',
});
let json;
let invoice;
if (res && res.body && (json = JSON.parse(res.body)) && json.message) {
invoice = json.message;
} else {
throw new Error('tippin.me problem: ' + JSON.stringify(res));
}
// --> use to pay specific invoice
// invoice =
// 'lnbc1pwrp35spp5z62nvj8yw6luq7ns4a8utpwn2qkkdwdt0ludwm54wjeazk2xv5wsdpu235hqurfdcsx7an9wf6x7undv4h8ggpgw35hqurfdchx6eff9p6nzvfc8q5scqzysxqyz5vqj8xq6wz6dezmunw6qxleuw67ensjnt3fldltrmmkvzurge0dczpn94fkwwh7hkh5wqrhsvfegtvhswn252hn6uw5kx99dyumz4v5n9sp337py2';
let l2 = new LightningCustodianWallet();
l2.setSecret(process.env.BLITZHUB);
await l2.authorize();
await l2.fetchTransactions();
await l2.fetchBalance();
let oldBalance = +l2.balance;
let txLen = l2.transactions_raw.length;
let decoded = await l2.decodeInvoice(invoice);
assert.ok(decoded.payment_hash);
assert.ok(decoded.description);
assert.equal(+decoded.num_satoshis, 0);
await l2.checkRouteInvoice(invoice);
let start = +new Date();
await l2.payInvoice(invoice, 3);
let end = +new Date();
if ((end - start) / 1000 > 9) {
console.warn('payInvoice took', (end - start) / 1000, 'sec');
}
await l2.fetchTransactions();
assert.equal(l2.transactions_raw.length, txLen + 1);
// transactions became more after paying an invoice
await l2.fetchBalance();
assert.equal(oldBalance - l2.balance, 3);
});
});

View file

@ -35,6 +35,9 @@ import Success from './screen/send/success';
import ManageFunds from './screen/lnd/manageFunds';
import ScanLndInvoice from './screen/lnd/scanLndInvoice';
import LNDCreateInvoice from './screen/lnd/lndCreateInvoice';
import LNDViewInvoice from './screen/lnd/lndViewInvoice';
import LNDViewAdditionalInvoiceInformation from './screen/lnd/lndViewAdditionalInvoiceInformation';
const ReorderWalletsStackNavigator = createStackNavigator({
ReorderWallets: {
@ -130,6 +133,55 @@ const CreateTransactionStackNavigator = createStackNavigator({
},
});
const ManageFundsStackNavigator = createStackNavigator({
ManageFunds: {
screen: ManageFunds,
},
SelectWallet: {
screen: SelectWallet,
},
SendDetails: {
screen: CreateTransactionStackNavigator,
navigationOptions: {
header: null,
},
},
});
const LNDViewInvoiceStackNavigator = createStackNavigator({
LNDViewInvoice: {
screen: LNDViewInvoice,
swipeEnabled: false,
gesturesEnabled: false,
},
LNDViewAdditionalInvoiceInformation: {
screen: LNDViewAdditionalInvoiceInformation,
},
});
const LNDCreateInvoiceStackNavigator = createStackNavigator({
LNDCreateInvoice: {
screen: LNDCreateInvoice,
},
LNDViewInvoice: {
screen: LNDViewInvoice,
swipeEnabled: false,
gesturesEnabled: false,
},
LNDViewAdditionalInvoiceInformation: {
screen: LNDViewAdditionalInvoiceInformation,
},
});
const CreateWalletStackNavigator = createStackNavigator({
AddWallet: {
screen: AddWallet,
},
ImportWallet: {
screen: ImportWallet,
},
});
const MainBottomTabs = createStackNavigator(
{
Wallets: {
@ -140,10 +192,10 @@ const MainBottomTabs = createStackNavigator(
},
},
AddWallet: {
screen: AddWallet,
},
ImportWallet: {
screen: ImportWallet,
screen: CreateWalletStackNavigator,
navigationOptions: {
header: null,
},
},
ScanQrWif: {
screen: scanQrWif,
@ -180,7 +232,10 @@ const MainBottomTabs = createStackNavigator(
// LND:
ManageFunds: {
screen: ManageFunds,
screen: ManageFundsStackNavigator,
navigationOptions: {
header: null,
},
},
ScanLndInvoice: {
screen: ScanLndInvoice,
@ -194,11 +249,17 @@ const MainBottomTabs = createStackNavigator(
header: null,
},
},
// Select Wallet. Mostly for deeplinking
SelectWallet: {
screen: SelectWallet,
LNDCreateInvoice: {
screen: LNDCreateInvoiceStackNavigator,
navigationOptions: {
header: null,
},
},
LNDViewExistingInvoice: {
screen: LNDViewInvoiceStackNavigator,
navigationOptions: {
header: null,
},
},
},
{

View file

@ -102,8 +102,8 @@ android {
applicationId "io.bluewallet.bluewallet"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 12
versionName "3.4.0"
versionCode 15
versionName "3.5.5"
ndk {
abiFilters "armeabi-v7a", "x86"
}

View file

@ -25,6 +25,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="bitcoin" />
<data android:scheme="lightning" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

View file

@ -112,4 +112,9 @@
<androidx.activity/>
<com.android.tools.apkparser/>
<com.android.tools.pixelprobe/>
<androidx.textclassifier/>
<androidx.remotecallback/>
<com.android.tools.chunkio/>
<com.android.tools.fakeadbserver/>
<androidx.savedstate/>
</metadata>

View file

@ -0,0 +1,10 @@
v3.5.0
------
ADD: Create LND invoice
ADD: Ability to show wallet XPUB in options
ADD: translations for german (DE)
ADD: Set receive amount & label
ADD: Added more Fiat currencies
ADD: help text in lighning settings
ADD: CZ locale

View file

@ -0,0 +1,9 @@
v3.5.5
------
ADD: pay zero-amount (tip) invoices
ADD: lightning withdrawal through zigzag
ADD: Thai translation
ADD: Dutch translation
ADD: Added Singapore Dollars
ADD: Added AUD, VEF, and ZAR fiats.

View file

@ -14,9 +14,9 @@ let encryption = require('../encryption');
export class AppStorage {
static FLAG_ENCRYPTED = 'data_encrypted';
static LANG = 'lang';
static CURRENCY = 'currency';
static EXCHANGE_RATES = 'currency';
static LNDHUB = 'lndhub';
static PREFERREDCURRENCY = 'preferredCurrency';
static PREFERRED_CURRENCY = 'preferredCurrency';
constructor() {
/** {Array.<AbstractWallet>} */
@ -174,8 +174,10 @@ export class AppStorage {
break;
}
// done
this.wallets.push(unserializedWallet);
this.tx_metadata = data.tx_metadata;
if (!this.wallets.some(wallet => wallet.getSecret() === unserializedWallet.secret)) {
this.wallets.push(unserializedWallet);
this.tx_metadata = data.tx_metadata;
}
}
return true;
} else {
@ -290,11 +292,23 @@ export class AppStorage {
for (let wallet of this.wallets) {
if (c++ === index) {
await wallet.fetchTransactions();
if (wallet.fetchPendingTransactions) {
await wallet.fetchPendingTransactions();
}
if (wallet.fetchUserInvoices) {
await wallet.fetchUserInvoices();
}
}
}
} else {
for (let wallet of this.wallets) {
await wallet.fetchTransactions();
if (wallet.fetchPendingTransactions) {
await wallet.fetchPendingTransactions();
}
if (wallet.fetchUserInvoices) {
await wallet.fetchUserInvoices();
}
}
}
}

View file

@ -102,9 +102,9 @@ export class LightningCustodianWallet extends LegacyWallet {
this.secret = 'lndhub://' + json.login + ':' + json.password;
}
async payInvoice(invoice) {
async payInvoice(invoice, freeAmount = 0) {
let response = await this._api.post('/payinvoice', {
body: { invoice: invoice },
body: { invoice: invoice, amount: freeAmount },
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
@ -145,12 +145,24 @@ export class LightningCustodianWallet extends LegacyWallet {
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
}
this.user_invoices_raw = json;
return json;
}
/**
* Basically the same as this.getUserInvoices() but saves invoices list
* to internal variable
*
* @returns {Promise<void>}
*/
async fetchUserInvoices() {
await this.getUserInvoices();
}
async addInvoice(amt, memo) {
let response = await this._api.post('/addinvoice', {
body: { amt: amt + '', memo: encodeURIComponent(memo) },
body: { amt: amt + '', memo: memo },
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
@ -306,8 +318,9 @@ export class LightningCustodianWallet extends LegacyWallet {
getTransactions() {
let txs = [];
this.pending_transactions_raw = this.pending_transactions_raw || [];
this.user_invoices_raw = this.user_invoices_raw || [];
this.transactions_raw = this.transactions_raw || [];
txs = txs.concat(this.pending_transactions_raw, this.transactions_raw.slice().reverse()); // slice so array is cloned
txs = txs.concat(this.pending_transactions_raw.slice(), this.transactions_raw.slice().reverse(), this.user_invoices_raw.slice()); // slice so array is cloned
// transforming to how wallets/list screen expects it
for (let tx of txs) {
if (tx.amount) {
@ -325,13 +338,21 @@ export class LightningCustodianWallet extends LegacyWallet {
if (tx.type === 'paid_invoice') {
tx.memo = tx.memo || 'Lightning payment';
if (tx.value > 0) tx.value = (tx.value * 1 + tx.fee * 1) * -1;
// outer code expects spending transactions to of negative value
}
if (tx.type === 'bitcoind_tx') {
tx.memo = 'On-chain transaction';
}
tx.received = new Date(tx.timestamp * 1000).toString(); // TODO once api is ready
if (tx.type === 'user_invoice') {
// incoming ln tx
tx.value = parseInt(tx.amt);
tx.memo = tx.description || 'Lightning invoice';
}
tx.received = new Date(tx.timestamp * 1000).toString();
}
return txs.sort(function(a, b) {
return b.timestamp - a.timestamp;
@ -497,7 +518,7 @@ export class LightningCustodianWallet extends LegacyWallet {
}
allowReceive() {
return false;
return true;
}
}

View file

@ -27,4 +27,10 @@ export class SegwitBech32Wallet extends LegacyWallet {
const scriptPubKey = bitcoin.script.witnessPubKeyHash.output.encode(pubKeyHash);
return bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.bitcoin);
}
static scriptPubKeyToAddress(scriptPubKey) {
const bitcoin = require('bitcoinjs-lib');
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex');
return bitcoin.address.fromOutputScript(scriptPubKey2, bitcoin.networks.bitcoin);
}
}

View file

@ -4,37 +4,46 @@ import { AppStorage } from './class';
import { FiatUnit } from './models/fiatUnit';
let BigNumber = require('bignumber.js');
let preferredFiatCurrency = FiatUnit.USD;
let lang = {};
// let btcusd = 6500; // default
let exchangeRates = {};
const STRUCT = {
LAST_UPDATED: 'LAST_UPDATED',
BTC_USD: 'BTC_USD',
BTC_EUR: 'BTC_EUR',
};
/**
* Saves to storage preferred currency, whole object
* from `./models/fiatUnit`
*
* @param item {Object} one of the values in `./models/fiatUnit`
* @returns {Promise<void>}
*/
async function setPrefferedCurrency(item) {
await AsyncStorage.setItem(AppStorage.PREFERRED_CURRENCY, JSON.stringify(item));
}
async function getPreferredCurrency() {
return JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY));
}
async function updateExchangeRate() {
let preferredFiatCurrency;
try {
preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERREDCURRENCY));
if (preferredFiatCurrency === null) {
throw Error();
}
} catch (_error) {
preferredFiatCurrency = FiatUnit.USD;
}
if (+new Date() - lang[STRUCT.LAST_UPDATED] <= 30 * 60 * 1000) {
if (+new Date() - exchangeRates[STRUCT.LAST_UPDATED] <= 30 * 60 * 1000) {
// not updating too often
return;
}
try {
preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY));
} catch (_) {}
preferredFiatCurrency = preferredFiatCurrency || FiatUnit.USD;
let json;
try {
const api = new Frisbee({
baseURI: 'https://www.bitstamp.net',
baseURI: 'https://api.coindesk.com',
});
let response = await api.get('/api/v2/ticker/' + preferredFiatCurrency.endPointKey);
json = response.body;
if (typeof json === 'undefined' || typeof json.last === 'undefined') {
let response = await api.get('/v1/bpi/currentprice/' + preferredFiatCurrency.endPointKey + '.json');
json = JSON.parse(response.body);
if (!json || !json.bpi || !json.bpi[preferredFiatCurrency.endPointKey] || !json.bpi[preferredFiatCurrency.endPointKey].rate_float) {
throw new Error('Could not update currency rate: ' + response.err);
}
} catch (Err) {
@ -42,53 +51,51 @@ async function updateExchangeRate() {
return;
}
lang[STRUCT.LAST_UPDATED] = +new Date();
lang[STRUCT[preferredFiatCurrency.storageKey]] = json.last * 1;
await AsyncStorage.setItem(AppStorage.CURRENCY, JSON.stringify(lang));
exchangeRates[STRUCT.LAST_UPDATED] = +new Date();
exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = json.bpi[preferredFiatCurrency.endPointKey].rate_float * 1;
await AsyncStorage.setItem(AppStorage.EXCHANGE_RATES, JSON.stringify(exchangeRates));
await AsyncStorage.setItem(AppStorage.PREFERRED_CURRENCY, JSON.stringify(preferredFiatCurrency));
}
async function startUpdater(force = false) {
if (force) {
const lang = JSON.parse(await AsyncStorage.getItem(AppStorage.CURRENCY));
delete lang[STRUCT.LAST_UPDATED];
await AsyncStorage.setItem(AppStorage.CURRENCY, JSON.stringify(lang));
try {
preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERREDCURRENCY));
if (preferredFiatCurrency === null) {
throw Error();
}
} catch (_error) {
preferredFiatCurrency = FiatUnit.USD;
}
let interval = false;
async function startUpdater() {
if (interval) {
clearInterval(interval);
exchangeRates[STRUCT.LAST_UPDATED] = 0;
}
lang = await AsyncStorage.getItem(AppStorage.CURRENCY);
try {
lang = JSON.parse(lang);
} catch (Err) {
lang = {};
}
lang = lang || {};
lang[STRUCT.LAST_UPDATED] = lang[STRUCT.LAST_UPDATED] || 0;
lang[STRUCT[preferredFiatCurrency.storageKey]] = lang[STRUCT[preferredFiatCurrency.storageKey]] || 6500;
setInterval(() => updateExchangeRate(), 2 * 60 * 100);
interval = setInterval(() => updateExchangeRate(), 2 * 60 * 100);
return updateExchangeRate();
}
function satoshiToLocalCurrency(satoshi) {
if (!lang[STRUCT[preferredFiatCurrency.storageKey]]) return satoshi;
if (!exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]) return satoshi;
let b = new BigNumber(satoshi);
b = b
.dividedBy(100000000)
.multipliedBy(lang[STRUCT[preferredFiatCurrency.storageKey]])
.multipliedBy(exchangeRates['BTC_' + preferredFiatCurrency.endPointKey])
.toString(10);
b = parseFloat(b).toFixed(2);
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: preferredFiatCurrency.formatterValue,
minimumFractionDigits: 2,
});
let formatter;
try {
formatter = new Intl.NumberFormat(preferredFiatCurrency.locale, {
style: 'currency',
currency: preferredFiatCurrency.endPointKey,
minimumFractionDigits: 2,
});
} catch (error) {
console.warn(error);
console.log(error);
formatter = new Intl.NumberFormat(FiatUnit.USD.locale, {
style: 'currency',
currency: preferredFiatCurrency.endPointKey,
minimumFractionDigits: 2,
});
}
return formatter.format(b);
}
@ -111,3 +118,5 @@ module.exports.STRUCT = STRUCT;
module.exports.satoshiToLocalCurrency = satoshiToLocalCurrency;
module.exports.satoshiToBTC = satoshiToBTC;
module.exports.BTCToLocalCurrency = BTCToLocalCurrency;
module.exports.setPrefferedCurrency = setPrefferedCurrency;
module.exports.getPreferredCurrency = getPreferredCurrency;

View file

@ -80,7 +80,7 @@
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"

View file

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.4.0</string>
<string>3.5.6</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@ -33,7 +33,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>168</string>
<string>216</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
@ -56,7 +56,7 @@
<key>NSCalendarsUsageDescription</key>
<string>This alert should not show up as we do not require this data</string>
<key>NSCameraUsageDescription</key>
<string>In order to quickly scan the recipient&apos;s address, we need your permission to use the camera to scan their QR Code.</string>
<string>In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This alert should not show up as we do not require this data</string>
<key>NSMotionUsageDescription</key>

View file

@ -1 +1 @@
2018 BlueWallet Services S.R.L.
2019 BlueWallet Services S.R.L.

View file

@ -1,11 +1,25 @@
ADD: Persist the preferred unit per wallet.
ADD: Deeplinking for bitcoin, lightning
ADD: Added fee in tx details
ADD: Fiat Currency in settings panel
ADD: Select Wallet on Send screen
ADD: Currency settings panel (eur, usd)
FIX: Disabled autocorrect when importing wallet (security issue)
FIX: amount display bug
ADD: spend bip44 (legacy HD wallets)
FIX: BIP44 mnemonic QR correctly imported
ADD: haptic feedback when the user only has 1 wallet
v3.5.5
------
ADD: pay zero-amount (tip) invoices
ADD: lightning withdrawal through zigzag
ADD: Thai translation
ADD: Dutch translation
ADD: Added Singapore Dollars
ADD: Added AUD, VEF, and ZAR fiats.
FIX: Loading indicator when creating a wallet
FIX: Changelly link
Fix and improve pt-BR translation
FIX: Cannot click on Lightning transactions #196
FIX: Fixed a clipping issue in lightning settings
FIX: fixed a margin issue in about that caused clipping
FIX: Changed invoice description field to label
FIX: Updated transaction buttons maximum width
FIX: Main Buttons layout #204
FIX: Add topup indication on wallet selection #207
FIX: Invoice QR code wrong scale #203
FIX: Don't allow user to pay for an invoice created with the same wallet.
FIX: If. balance was not a string, app would crash.
FIX: Changed language selection screen to FlatList
FIX: Made amount tap area larger
FIX: Fixed an issue in currency settings where the checkmark wouldn't be in the correct preference

View file

@ -0,0 +1,37 @@
Guardar, enviar e receber bitcoin com uma carteira focada na segurança e simplicidade.
Na Blue Wallet você possui as suas chaves privadas. Uma carteira Bitcoin focada nos usuários.
Você pode instantaneamente transacionar com qualquer pessoa no mundo e transformar o sistema financeiro diretamente do seu bolso.
Crie carteiras Bitcoin ilimitadas e gratuitamente, ou acesse as suas carteiras existentes através do seu dispositivo iOS. É simples e rápido.
As funcionalidades disponíveis:
1 - Segurança por design
Open Source
Este aplicativo tem licença MIT, pode construir e lança-lo você mesmo! Feito em ReactNative
Negação plausível
Senha falsa que decripta wallets falsas. Para casos especias onde possa ser obrigado a revelar a sua senha, pode revelar a senha falsa, mantenha as suas bitcoin protegidas
Encriptação completa
Construída em cima da multi-camada de encritação do iOS, A BlueWallet encripta tudo com uma adicional senha de usuário
Carteiras SegWit e HD
SegWit suportado (Ajuda a diminuir os fees ainda mais) e carteiras HD activadas
2 - Focada na sua experiência
Esteja em control
As chaves privadas nunca saiem do celular, você controla as suas chaves privadas
Fees flexíveis
A começar em 1 Satoshi. Não page a mais por transações
Substituição de Fee (RBF)
Acelere as suas transações aumentendo o fee (BIP125). Pode também alterar o endereço de destinatário em transações não confirmadas
Carteira Sentinela
Carteira de "assitir apenas", observe o seu armazenamento externo de Bitcoin sem ter de lhe tocar

View file

@ -0,0 +1 @@
bitcoin,wallet,segwit,crypto,blockchain,btc,cryptocurrency,wallet,bread,samourai,lightning,ethereum

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@
BlueWallet - Bitcoin wallet

View file

@ -0,0 +1 @@
http://www.bluewallet.io/privacy.txt

View file

@ -0,0 +1,10 @@
Features
* Open Source
* Full encryption
* Plausible deniability
* Flexible fees
* Replace-By-Fee (RBF)
* SegWit
* Watch-only (Sentinel) wallets
* Lightning network

View file

@ -0,0 +1 @@
../en-US/release_notes.txt

View file

@ -0,0 +1 @@
Bitcoin & Lightning

View file

@ -0,0 +1 @@
https://github.com/BlueWallet/BlueWallet/issues

213
loc/cs_CZ.js Normal file
View file

@ -0,0 +1,213 @@
module.exports = {
_: {
storage_is_encrypted: 'Vaše úložiště je zašifrované. Zadejte heslo k odemčení',
enter_password: 'Zadejte heslo',
bad_password: 'Špatné heslo, prosím zkuste to znovu',
months_ago: 'měsíců',
days_ago: 'dní',
hours_ago: 'hodin',
minutes_ago: 'minut',
never: 'nikdy',
},
wallets: {
select_wallet: 'Vyberte peněženku',
options: 'možnosti',
list: {
app_name: 'Blue Wallet',
title: 'peněženky',
header: 'Peněženka reprezentuje pár tajného (privátního) klíče a adresy' + 'kterou můžete sdílet, abyste získali mince',
add: 'Přidat peněženku',
create_a_wallet: 'Vytvořit peněženku',
create_a_wallet1: 'Je to zdarma a můžete vytvořit',
create_a_wallet2: 'tolik, kolik budete chtít',
latest_transaction: 'poslední transakce',
empty_txs1: 'Zde budou zobrazeny vaše transakce,',
empty_txs2: 'zatím žádné',
tap_here_to_buy: 'Klikněte zde pro zakoupení Bitcoinu',
},
reorder: {
title: 'Seřadit peěženky',
},
add: {
title: 'přidat peněženku',
description:
'Můžete naskenovat zálohovoanou papírovou peněženku (WIF - Wallet Import Format), nevo vytvořit novou peněženku. Segwit peněženky jsou podporovány standardně.',
scan: 'Skenovat',
create: 'Vytvořit',
label_new_segwit: 'Nová SegWit',
label_new_lightning: 'Nová Lightning',
wallet_name: 'název peněženky',
wallet_type: 'typ',
or: 'nebo',
import_wallet: 'Importovat peněženku',
imported: 'Importována',
coming_soon: 'Již brzy',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
},
details: {
title: 'Peněženka',
address: 'Adresa',
type: 'Typ',
label: 'Popisek',
destination: 'cíl',
description: 'Popis',
are_you_sure: 'Jste si jistý?',
yes_delete: 'Ano, smazat',
no_cancel: 'Ne, zrušit',
delete: 'Smazat',
save: 'Uložit',
delete_this_wallet: 'Smazat peněženku',
export_backup: 'Exportovat / zálohovat',
buy_bitcoin: 'Koupit Bitcoin',
show_xpub: 'Ukázat XPUB',
},
export: {
title: 'exportovat peněženku',
},
xpub: {
title: 'XPUB peněženky',
copiedToClipboard: 'Zkopírováno do schránky.',
},
import: {
title: 'importovat',
explanation:
'Zadejte zde svůj mnemonic seed, privátní klíč, WIF, nebo cokoliv co máte. BlueWallet se pokusí uhodnout správný formát a naimportovat vaší peněženku',
imported: 'Importováno',
error: 'Chyba při importu. Prosím ujistěte se, že poskytnutá data jsou správná.',
success: 'Úspěch',
do_import: 'Importovat',
scan_qr: 'nebo raději naskenovat QR kód?',
},
scanQrWif: {
go_back: 'Zpět',
cancel: 'Zrušit',
decoding: 'Dekóduji',
input_password: 'Vložte heslo',
password_explain: 'Toto je BIP38 zašifrovaný privátní klíč',
bad_password: 'Špatné heslo',
wallet_already_exists: 'Tato peněženka již existuje',
bad_wif: 'Špatný WIF',
imported_wif: 'Importovaný WIF ',
with_address: ' s adresou ',
imported_segwit: 'Importovaná SegWit',
imported_legacy: 'Importovaná Legacy',
imported_watchonly: 'Importovaná Watch-only',
},
},
transactions: {
list: {
tabBarLabel: 'Transakce',
title: 'transakce',
description: 'Seznam příchozích a odchozích transakcí vašich peněženek',
conf: 'potvrzení',
},
details: {
title: 'Transakce',
from: 'Input',
to: 'Output',
copy: 'Kopírovat',
transaction_details: 'Detaily transakce',
show_in_block_explorer: 'Ukázat v block exploreru',
},
},
send: {
header: 'Poslat',
details: {
title: 'vytvořit transakci',
amount_field_is_not_valid: 'Čáskta není správně vyplněna',
fee_field_is_not_valid: 'Poplatek není správně vyplněn',
address_field_is_not_valid: 'Adresa není správně vyplněna',
total_exceeds_balance: 'Částka, kterou se snažíte poslat, přesahuje dostupný zůstatek.',
create_tx_error: 'Nastala chyba při vytváření transakce. Prosím ujistěte se, že adresa je platná.',
address: 'adresa',
amount_placeholder: 'částka k odeslání (v BTC)',
fee_placeholder: 'plus transakční poplatek (v BTC)',
note_placeholder: 'poznámka pro sebe',
cancel: 'Zrušit',
scan: 'Skenovat',
send: 'Poslat',
create: 'Vytvořit',
remaining_balance: 'Zbývající zůstatek',
},
confirm: {
header: 'Potvrdit',
sendNow: 'Poslat hned',
},
success: {
done: 'Hotovo',
},
create: {
details: 'Detaily',
title: 'vytvořit transakci',
error: 'Chyba při vytváření transakce. Nesprávná adresa nebo částka?',
go_back: 'Zpět',
this_is_hex: 'Toto je vaše transakce, podepsána a připravena k odeslání do sítě.',
to: 'To',
amount: 'Částka',
fee: 'Poplatek',
tx_size: 'velikost transakce',
satoshi_per_byte: 'Satoshi/byte',
memo: 'Popisek',
broadcast: 'Odeslat do sítě',
not_enough_fee: 'Nedostatečný poplatek. Zvyšte poplatek.',
},
},
receive: {
header: 'Přijmout',
details: {
title: 'Sdílejte tuto adresu s plátcem',
share: 'sdílet',
copiedToClipboard: 'Zkopírováno do schránky.',
label: 'Popis',
setAmount: 'Přijmout částku...',
},
},
buyBitcoin: {
header: 'Koupit Bitcoin',
tap_your_address: 'Klikněte na svojí adresu pro zkopírování do schránky:',
copied: 'Zkopírováno do schránky.',
},
settings: {
header: 'nastavení',
plausible_deniability: 'Plausible deniability...',
storage_not_encrypted: 'Uložiště: nezašifrováno',
storage_encrypted: 'Úložiště: zašifrováno',
password: 'Heslo',
password_explain: 'Vytořte si heslo k zašifrování úložiště.',
retype_password: 'Heslo znovu',
passwords_do_not_match: 'Hesla se neshodují',
encrypt_storage: 'Zašifrovat úložiště',
about: 'O BlueWallet',
language: 'Jazyk',
currency: 'Měna',
},
plausibledeniability: {
title: 'Plausible Deniability',
help:
'Za určitých okolností můžete být donuceni k prozrazení vašeho hesla.' +
'K zajištění bezpečností vašich prostředků, BlueWallet může vytvořit' +
'další zašifrované úložiště s rozdílný heslem. V případě potřeby' +
'můžete toto heslo dát třetí straně. Pokud bude zadáno do BlueWallet,' +
'odemkne nové "falešné" úložiště. Toto bude vypadat legitimně, ale' +
'udrží vaše pravé hlavní úložiště v bezpečí.',
help2: 'Nové úložiště bude plně funkční, můžete na něj uložit minimální částku, aby vypadalo více uvěřitelně.',
create_fake_storage: 'Vytvořit falešné zašifrované úložiště',
go_back: 'Zpět',
create_password: 'Vytvořit heslo',
create_password_explanation: 'Heslo k falešnému úložišti nesmí být stejné jako heslo k hlavnímu úložišti',
password_should_not_match: 'Heslo k falešnému úložišti nesmí být stejné jako heslo k hlavnímu úložišti',
retype_password: 'Heslo znovu',
passwords_do_not_match: 'Hesla se neshodují, zkuste to znovu',
success: 'Úspěch',
},
lnd: {
title: 'spravovat zůstatek',
choose_source_wallet: 'Vyberte zdrojovou peněženku',
refill_lnd_balance: 'Doplnit zůstatek na Lightning peněžence',
refill: 'Doplnit',
withdraw: 'Vybrat',
expired: 'Expirováno',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
},
};

View file

@ -15,10 +15,11 @@ module.exports = {
list: {
app_name: 'Blue Wallet',
title: 'Wallets',
header: 'Eine Wallet (Brieftasche) spiegelt ein Paar von kryptographischen Schlüssel wider. Einen geheimen und eine Adresse als öffentlichen Schlüssel. Letztern kann man zum Erhalt von Bitcoin teilen.',
header:
'Eine Wallet (Brieftasche) spiegelt ein Paar von kryptographischen Schlüssel wider. Einen geheimen und eine Adresse als öffentlichen Schlüssel. Letztern kann man zum Erhalt von Bitcoin teilen.',
add: 'Wallet hinzufügen',
create_a_wallet: 'Wallet erstellen',
create_a_wallet1: "Es ist kostenlos und du kannst",
create_a_wallet1: 'Es ist kostenlos und du kannst',
create_a_wallet2: 'so viele erstellen, wie du möchtest',
latest_transaction: 'Lezte Transaktion',
empty_txs1: 'Deine Transaktionen erscheinen hier',
@ -72,7 +73,7 @@ module.exports = {
import: {
title: 'Importieren',
explanation:
"Gib hier deine mnemonische Phrase, deinen privaten Schlüssel, WIF oder worüber du auch immer verfügst ein. BlueWallet wird bestmöglich dein Format interpretieren und die Wallet importieren",
'Gib hier deine mnemonische Phrase, deinen privaten Schlüssel, WIF oder worüber du auch immer verfügst ein. BlueWallet wird bestmöglich dein Format interpretieren und die Wallet importieren',
imported: 'Importiert',
error: 'Fehler beim Import. Ist die Eingabe korrekt?',
success: 'Erfolg',
@ -209,5 +210,6 @@ module.exports = {
refill_lnd_balance: 'Fülle deine Lightning Wallet auf',
refill: 'Auffüllen',
withdraw: 'Abheben',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
},
};

View file

@ -74,7 +74,7 @@ module.exports = {
explanation:
"Write here your mnemonic, private key, WIF, or anything you've got. BlueWallet will do its best to guess the correct format and import your wallet",
imported: 'Imported',
error: 'Failed to import. Is the event valid?',
error: 'Failed to import. Please, make sure that the provided data is valid.',
success: 'Success',
do_import: 'Import',
scan_qr: 'or scan QR code instead?',
@ -187,7 +187,7 @@ module.exports = {
help:
'Under certain circumstances, you might be forced to disclose a ' +
'password. To keep your coins safe, BlueWallet can create another ' +
'encrypted storage, with a different password. Under the pressure, ' +
'encrypted storage, with a different password. Under pressure, ' +
'you can disclose this password to a 3rd party. If entered in ' +
"BlueWallet, it will unlock new 'fake' storage. This will seem " +
'legit to a 3rd party, but will secretly keep your main storage ' +
@ -208,5 +208,7 @@ module.exports = {
refill_lnd_balance: 'Refill Lightning wallet balance',
refill: 'Refill',
withdraw: 'Withdraw',
expired: 'Expired',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
},
};

View file

@ -210,5 +210,7 @@ module.exports = {
refill_lnd_balance: 'Rellenar el balance de la billetera Lightning',
refill: 'Rellenar',
withdraw: 'Retirar',
expired: 'Expirado',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
},
};

215
loc/fr_FR.js Normal file
View file

@ -0,0 +1,215 @@
module.exports = {
_: {
storage_is_encrypted: 'L\'espace de stockage est chiffré. Mot de passe requis pour le déchiffrer.',
enter_password: 'Saisir mot de passe',
bad_password: 'Mauvais mot de passe, ré-essayer',
months_ago: 'mois',
days_ago: 'jours',
hours_ago: 'heures',
minutes_ago: 'minutes',
never: 'jamais',
},
wallets: {
select_wallet: 'Choix du portefeuille',
options: 'options',
list: {
app_name: 'Blue Wallet',
title: 'portefeuilles',
header: 'Un portefeuille represente une paire de clées (publique/privée) et une adresse que vous pouvez partager pour recevoir des transactions.',
add: 'Ajouter un portefeuille',
create_a_wallet: 'Créer un portefeuille',
create_a_wallet1: "C\'est gratuit et vous pouvez en créer",
create_a_wallet2: 'autant que vous souhaitez',
latest_transaction: 'dernière transaction',
empty_txs1: 'Vos transactions apparaîtront ici,',
empty_txs2: 'Aucune pour le moment',
tap_here_to_buy: 'Cliquez ici pour acheter du Bitcoin',
},
reorder: {
title: 'Trier vos portefeuilles',
},
add: {
title: 'ajouter un portefeuille',
description:
'Vous pouvez soit scanner et importer un paper wallet (au format WIF - Wallet Import Format), ou créer un nouveau portefeuille. Compatible avec Segwit par defaut.',
scan: 'Scanner',
create: 'Créer',
label_new_segwit: 'Nouveau SegWit',
label_new_lightning: 'Nouveau Lightning',
wallet_name: 'nom du portefeuille',
wallet_type: 'type',
or: 'ou',
import_wallet: 'Importer un portefeuille',
imported: 'Importé',
coming_soon: 'Bientôt',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
},
details: {
title: 'Portefeuille',
address: 'Adresse',
type: 'Type',
label: 'Libelé',
destination: 'destination',
description: 'description',
are_you_sure: 'Êtes vous sur?',
yes_delete: 'Oui, supprimer',
no_cancel: 'Non, annuler',
delete: 'Supprimer',
save: 'Sauvegarder',
delete_this_wallet: 'Supprimer ce portefeuille',
export_backup: 'Exporter / sauvegarder',
buy_bitcoin: 'Acheter du Bitcoin',
show_xpub: 'Afficher XPUB du portefeuille',
},
export: {
title: 'export du portefeuille',
},
xpub: {
title: 'XPUB portefeuille',
copiedToClipboard: 'Copié dans le presse-papiers.',
},
import: {
title: 'importer',
explanation:
"Write here your mnemonic, private key, WIF, or anything you've got. BlueWallet will do its best to guess the correct format and import your wallet",
imported: 'Importé',
error: 'Échec de l\'import. Merci, de vérifier que les données saisies sont valides.',
success: 'Succès',
do_import: 'Importer',
scan_qr: 'ou scaner un QR code',
},
scanQrWif: {
go_back: 'Retour',
cancel: 'Annuler',
decoding: 'Déchiffrage',
input_password: 'Saisir mot de passe',
password_explain: 'Ceci est une clée privée chiffrée avec BIP38',
bad_password: 'Mauvais mot de passe',
wallet_already_exists: 'Ce portefeuille existe déjà',
bad_wif: 'Mauvais WIF',
imported_wif: 'WIF Importé',
with_address: ' avec adresse ',
imported_segwit: 'SegWit Importé',
imported_legacy: 'Legacy Importé',
imported_watchonly: 'Monitoring Importé',
},
},
transactions: {
list: {
tabBarLabel: 'Transactions',
title: 'transactions',
description: 'Une liste des transactions entrentes et sortantes de vos portefeuilles',
conf: 'conf',
},
details: {
title: 'Transaction',
from: 'De',
to: 'À',
copy: 'Copier',
transaction_details: 'Détails de la transaction',
show_in_block_explorer: 'Afficher dans le "block explorer"',
},
},
send: {
header: 'Envoyer',
details: {
title: 'créer une transaction',
amount_field_is_not_valid: 'Champ montant invalide',
fee_field_is_not_valid: 'Champ frais invalide',
address_field_is_not_valid: 'Champ adresse invalide',
total_exceeds_balance: 'Le montant à envoyer excède le montant disponible.',
create_tx_error: 'There was an error creating the transaction. Please, make sure the address is valid.',
address: 'adresse',
amount_placeholder: 'montant à envoyer (en BTC)',
fee_placeholder: 'plus frais de transaction (en BTC)',
note_placeholder: 'note (optionnelle)',
cancel: 'Annuler',
scan: 'Scanner',
send: 'Envoyer',
create: 'Créer',
remaining_balance: 'Balance restante',
},
confirm: {
header: 'Confirmer',
sendNow: 'Envoyer maintenant',
},
success: {
done: 'Terminé',
},
create: {
details: 'Details',
title: 'créer une transaction',
error: 'Erreur creating transaction. Invalid address or send amount?',
go_back: 'Retour',
this_is_hex: 'This is transaction hex, signed and ready to be broadcast to the network.',
to: 'À',
amount: 'Montant',
fee: 'Frais',
tx_size: 'Taille de la Transaction (TX size)',
satoshi_per_byte: 'Satoshi par byte',
memo: 'Memo',
broadcast: 'Broadcast',
not_enough_fee: 'Frais insufisants. Veuillez augmenter les frais',
},
},
receive: {
header: 'Recevoir',
details: {
title: 'Partager cette adresse avec le destinataire',
share: 'partager',
copiedToClipboard: 'Copier dans le presse-papiers.',
label: 'Description',
setAmount: 'Revevoir avec montant',
},
},
buyBitcoin: {
header: 'Acheter du Bitcoin',
tap_your_address: 'Cliquez votre adresse pour la copier:',
copied: 'Copié dans le presse-papiers!',
},
settings: {
header: 'réglages',
plausible_deniability: 'Déni plausible...',
storage_not_encrypted: 'Stockage: non chiffré',
storage_encrypted: 'Stockage: chiffré',
password: 'Mot de passe',
password_explain: 'Créer le mot de passe utilisé pour déchiffrer l\'espace de stockage principal',
retype_password: 'Re-saisir votre mot de passe',
passwords_do_not_match: 'Les mots de passe ne correspondent pas',
encrypt_storage: 'Chiffrer le stockage',
about: 'À propos',
language: 'Langue',
currency: 'Devise',
},
plausibledeniability: {
title: 'Déni plausible',
help:
'Dans certaines circonstances, vous serez peut-être forcé par un tiers à communiquer ' +
'votre mot de passe. Pour protéger vos biens, BlueWallet permet de créer un autre ' +
'espace de stockage, avec un mot de passe différent. Sous la contrainte, ' +
'vous pourrez divulger ce mot de passe au tier. Quand il est saisi ' +
"BlueWallet, débloquera se 'faux' espace de stockage. Le tiers pourra " +
'confondre ces données avec des données légitimes, votre espace de stockage ' +
'principal restera sécurisé et hors d\'atteinte.',
help2: 'New storage will be fully functional, and you can store some ' + 'minimum amounts there so it looks more believable.',
create_fake_storage: 'Créer un faux espace de stockage chiffré',
go_back: 'Retour',
create_password: 'Créer un mot de passe',
create_password_explanation: 'Le mot de passe pour le faux espace de stockage ne doit pas être le même que celui du stockage principal',
password_should_not_match: 'Le mot de passe pour le faux espace de stockage ne doit pas être le même que celui du stockage principal',
retype_password: 'Confirmation du mot de passe',
passwords_do_not_match: 'Vos mot de passe ne sont pas identiques, veillez ré-essayer',
success: 'Succès',
},
lnd: {
title: 'gérer vos fonds',
choose_source_wallet: 'Choisir un portefeuille source',
refill_lnd_balance: 'Déposer des fonds dans votre portfeuille Lightning',
refill: 'Déposer des fonds',
withdraw: 'Retirer des fonds',
expired: 'Expiré',
sameWalletAsInvoiceError: 'Vous ne pouvez pas payer une facture avec le même portefeuille utilisé pour la créer.',
},
};

View file

@ -21,7 +21,19 @@ let strings;
locale = locale.split('-');
locale = locale[0];
console.log('current locale:', locale);
if (locale === 'en' || locale === 'ru' || locale === 'ua' || locale === 'es' || locale === 'pt-br' || locale === 'pt-pt' || locale === 'de-de') {
if (
locale === 'en' ||
locale === 'ru' ||
locale === 'ua' ||
locale === 'es' ||
locale === 'fr-fr' ||
locale === 'pt-br' ||
locale === 'pt-pt' ||
locale === 'de-de' ||
locale === 'cs-cz' ||
locale === 'th-th' ||
locale === 'nl-nl'
) {
locale = locale.replace('-', '_');
strings.setLanguage(locale);
} else {
@ -38,7 +50,11 @@ strings = new Localization({
pt_pt: require('./pt_PT.js'),
es: require('./es.js'),
ua: require('./ua.js'),
de_de: require('.de_DE.js')
de_de: require('./de_DE.js'),
cs_cz: require('./cs_CZ.js'),
th_th: require('./th_TH.js'),
nl_nl: require('./nl_NL.js'),
fr_fr: require('./fr_FR.js'),
});
strings.saveLanguage = lang => AsyncStorage.setItem(AppStorage.LANG, lang);
@ -90,7 +106,7 @@ strings.formatBalance = (balance, toUnit) => {
return balance + ' ' + BitcoinUnit.BTC;
} else if (toUnit === BitcoinUnit.SATS) {
const value = new BigNumber(balance).multipliedBy(100000000);
return value.toString() + ' ' + BitcoinUnit.SATS;
return new Intl.NumberFormat().format(value.toString()) + ' ' + BitcoinUnit.SATS;
} else if (toUnit === BitcoinUnit.LOCAL_CURRENCY) {
return currency.BTCToLocalCurrency(balance);
}
@ -111,12 +127,12 @@ strings.formatBalanceWithoutSuffix = (balance, toUnit) => {
const value = new BigNumber(balance).dividedBy(100000000).toFixed(8);
return removeTrailingZeros(value);
} else if (toUnit === BitcoinUnit.SATS) {
return balance;
return new Intl.NumberFormat().format(balance);
} else if (toUnit === BitcoinUnit.LOCAL_CURRENCY) {
return currency.satoshiToLocalCurrency(balance);
}
}
return balance;
return balance.toString();
};
module.exports = strings;

216
loc/nl_NL.js Normal file
View file

@ -0,0 +1,216 @@
module.exports = {
_: {
storage_is_encrypted: 'Uw opslag is versleuteld. Wachtwoord is vereist om het te ontcijferen',
enter_password: 'Voer wachtwoord in',
bad_password: 'Verkeerd wachtwoord, probeer opnieuw',
months_ago: 'maanden geleden',
days_ago: 'dagen geleden',
hours_ago: 'uur geleden',
minutes_ago: 'minuten geleden',
never: 'nooit',
},
wallets: {
select_wallet: 'Selecteer portemonnee',
options: 'opties',
list: {
app_name: 'Blue Wallet',
title: 'portemonnees',
header: 'Een portemonnee vertegenwoordigt een geheime (privésleutel) en een adres' + 'dat u kunt delen om munten te ontvangen.',
add: 'Portemonnee toevoegen',
create_a_wallet: 'Portemonnee aanmaken',
create_a_wallet1: 'Het is gratis en u kunt er',
create_a_wallet2: 'zoveel maken als u wilt',
latest_transaction: 'laatste transactie',
empty_txs1: 'Uw transacties verschijnen hier,',
empty_txs2: 'geen transacties op dit moment',
tap_here_to_buy: 'Klik hier om Bitcoin te kopen',
},
reorder: {
title: 'Portemonnees opnieuw ordenen',
},
add: {
title: 'portemonnee toevoegen',
description:
'U kunt een back-up papieren portemonnee scannen (in WIF - Wallet Import Format) of een nieuwe portemonnee maken. Segwit-wallets worden standaard ondersteund.',
scan: 'Scannen',
create: 'Aanmaken',
label_new_segwit: 'Nieuwe SegWit',
label_new_lightning: 'Nieuwe Lightning',
wallet_name: 'portemonnee naam',
wallet_type: 'type',
or: 'of',
import_wallet: 'Portemonnee importeren',
imported: 'Geïmporteerd',
coming_soon: 'Komt binnenkort',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
},
details: {
title: 'Portemonnee',
address: 'Adres',
type: 'Type',
label: 'Label',
destination: 'bestemming',
description: 'beschrijving',
are_you_sure: 'Weet u het zeker?',
yes_delete: 'Ja, verwijderen',
no_cancel: 'Nee, annuleren',
delete: 'Verwijderen',
save: 'Opslaan',
delete_this_wallet: 'Verwijder deze portemonnee',
export_backup: 'Exporteren / back-up maken',
buy_bitcoin: 'Koop Bitcoin',
show_xpub: 'Toon portemonnee XPUB',
},
export: {
title: 'portemonnee exporteren',
},
xpub: {
title: 'portemonnee XPUB',
copiedToClipboard: 'Gekopieerd naar het klembord.',
},
import: {
title: 'importeren',
explanation:
'Schrijf hier uw ezelsbruggetje, privésleutel, WIF, of een ander formaat. BlueWallet zal zijn best doen om het juiste formaat te raden en uw portemonnee te importeren',
imported: 'Geïmporteerd',
error: 'Importeren mislukt. Zorg ervoor dat de verstrekte gegevens geldig zijn.',
success: 'Succes',
do_import: 'Importeren',
scan_qr: 'of QR-code scannen?',
},
scanQrWif: {
go_back: 'Ga terug',
cancel: 'Annuleren',
decoding: 'Decoderen',
input_password: 'Voer wachtwoord in',
password_explain: 'Dit is een BIP38-gecodeerde privésleutel',
bad_password: 'Verkeerd wachtwoord',
wallet_already_exists: "Zo'n portemonnee bestaat al",
bad_wif: 'Verkeerde WIF',
imported_wif: 'WIF geïmporteerd ',
with_address: ' met adres ',
imported_segwit: 'SegWit geïmporteerd',
imported_legacy: 'Legacy geïmporteerd',
imported_watchonly: 'Watch-only geïmporteerd',
},
},
transactions: {
list: {
tabBarLabel: 'Transacties',
title: 'transacties',
description: 'Een lijst met ingaande of uitgaande transacties van uw portemonnee',
conf: 'conf',
},
details: {
title: 'Transacties',
from: 'Invoer',
to: 'Uitgang',
copy: 'Kopiëren',
transaction_details: 'Transactie details',
show_in_block_explorer: 'Weergeven in blokverkenner',
},
},
send: {
header: 'Vertuur',
details: {
title: 'transacties aanmaken',
amount_field_is_not_valid: 'Bedrag veld is niet geldig',
fee_field_is_not_valid: 'Tarief is niet geldig',
address_field_is_not_valid: 'Adresveld is niet geldig',
total_exceeds_balance: 'Het verzendingsbedrag overschrijdt het beschikbare saldo.',
create_tx_error: 'Er is een fout opgetreden bij het maken van de transactie. Zorg ervoor dat het adres geldig is.',
address: 'adres',
amount_placeholder: 'te verzenden bedrag (in BTC)',
fee_placeholder: 'plus transactie vergoeding (in BTC)',
note_placeholder: 'notitie voor mezelf',
cancel: 'Annuleren',
scan: 'Scannen',
send: 'Verzenden',
create: 'Aanmaken',
remaining_balance: 'Resterende saldo',
},
confirm: {
header: 'Bevestig',
sendNow: 'Nu verzenden',
},
success: {
done: 'Klaar',
},
create: {
details: 'Details',
title: 'transactie aanmaken',
error: 'Fout bij het maken van transactie. Ongeldig adres of bedrag?',
go_back: 'Ga terug',
this_is_hex: 'Dit is de transactie-hex, ondertekend en klaar om op het netwerk te worden uitgezonden.',
to: 'Naar',
amount: 'Bedrag',
fee: 'Vergoeding',
tx_size: 'TX grootte',
satoshi_per_byte: 'Satoshi per byte',
memo: 'Memo',
broadcast: 'Uitzenden',
not_enough_fee: 'Niet genoeg vergoeding. Verhoog de vergoeding',
},
},
receive: {
header: 'Ontvang',
details: {
title: 'Deel dit adres met betaler',
share: 'delen',
copiedToClipboard: 'Gekopieerd naar het klembord.',
label: 'Omschrijving',
setAmount: 'Ontvang met bedrag',
},
},
buyBitcoin: {
header: 'Koop Bitcoin',
tap_your_address: 'Tik op uw adres om het naar het klembord te kopiëren:',
copied: 'Gekopieerd naar het klembord!',
},
settings: {
header: 'instellingen',
plausible_deniability: 'Plausibele ontkenning...',
storage_not_encrypted: 'Opslag: niet versleuteld',
storage_encrypted: 'Opslag: versleuteld',
password: 'Wachtwoord',
password_explain: 'Maak een wachtwoord aan dat u wilt gebruiken om de opslag te versleutelen',
retype_password: 'Geef nogmaals het wachtwoord',
passwords_do_not_match: 'Wachtwoorden komen niet overeen',
encrypt_storage: 'Versleutel opslag',
about: 'Over',
language: 'Taal',
currency: 'Valuta',
},
plausibledeniability: {
title: 'Plausibele ontkenning',
help:
'Onder bepaalde omstandigheden kunt u worden gedwongen om uw' +
' wachtwoord te onthullen. Om uw munten veilig te houden, kan ' +
'BlueWallet nog een versleutelde opslag aanmaken, met een ander ' +
'wachtwoord. Onder druk kunt u dit wachtwoord bekendmaken aan ' +
'de derde partij. Indien ingevoerd in BlueWallet, zal het nieuwe ' +
"nep'-opslagruimte worden ontgrendeld. Dit lijkt legitiem voor de " +
'derde partij, maar zal uw hoofdopslag met munten niet bekend maken ' +
'aan de derde partij',
help2:
'De nieuwe opslag zal volledig functioneel zijn en u kunt er ' + 'een minimum aantal munten opslaan zodat het geloofwaardig lijkt.',
create_fake_storage: 'Nep versleutelde opslag aanmaken',
go_back: 'Ga terug',
create_password: 'Wachtwoord aanmaken',
create_password_explanation: 'Wachtwoord voor nep-opslag hoort niet overeen te komen met wachtwoord voor uw hoofdopslag',
password_should_not_match: 'Wachtwoord voor nep-opslag hoort niet overeen te komen met wachtwoord voor uw hoofdopslag',
retype_password: 'Herhaal wachtwoord',
passwords_do_not_match: 'Wachtwoorden komen niet overeen, probeer het opnieuw',
success: 'Succes',
},
lnd: {
title: 'fondsen beheren',
choose_source_wallet: 'Kies een bron portemonnee',
refill_lnd_balance: 'Vul Lightning-portemonneesaldo bij',
refill: 'Bijvullen',
withdraw: 'Opvragen',
expired: 'Verlopen',
sameWalletAsInvoiceError: 'U kunt geen factuur betalen met dezelfde portemonnee die is gebruikt om de factuur te maken.',
},
};

View file

@ -1,125 +1,125 @@
module.exports = {
_: {
storage_is_encrypted: 'O armazenamento está encriptado. Uma password é necessaria para desencriptar',
enter_password: 'Inserir password',
bad_password: 'pasword errada, tentar novamente',
storage_is_encrypted: 'Os arquivos estão criptografados, é necessária uma senha',
enter_password: 'Inserir senha',
bad_password: 'Senha errada, tente outra vez',
months_ago: 'meses atrás',
days_ago: 'dias atrás',
hours_ago: 'horas atrás',
minutes_ago: 'minutos',
never: 'nunca...',
minutes_ago: 'minutos atrás',
never: 'nunca',
},
wallets: {
options: 'options',
select_wallet: 'Select Wallet',
options: 'opções',
select_wallet: 'Escolher carteira',
list: {
tabBarLabel: 'Wallets',
tabBarLabel: 'Carteiras',
app_name: 'Blue Wallet',
title: 'Wallets',
header: 'Uma wallet representa um par entre um segredo (chave privada) e um endereço' + 'que pode partilhar para receber Bitcoin.',
title: 'carteiras',
header: 'Uma carteira representa um par composto de uma chave privada e um endereço que você pode .',
add: 'adicionar wallet',
create_a_wallet: 'Criar uma wallet',
create_a_wallet1: 'Gratuito e pode criar',
create_a_wallet2: ' quantas quiser',
create_a_wallet: 'Criar uma carteira',
create_a_wallet1: 'é grátis e você pode criar',
create_a_wallet2: 'quantas você quiser',
latest_transaction: 'última transação',
empty_txs1: 'Suas transações aparecerão aqui',
empty_txs2: 'nenhuma de momento',
tap_here_to_buy: 'Tap here to buy Bitcoin',
empty_txs1: 'Suas transações aparecerão aqui,',
empty_txs2: 'nenhuma no momento',
tap_here_to_buy: 'Toque aqui para comprar Bitcoin',
},
reorder: {
title: 'Reorder Wallets',
title: 'Reordenar carteiras',
},
add: {
title: 'Adicionar Wallet',
title: 'criando carteira',
description:
'Pode fazer um scaneamento de um backup de uma wallet em papel (em WIF - Wallet Import Format), ou criar uma nova wallet. Segwit suportado por defeito.',
scan: 'Scanear',
'Você pode ler o backup de uma carteira (em WIF - Wallet Import Format) ou criar uma nova. O padrão é criar uma carteira SegWit.',
scan: 'Ler backup',
create: 'Criar',
label_new_segwit: 'Novo SegWit',
label_new_lightning: 'Novo Lightning',
wallet_name: 'Nome',
wallet_type: 'Tipo',
label_new_segwit: 'Nova carteira SegWit',
label_new_lightning: 'Nova carteira Lightning',
wallet_name: 'nome',
wallet_type: 'tipo',
or: 'ou',
import_wallet: 'Importar wallet',
import_wallet: 'Importar carteira',
imported: 'Importado',
coming_soon: 'Brevemente',
coming_soon: 'Em breve',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
},
details: {
title: 'wallet',
title: 'Carteira',
address: 'Endereço',
type: 'Tipo',
destination: 'destination',
description: 'description',
destination: 'destino',
description: 'descrição',
label: 'Nome',
are_you_sure: 'Tem a certeza?',
yes_delete: 'Sim, eliminar',
are_you_sure: 'Tem certeza?',
yes_delete: 'Sim, apagar',
no_cancel: 'Não, cancelar',
delete_this_wallet: 'Apagar esta wallet',
delete_this_wallet: 'Apagar esta carteira',
export_backup: 'Exportar / backup',
buy_bitcoin: 'Buy Bitcoin',
show_xpub: 'Show wallet XPUB',
delete: 'Delete',
save: 'Save',
buy_bitcoin: 'Comprar Bitcoin',
show_xpub: 'Ver XPUB',
delete: 'Apagar',
save: 'Salvar',
},
export: {
title: 'Exportar Wallet',
title: 'Exportar carteira',
},
xpub: {
title: 'wallet XPUB',
copiedToClipboard: 'copiado para clip board',
title: 'XPUB',
copiedToClipboard: 'Copiado para a área de transferência',
},
import: {
title: 'importar',
explanation:
'Escreva aqui sua frase mnemônica, chave privada, WIF, etc. Vamos fazer nosso melhor para importat a sua wallet em qualquer formato',
'Escreva aqui sua frase mnemônica, chave privada, WIF, ou o que você tiver. Faremos nosso melhor para adivinhar o formato e importat sua carteira',
imported: 'Importada',
error: 'Falhou. é um formato válido?',
error: 'Erro. Por favor, confira se o formato que você passou é válido.',
success: 'Sucesso',
do_import: 'Importar',
scan_qr: 'ou scan um QR code?',
scan_qr: 'ou ler um código QR?',
},
scanQrWif: {
go_back: 'Voltar',
cancel: 'Cancelar',
decoding: 'Descodificar',
input_password: 'Inserir password',
password_explain: 'Isto é um BIP38 chave privada encriptada',
bad_password: 'Password errada',
wallet_already_exists: 'Esta wallet já existe',
decoding: 'Decodificar',
input_password: 'Inserir senha',
password_explain: 'Isto é um chave privada criptografada BIP38',
bad_password: 'Senha errada',
wallet_already_exists: 'Esta carteira já existe',
bad_wif: 'WIF errado',
imported_wif: 'WIF transferido ',
imported_wif: 'WIF importado ',
with_address: ' com endereço ',
imported_segwit: 'SegWit transferido',
imported_legacy: 'Legacy transferido',
imported_watchonly: 'Watch-only importada',
imported_segwit: 'Carteira SegWit importada',
imported_legacy: 'Carteira antiga importada',
imported_watchonly: 'Carteira somente-leitura importada',
},
},
transactions: {
list: {
tabBarLabel: 'Transações',
title: 'Transações',
description: 'Uma lista de transações feitas ou recebidas nas suas wallets',
description: 'Uma lista de transações feitas ou recebidas nas suas carteiras',
conf: 'conf',
},
details: {
title: 'transação',
title: 'Transação',
from: 'De',
to: 'Para',
copy: 'Copiar',
transaction_details: 'Transaction details',
show_in_block_explorer: 'Show in block explorer',
transaction_details: 'Detalhes',
show_in_block_explorer: 'Mostrar num navegador',
},
},
send: {
header: 'Enviar',
confirm: {
header: 'Confirm',
sendNow: 'Send now',
header: 'Confirmar',
sendNow: 'Enviar agora',
},
success: {
done: 'Done',
done: 'Enviado',
},
details: {
title: 'Criar Transacção',
@ -134,53 +134,53 @@ module.exports = {
cancel: 'Cancelar',
scan: 'Scanear',
create: 'Criar',
address: 'Address',
total_exceeds_balance: 'The total amount exceeds balance',
address: 'Endereço',
total_exceeds_balance: 'Valor total excede o saldo disponível',
send: 'Send',
remaining_balance: 'Saldo restante',
},
create: {
title: 'Criar Transacção',
details: 'Details',
error: 'Erro ao criar transação. Endereço inválido ou quantia?',
details: 'Detalhes',
error: 'Erro ao criar transação. Endereço ou valor inválidos?',
go_back: 'Voltar',
this_is_hex: 'Este é o hex da transação, assinado e pronto para ser difundido para a network. Continuar?',
this_is_hex: 'Este é o hex da transação, assinado e pronto para ser divulgado para o mundo. Continuar?',
to: 'Para',
amount: 'Quantia',
amount: 'Valor',
fee: 'Taxa',
tx_size: 'Tamanho TX',
satoshi_per_byte: 'satoshiPerByte',
memo: 'Nota pessoal',
broadcast: 'Difundir',
not_enough_fee: 'Taxa demasiado baixa. Aumente a taxa',
tx_size: 'Tamanho',
satoshi_per_byte: 'satoshis por byte',
memo: 'Nota',
broadcast: 'Divulgar',
not_enough_fee: 'Taxa muito baixa. Aumente a taxa',
},
},
receive: {
header: 'receber',
header: 'Receber',
details: {
title: 'Partilhar este endereço com o pagador',
share: 'partilhar',
copiedToClipboard: 'copiado para clip board',
label: 'Description',
setAmount: 'Receive with amount',
title: 'Envie este endereço para o pagador',
share: 'Compartilhar',
copiedToClipboard: 'Copiado para a área de trabalho',
label: 'Descrição',
setAmount: 'Valor a receber',
},
},
buyBitcoin: {
header: 'Buy Bitcoin',
tap_your_address: 'Tap your address to copy it to clipboard:',
copied: 'Copied to Clipboard!',
header: 'Comprar Bitcoin',
tap_your_address: 'Toque seu endereço para copiá-lo para a área de transferência:',
copied: 'Copiado!',
},
settings: {
tabBarLabel: 'Definições',
tabBarLabel: 'preferências',
header: 'definições',
plausible_deniability: 'Negação plausível...',
storage_not_encrypted: 'Armazenamento: não encriptado',
storage_encrypted: 'Armazenamento: encriptado',
password: 'Password',
password_explain: 'Definir a password para desencriptar o armazenamento',
retype_password: 'Inserir password novamente',
passwords_do_not_match: 'Passwords não coincidem',
encrypt_storage: 'Encriptar',
storage_not_encrypted: 'Arquivos: não criptografados',
storage_encrypted: 'Arquivos: criptografados',
password: 'Senha',
password_explain: 'Definir a senha para descriptografar os arquivos',
retype_password: 'Inserir senha novamente',
passwords_do_not_match: 'Senhas não coincidem',
encrypt_storage: 'Criptografar',
about: 'Sobre',
language: 'Idioma',
currency: 'Moeda',
@ -188,28 +188,30 @@ module.exports = {
plausibledeniability: {
title: 'Negação plausível',
help:
'Em algumas circunstâncias, pode ser forçado a relevar uma ' +
'password. Para manter as suas moedas seguras, A BlueWallet pode criar outro ' +
'armazenamento encriptado, com uma password diferente. Sobre pressão, ' +
'pode revelar esta password a um terceiro. Se inserida na ' +
'BlueWallet, esta vai abrir um armazenamento "falso". Que vai parecer ' +
'legítimo a um terceiro, mas que secretamente vai manter o seu armazenamento principal ' +
'com as moedas em segurança.',
help2: 'Este novo armazenamento é completamente funcional, e pode guardar ' + 'um valor minímo para parecer mais real.',
create_fake_storage: 'Criar armazenamento encriptado FALSO',
'Em algumas circunstâncias, você pode ser forçado a revelar uma ' +
'senha. Para manter seus bitcoins seguros, A BlueWallet pode criar ' +
'uma senha alternativa. Sob pressão, você pode revelar essa senha ao ' +
'invés da senha principal. Quando inserida na BlueWallet, esta abrirá ' +
'uma interface falsa, que parecerá legítima a um terceiro, enquanto ' +
'suas carteiras originais continuarão à salvo em segredo.',
help2:
'Essa nova interface é completamente funcional e você pode inclusive ' + 'manter nele um valor minímo para que pareça mais real.',
create_fake_storage: 'Criar armazenamento criptografada falsa',
go_back: 'Voltar',
create_password: 'Criar password',
create_password_explanation: 'Password para armazenamento FALSO não deve coincidir com o principal',
password_should_not_match: 'Password para armazenamento FALSO não deve coincidir com o principal',
retype_password: 'Inserir password novamente',
passwords_do_not_match: 'Passwords não coincidem, tente novamente',
create_password: 'Criar senha',
create_password_explanation: 'A senha para a interface falsa não deve coincidir com a principal',
password_should_not_match: 'A senha para a interface falsa não deve coincidir com a principal',
retype_password: 'Inserir senha novamente',
passwords_do_not_match: 'Senhas não coincidem, tente novamente',
success: 'Sucesso',
},
lnd: {
title: 'gerenciar fundos',
choose_source_wallet: 'Escolha a sua wallet',
refill_lnd_balance: 'Carregar o saldo da Lightning wallet',
refill: 'Carregar',
withdraw: 'Transferir',
title: 'manejar fundos',
choose_source_wallet: 'Escolha a carteira de origem',
refill_lnd_balance: 'Recarregar a carteira Lightning',
refill: 'Recarregar',
withdraw: 'Sacar',
expired: 'Vencido',
sameWalletAsInvoiceError: 'Você não pode pagar uma fatura com a mesma carteira que a criou.',
},
};

View file

@ -210,5 +210,7 @@ module.exports = {
refill_lnd_balance: 'Carregar o saldo da Lightning wallet',
refill: 'Carregar',
withdraw: 'Transferir',
expired: 'Expired',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
},
};

View file

@ -211,5 +211,7 @@ module.exports = {
refill_lnd_balance: 'Пополнить баланс Lightning кошелька',
refill: 'Пополнить',
withdraw: 'Вывести',
expired: 'Expired',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
},
};

213
loc/th_TH.js Normal file
View file

@ -0,0 +1,213 @@
module.exports = {
_: {
storage_is_encrypted: 'ที่เก็บข้อมูลของคุณถูกเข้ารหัส. ต้องการรหัสผ่านเพื่อถอดรหัส',
enter_password: 'กรุณาใส่รหัสผ่าน',
bad_password: 'รหัสผ่านไม่ถูกต้อง กรุณาใส่รหัสผ่านอีกครั้ง',
months_ago: 'เดือนที่แล้ว',
days_ago: 'วันที่แล้ว',
hours_ago: 'ชั่วโมงที่แล้ว',
minutes_ago: 'นาทีที่แล้ว',
never: 'ไม่เคย',
},
wallets: {
select_wallet: 'เลือกกระเป๋าสตางค์',
options: 'ทางเลือก',
list: {
app_name: 'บูลวอลเล็ต',
title: 'กระเป๋าสตางค์',
header: 'กระเป๋าสตางค์คือที่เก็บไพร์เวทคีย์และแอดเดรส' + 'ที่คุณสามารถแชร์เพื่อโอนรับเหรียญ.',
add: 'เพิ่มกระเป๋าสตางค์',
create_a_wallet: 'สร้างกระเป๋าสตางค์',
create_a_wallet1: 'ไม่มีค่าใช้จ่าย และคุณสามารถสร้างกระเป๋าสตางค์',
create_a_wallet2: 'ได้มากเท่าที่ต้องการ',
latest_transaction: 'ธุรกรรมล่าสุด',
empty_txs1: 'ธุรกรรมจะปรากฏที่นี่,',
empty_txs2: 'ไม่มี ณ ขณะนี้',
tap_here_to_buy: 'กดที่นี่เพื่อซื้อบิตคอยน์',
},
reorder: {
title: 'เปลี่ยนลำดับกระเป๋าสตางค์',
},
add: {
title: 'เพิ่มกระเป๋าสตางค์',
description:
'คุณสามรถสแกนกระเป๋าสตางค์กระดาษ(ในรูปแบบ WIF - Wallet Import Format), หรือสร้างกระเป๋าสตางค์ใหม่. กระเป๋าสตางค์ใหม่จะใช้ Segwit โดยอัตโนมัติ.',
scan: 'สแกน',
create: 'สร้าง',
label_new_segwit: 'SegWit ใหม่',
label_new_lightning: 'ไลท์นิงใหม่',
wallet_name: 'ชื่อกระเป๋าสตางค์',
wallet_type: 'ชนิด',
or: 'หรือ',
import_wallet: 'นำเข้ากระเป๋าสตางค์',
imported: 'นำเข้าแล้ว',
coming_soon: 'เร็วๆนี้',
lightning: 'ไลท์นิง',
bitcoin: 'บิตคอยน์',
},
details: {
title: 'กระเป๋าสตางค์',
address: 'แอดเดรส',
type: 'ชนิด',
label: 'ป้าย',
destination: 'เป้าหมาย',
description: 'คำอธิบาย',
are_you_sure: 'คุณแน่ใจหรือไม่?',
yes_delete: 'ใช่, ลบเลย',
no_cancel: 'ไม่ใช่, ยกเลิก',
delete: 'ลบ',
save: 'เก็บ',
delete_this_wallet: 'ลบกระเป๋าสตางค์อันนี้',
export_backup: 'ส่งออก / สำรอง',
buy_bitcoin: 'ซื้อบิตคอยน์',
show_xpub: 'แสดง XPUB ของกระเป๋าสตางค์',
},
export: {
title: 'ส่งออกกระเป๋าสตางค์',
},
xpub: {
title: 'XPUB ของกระเป๋าสตางค์',
copiedToClipboard: 'ก๊อปปี้ไปที่คลิปบอร์ดแล้ว.',
},
import: {
title: 'นำเข้า',
explanation: 'บันทึกนีโมนิค(สิ่งที่ช่วยให้จำได้), ไพร์เวทคีย์, WIF, และทุกๆอย่าง. บูลวอลเล็ทจะพยายามนำเข้ากระเป๋าสตางค์ของคุณ',
imported: 'นำเข้าแล้ว',
error: 'ไม่สามารถนำเข้าได้. กรุณาตรวจสอบข้อมูลให้ถูกต้อง.',
success: 'สำเร็จ',
do_import: 'นำเข้า',
scan_qr: 'หรือสแกนรหัสคิวอาร์แทน?',
},
scanQrWif: {
go_back: 'กลับ',
cancel: 'ยกเลิก',
decoding: 'กำลังถอดรหัส',
input_password: 'ใส่รหัสผ่าน',
password_explain: 'นี่คือไพร์เวทคีย์ที่เข้ารหัสแบบ BIP38',
bad_password: 'รหัสไม่ถูกต้อง',
wallet_already_exists: 'กระเป๋าสตางค์นี้มีอยู่แล้ว',
bad_wif: 'WIF ไม่ถูกต้อง',
imported_wif: 'WIF ที่นำเข้า',
with_address: ' ด้วยแอดเดรส ',
imported_segwit: 'SegWit ที่นำเข้า',
imported_legacy: 'Legacy ที่นำเข้า',
imported_watchonly: 'Watch-only ที่นำเข้า',
},
},
transactions: {
list: {
tabBarLabel: 'ธุรกรรม',
title: 'ธุรกรรม',
description: 'รายการธุรกรรมเข้าออกของกระเป๋าสตางค์ของคุณ',
conf: 'conf',
},
details: {
title: 'ธุรกรรม',
from: 'อินพุท',
to: 'เอ้าพุท',
copy: 'ก๊อปปี้',
transaction_details: 'รายละเอียดธุรกรรม',
show_in_block_explorer: 'แสดงด้วย block explorer',
},
},
send: {
header: 'ส่ง',
details: {
title: 'สร้างธุรกรรม',
amount_field_is_not_valid: 'จำนวนเงินไม่ถูกต้อง',
fee_field_is_not_valid: 'ค่าธรรมเนียมไม่ถูกต้อง',
address_field_is_not_valid: 'แอดเดรสไม่ถูกต้อง',
total_exceeds_balance: 'จำนวนเงินที่จะส่งเกินเงินที่มี.',
create_tx_error: 'ไม่สามารถสร้างธุรกรรมได้. กรุณาตรวจสอบแอดเดรสให้ถูกต้อง.',
address: 'แอดเดรส',
amount_placeholder: 'จำนวนเงินที่ส่ง (หน่วย BTC)',
fee_placeholder: 'รวมค่าธรรมเนียม (หน่วย BTC)',
note_placeholder: 'หมายเหตุถึงตัวท่านเอง',
cancel: 'ยกเลิก',
scan: 'สแกน',
send: 'ส่ง',
create: 'สร้าง',
remaining_balance: 'ยอดคงเหลือ',
},
confirm: {
header: 'ยืนยัน',
sendNow: 'ส่งเดี๋ยวนี้',
},
success: {
done: 'สำเร็จ',
},
create: {
details: 'รายละเอียด',
title: 'สร้างธุรกรรม',
error: 'ไม่สามารถสร้างธุรกรรมได้. แอดเดรสไม่ถูกต้อง หรือ จำนวนเงินไม่ถูกต้อง?',
go_back: 'กลับ',
this_is_hex: 'นี่คือ hex ของธุรกรรม, signed แล้วและพร้อมที่จะบรอดคาซท์ไปยังเน็ตเวิร์ค.',
to: 'ถึง',
amount: 'จำนวนเงิน',
fee: 'ค่าธรรมเนียม',
tx_size: 'ขนาดธุรกรรม',
satoshi_per_byte: 'ซาโตชิต่อไบท์',
memo: 'บันทึกช่วยจำ',
broadcast: 'บรอดคาซท์',
not_enough_fee: 'ค่าธรรมเนียมไม่เพียงพอ. กรุณาเพิ่มค่าธรรมเนียม',
},
},
receive: {
header: 'รับ',
details: {
title: 'แชร์แอดเดรสนี้กับผู้จ่าย',
share: 'แชร์',
copiedToClipboard: 'ก๊อปปี้ไปที่คลิปบอร์ดแล้ว.',
label: 'คำอธิบาย',
setAmount: 'รับด้วยจำนวน',
},
},
buyBitcoin: {
header: 'ซื้อบิตคอยน์',
tap_your_address: 'กดที่แอดเดรสของคุณเพื่อก๊อปปี้ไปยังคลิปบอร์ด:',
copied: 'ก๊อปปี้ไปที่คลิปบอร์ดแล้ว!',
},
settings: {
header: 'ตั้งค่า',
plausible_deniability: 'การปฏิเสธที่เป็นไปได้...',
storage_not_encrypted: 'ที่เก็บข้อมูล: ยังไม่เข้ารหัส',
storage_encrypted: 'ที่เก็บข้อมูล: เข้ารหัสแล้ว',
password: 'รหัสผ่าน',
password_explain: 'สร้างรหัสผ่านที่จะใช้ในการเข้ารหัสที่เก็บข้อมูล',
retype_password: 'ใส่รหัสผ่านอีกครั้ง',
passwords_do_not_match: 'รหัสผ่านไม่ตรงกัน',
encrypt_storage: 'เข้ารหัสที่เก็บข้อมูล',
about: 'เกี่ยวกับ',
language: 'ภาษา',
currency: 'เงินตรา',
},
plausibledeniability: {
title: 'การปฏิเสธที่เป็นไปได้',
help:
'ภายใต้บางสถานการ์ณ, คุณอาจจะจำเป็นต้องเปิดเผย' +
'รหัสผ่าน. เพื่อเก็บเหรียญให้ปลอดถัย บูลวอลเล็ทสามารถสร้างที่เก็บข้อมูล' +
'อีกแห่งหนึ่งโดยใช้รหัสผ่านคนละอัน. ภายใต้สถานการ์ณที่จำเป็น ' +
'คุณสามารถเปิดเลยรหัสผ่านนี้กับบุคคลที่สาม. และเมื่อใส่รหัสผ่านนี้ใน ' +
'บลูวอลเล็ท ที่เก็บข้อมูลเทียมจะถูกเปิด. และ' +
'น่าจะเป็นที่ยอมรับได้ต่อบุคลที่สาม, วิธีนี้จะทำให้ที่เก็บข้อมูลหลักมีความปลอดภัย' +
'และเป็นความลับ.',
help2: 'ที่เก็บข้อมูลอันใหม่จะทำงานได้สมบูรณ์ และคุณสามารถเก็บจำนวนเงินขั้นต่ำได้ ' + 'โดยที่มีความน่าเชื่อถือ.',
create_fake_storage: 'สร้างที่เก็บข้อมูลเทียม',
go_back: 'กลับ',
create_password: 'สร้างรหัสผ่าน',
create_password_explanation: 'รหัสผ่านสำหรับที่เก็บข้อมูลเทียมไม่ควรตรงกับรหัสผ่านที่ใช้กับที่เก็บข้อมูลเทียมจริง',
password_should_not_match: 'รหัสผ่านสำหรับที่เก็บข้อมูลเทียมไม่ควรตรงกับรหัสผ่านที่ใช้กับที่เก็บข้อมูลเทียมจริง',
retype_password: 'ใส่รหัสผ่านอีกครั้ง ใส่รหัสผ่านอีกครั้ง',
passwords_do_not_match: 'รหัสผ่านไม่ตรงกัน ',
success: 'Success',
},
lnd: {
title: 'จัดการเงิน',
choose_source_wallet: 'เลือกกระเป๋าสตางค์',
refill_lnd_balance: 'เติมกระเป๋าสตางค์ไลท์นิง',
refill: 'เติม',
withdraw: 'ถอน',
expired: 'หมดอายุแล้ว',
sameWalletAsInvoiceError: 'คุณไม่สามารถจ่ายใบแจ้งหนี้นี้ด้วยกระเป๋าสตางค์อันเดียวกันกับที่ใช้สร้างมัน.',
},
};

View file

@ -211,5 +211,7 @@ module.exports = {
refill_lnd_balance: 'Збільшити баланс Lightning гаманця',
refill: 'Поповнити',
withdraw: 'Вивести',
expired: 'Expired',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
},
};

View file

@ -1,4 +1,14 @@
export const FiatUnit = Object.freeze({
USD: { endPointKey: 'btcusd', storageKey: 'BTC_USD', formatterValue: 'USD', symbol: '$' },
EUR: { endPointKey: 'btceur', storageKey: 'BTC_EUR', formatterValue: 'EUR', symbol: '€' },
USD: { endPointKey: 'USD', symbol: '$', locale: 'en-US' },
AUD: { endPointKey: 'AUD', symbol: '$', locale: 'en-AU' },
EUR: { endPointKey: 'EUR', symbol: '€', locale: 'en-EN' },
GBP: { endPointKey: 'GBP', symbol: '£', locale: 'en-GB' },
RUB: { endPointKey: 'RUB', symbol: '₽', locale: 'ru-RU' },
CAD: { endPointKey: 'CAD', symbol: '$', locale: 'en-CA' },
CNY: { endPointKey: 'CNY', symbol: '¥', locale: 'zh-CN' },
JPY: { endPointKey: 'JPY', symbol: '¥', locale: 'ja-JP' },
INR: { endPointKey: 'INR', symbol: '₹', locale: 'hi-HN' },
VEF: { endPointKey: 'VEF', symbol: 'Bs.', locale: 'es-VE' },
SGD: { endPointKey: 'SGD', symbol: 'S$', locale: 'zh-SG' },
ZAR: { endPointKey: 'ZAR', symbol: 'R', locale: 'en-ZA' },
});

View file

@ -1,6 +1,6 @@
{
"name": "BlueWallet",
"version": "3.4.0",
"version": "3.5.5",
"devDependencies": {
"babel-eslint": "^8.2.6",
"babel-jest": "23.6.0",

View file

@ -1,6 +1,6 @@
import prompt from 'react-native-prompt-android';
module.exports = (title, text, isCancelable = true) => {
module.exports = (title, text, isCancelable = true, type = 'secure-text') => {
return new Promise((resolve, reject) => {
const buttons = isCancelable
? [
@ -30,7 +30,7 @@ module.exports = (title, text, isCancelable = true) => {
];
prompt(title, text, buttons, {
type: 'secure-text',
type: type,
cancelable: isCancelable,
});
});

View file

@ -0,0 +1,126 @@
/* global alert */
import React, { Component } from 'react';
import { ActivityIndicator, View, TextInput, KeyboardAvoidingView, Keyboard, TouchableWithoutFeedback, Text } from 'react-native';
import { BlueNavigationStyle, BlueButton, BlueBitcoinAmount } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
let EV = require('../../events');
let loc = require('../../loc');
export default class LNDCreateInvoice extends Component {
static navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
title: loc.receive.header,
});
constructor(props) {
super(props);
// fallback to first wallet if it exists
const fromWallet = props.navigation.getParam('fromWallet');
this.state = {
fromWallet,
description: '',
isLoading: false,
};
}
async createInvoice() {
this.setState({ isLoading: true }, async () => {
try {
const invoiceRequest = await this.state.fromWallet.addInvoice(this.state.amount, this.state.description);
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
ReactNativeHapticFeedback.trigger('notificationSuccess', false);
this.props.navigation.navigate('LNDViewInvoice', {
invoice: invoiceRequest,
fromWallet: this.state.fromWallet,
});
} catch (_error) {
ReactNativeHapticFeedback.trigger('notificationError', false);
this.setState({ isLoading: false });
alert('Error');
}
});
}
renderCreateButton = () => {
return (
<View style={{ paddingHorizontal: 56, paddingVertical: 16, alignContent: 'center', backgroundColor: '#FFFFFF' }}>
{this.state.isLoading ? (
<ActivityIndicator />
) : (
<BlueButton
disabled={!(this.state.description.length > 0 && this.state.amount > 0)}
onPress={() => this.createInvoice()}
title={loc.send.details.create}
/>
)}
</View>
);
};
render() {
if (!this.state.fromWallet) {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
<Text>System error: Source wallet not found (this should never happen)</Text>
</View>
);
}
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
<View style={{ flex: 1, justifyContent: 'space-between' }}>
<View style={{ flex: 1, backgroundColor: '#FFFFFF' }}>
<KeyboardAvoidingView behavior="position">
<BlueBitcoinAmount
isLoading={this.state.isLoading}
amount={this.state.amount}
onChangeText={text => {
this.setState({ amount: text });
}}
disabled={this.state.isLoading}
unit={BitcoinUnit.SATS}
/>
<View
style={{
flexDirection: 'row',
borderColor: '#d2d2d2',
borderBottomColor: '#d2d2d2',
borderWidth: 1.0,
borderBottomWidth: 0.5,
backgroundColor: '#f5f5f5',
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
marginVertical: 8,
borderRadius: 4,
}}
>
<TextInput
onChangeText={text => this.setState({ description: text })}
placeholder={loc.receive.details.label}
value={this.state.description}
numberOfLines={1}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
editable={!this.state.isLoading}
/>
</View>
{this.renderCreateButton()}
</KeyboardAvoidingView>
</View>
</View>
</TouchableWithoutFeedback>
);
}
}
LNDCreateInvoice.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.function,
navigate: PropTypes.func,
getParam: PropTypes.func,
}),
};

View file

@ -0,0 +1,101 @@
/* global alert */
import React, { Component } from 'react';
import { Animated, StyleSheet, View, TouchableOpacity, Clipboard, Share } from 'react-native';
import { BlueLoading, SafeBlueArea, BlueButton, BlueNavigationStyle, BlueText, BlueSpacing20 } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { QRCode } from 'react-native-custom-qr-codes';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
const loc = require('../../loc');
export default class LNDViewAdditionalInvoiceInformation extends Component {
static navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true, () => navigation.dismiss()),
title: 'Additional Information',
});
state = { walletInfo: undefined };
copyToClipboard = () => {
this.setState({ addressText: loc.receive.details.copiedToClipboard }, () => {
Clipboard.setString(this.state.walletInfo.uris[0]);
setTimeout(() => this.setState({ addressText: this.state.walletInfo.uris[0] }), 1000);
});
};
async componentDidMount() {
const fromWallet = this.props.navigation.getParam('fromWallet');
try {
await fromWallet.fetchInfo();
} catch (_) {
alert('Network error');
return;
}
this.setState({ walletInfo: fromWallet.info_raw, addressText: fromWallet.info_raw.uris[0] });
}
render() {
if (typeof this.state.walletInfo === 'undefined') {
return (
<SafeBlueArea style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<BlueLoading />
</SafeBlueArea>
);
}
return (
<SafeBlueArea style={{ flex: 1 }}>
<View style={{ flex: 1, justifyContent: 'space-between', alignItems: 'center' }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}>
<QRCode
content={this.state.walletInfo.uris[0]}
size={300}
color={BlueApp.settings.foregroundColor}
backgroundColor={BlueApp.settings.brandingColor}
logo={require('../../img/qr-code.png')}
/>
<BlueSpacing20 />
<BlueText>Open direct channel with this node:</BlueText>
<TouchableOpacity onPress={this.copyToClipboard}>
<Animated.Text style={styles.address} numberOfLines={0}>
{this.state.addressText}
</Animated.Text>
</TouchableOpacity>
</View>
<View style={{ marginBottom: 24 }}>
<BlueButton
icon={{
name: 'share-alternative',
type: 'entypo',
color: BlueApp.settings.buttonTextColor,
}}
onPress={async () => {
Share.share({
message: this.state.walletInfo.uris[0],
});
}}
title={loc.receive.details.share}
/>
</View>
</View>
</SafeBlueArea>
);
}
}
const styles = StyleSheet.create({
address: {
marginVertical: 32,
fontSize: 15,
color: '#9aa0aa',
textAlign: 'center',
},
});
LNDViewAdditionalInvoiceInformation.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.function,
getParam: PropTypes.function,
dismiss: PropTypes.function,
}),
};

View file

@ -0,0 +1,245 @@
/* global alert */
import React, { Component } from 'react';
import { Animated, StyleSheet, View, TouchableOpacity, Clipboard, Dimensions, Share } from 'react-native';
import { BlueLoading, BlueText, SafeBlueArea, BlueButton, BlueNavigationStyle, BlueSpacing20 } from '../../BlueComponents';
import PropTypes from 'prop-types';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { Icon } from 'react-native-elements';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
const loc = require('../../loc');
const EV = require('../../events');
const QRFast = require('react-native-qrcode');
const { width, height } = Dimensions.get('window');
export default class LNDViewInvoice extends Component {
static navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true, () => navigation.dismiss()),
title: 'Lightning Invoice',
headerLeft: null,
});
constructor(props) {
super(props);
const invoice = props.navigation.getParam('invoice');
const fromWallet = props.navigation.getParam('fromWallet');
this.state = {
invoice,
fromWallet,
isLoading: typeof invoice === 'string',
addressText: typeof invoice === 'object' && invoice.hasOwnProperty('payment_request') ? invoice.payment_request : invoice,
isFetchingInvoices: true,
qrCodeHeight: height > width ? height / 2.5 : width / 2,
};
this.fetchInvoiceInterval = undefined;
}
async componentDidMount() {
this.fetchInvoiceInterval = setInterval(async () => {
if (this.state.isFetchingInvoices) {
try {
const userInvoices = JSON.stringify(await this.state.fromWallet.getUserInvoices());
const updatedUserInvoice = JSON.parse(userInvoices).filter(invoice =>
typeof this.state.invoice === 'object'
? invoice.payment_request === this.state.invoice.payment_request
: invoice.payment_request === this.state.invoice,
)[0];
if (typeof updatedUserInvoice !== 'undefined') {
this.setState({ invoice: updatedUserInvoice, isLoading: false, addressText: updatedUserInvoice.payment_request });
if (updatedUserInvoice.ispaid) {
// we fetched the invoice, and it is paid :-)
this.setState({ isFetchingInvoices: false });
ReactNativeHapticFeedback.trigger('notificationSuccess', false);
clearInterval(this.fetchInvoiceInterval);
this.fetchInvoiceInterval = undefined;
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
} else {
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = updatedUserInvoice.timestamp + updatedUserInvoice.expire_time;
if (invoiceExpiration < now && !updatedUserInvoice.ispaid) {
// invoice expired :-(
this.setState({ isFetchingInvoices: false });
ReactNativeHapticFeedback.trigger('notificationError', false);
clearInterval(this.fetchInvoiceInterval);
this.fetchInvoiceInterval = undefined;
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
}
}
}
} catch (error) {
clearInterval(this.fetchInvoiceInterval);
this.fetchInvoiceInterval = undefined;
console.log(error);
alert(error);
this.props.navigation.dismiss();
}
}
}, 3000);
}
componentWillUnmount() {
clearInterval(this.fetchInvoiceInterval);
this.fetchInvoiceInterval = undefined;
}
copyToClipboard = () => {
this.setState({ addressText: loc.receive.details.copiedToClipboard }, () => {
Clipboard.setString(this.state.invoice.payment_request);
setTimeout(() => this.setState({ addressText: this.state.invoice.payment_request }), 1000);
});
};
onLayout = () => {
const { height } = Dimensions.get('window');
this.setState({ qrCodeHeight: height > width ? height / 2.5 : width / 2 });
};
render() {
if (this.state.isLoading) {
return <BlueLoading />;
}
const { invoice } = this.state;
if (typeof invoice === 'object') {
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = invoice.timestamp + invoice.expire_time;
if (invoice.ispaid || invoice.type === 'paid_invoice') {
return (
<SafeBlueArea style={{ flex: 1 }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<View
style={{
backgroundColor: '#ccddf9',
width: 120,
height: 120,
borderRadius: 60,
alignSelf: 'center',
justifyContent: 'center',
marginTop: 43,
marginBottom: 53,
}}
>
<Icon name="check" size={50} type="font-awesome" color="#0f5cc0" />
</View>
<BlueText>This invoice has been paid for</BlueText>
</View>
</SafeBlueArea>
);
}
if (invoiceExpiration < now && !invoice.ispaid) {
return (
<SafeBlueArea style={{ flex: 1 }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<View
style={{
backgroundColor: '#ccddf9',
width: 120,
height: 120,
borderRadius: 60,
alignSelf: 'center',
justifyContent: 'center',
marginTop: 43,
marginBottom: 53,
}}
>
<Icon name="times" size={50} type="font-awesome" color="#0f5cc0" />
</View>
<BlueText>This invoice was not paid for and has expired</BlueText>
</View>
</SafeBlueArea>
);
} else if (invoiceExpiration > now && invoice.ispaid) {
if (invoice.ispaid) {
return (
<SafeBlueArea style={{ flex: 1 }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<BlueText>'This invoice has been paid for.'</BlueText>
</View>
</SafeBlueArea>
);
}
}
}
// Invoice has not expired, nor has it been paid for.
return (
<SafeBlueArea>
<View
style={{
flex: 1,
alignItems: 'center',
marginTop: 8,
paddingHorizontal: 16,
justifyContent: 'space-between',
}}
onLayout={this.onLayout}
>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<QRFast
value={typeof this.state.invoice === 'object' ? invoice.payment_request : invoice}
fgColor={BlueApp.settings.brandingColor}
bgColor={BlueApp.settings.foregroundColor}
size={this.state.qrCodeHeight}
/>
</View>
<BlueSpacing20 />
{invoice && invoice.amt && <BlueText>Please pay {invoice.amt} sats</BlueText>}
{invoice && invoice.description && <BlueText>For: {invoice.description}</BlueText>}
<TouchableOpacity onPress={this.copyToClipboard}>
<Animated.Text style={styles.address} numberOfLines={0}>
{this.state.addressText}
</Animated.Text>
</TouchableOpacity>
<BlueButton
icon={{
name: 'share-alternative',
type: 'entypo',
color: BlueApp.settings.buttonTextColor,
}}
onPress={async () => {
Share.share({
message: 'lightning:' + invoice.payment_request,
});
}}
title={loc.receive.details.share}
/>
<BlueButton
buttonStyle={{ backgroundColor: 'white' }}
icon={{
name: 'info',
type: 'entypo',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => this.props.navigation.navigate('LNDViewAdditionalInvoiceInformation', { fromWallet: this.state.fromWallet })}
title="Additional Information"
/>
</View>
<BlueSpacing20 />
</SafeBlueArea>
);
}
}
const styles = StyleSheet.create({
address: {
marginVertical: 32,
fontSize: 15,
color: '#9aa0aa',
textAlign: 'center',
},
});
LNDViewInvoice.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.function,
navigate: PropTypes.function,
getParam: PropTypes.function,
dismiss: PropTypes.function,
}),
};

View file

@ -1,138 +1,77 @@
/* global alert */
import React, { Component } from 'react';
import { TouchableOpacity, View } from 'react-native';
import { Dropdown } from 'react-native-material-dropdown';
import { BlueSpacingVariable, BlueLoading, SafeBlueArea, BlueCard, BlueHeaderDefaultSub } from '../../BlueComponents';
import { TouchableOpacity, Linking, View } from 'react-native';
import { BlueSpacingVariable, BlueNavigationStyle, SafeBlueArea, BlueCard } from '../../BlueComponents';
import { ListItem } from 'react-native-elements';
import PropTypes from 'prop-types';
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
let loc = require('../../loc');
let data = [];
export default class ManageFunds extends Component {
static navigationOptions = {
header: ({ navigation }) => {
return <BlueHeaderDefaultSub leftText={loc.lnd.title} onClose={() => navigation.goBack(null)} />;
},
};
static navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
title: loc.lnd.title,
headerLeft: null,
});
constructor(props) {
super(props);
let fromSecret;
if (props.navigation.state.params.fromSecret) fromSecret = props.navigation.state.params.fromSecret;
let fromWallet = false;
this.onWalletSelect = this.onWalletSelect.bind(this);
for (let w of BlueApp.getWallets()) {
if (w.getSecret() === fromSecret) {
fromWallet = w;
break;
}
}
if (fromWallet) {
console.log(fromWallet.type);
}
this.state = {
fromWallet,
fromSecret,
isLoading: true,
};
this.state = { fromWallet: props.navigation.getParam('fromWallet') };
}
async componentDidMount() {
data = [];
for (let c = 0; c < BlueApp.getWallets().length; c++) {
let w = BlueApp.getWallets()[c];
if (w.type !== LightningCustodianWallet.type) {
data.push({
value: c,
label: w.getLabel() + ' (' + w.getBalance() + ' BTC)',
});
async onWalletSelect(wallet) {
this.props.navigation.dismiss();
/** @type {LightningCustodianWallet} */
let toAddress = false;
if (this.state.fromWallet.refill_addressess.length > 0) {
toAddress = this.state.fromWallet.refill_addressess[0];
} else {
try {
await this.state.fromWallet.fetchBtcAddress();
toAddress = this.state.fromWallet.refill_addressess[0];
} catch (Err) {
return alert(Err.message);
}
}
this.setState({
isLoading: false,
});
if (wallet) {
setTimeout(() => {
this.props.navigation.navigate('SendDetails', {
memo: loc.lnd.refill_lnd_balance,
fromSecret: wallet.getSecret(),
address: toAddress,
});
}, 100);
} else {
return alert('Internal error');
}
}
render() {
if (this.state.isLoading) {
return <BlueLoading />;
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
<BlueSpacingVariable />
<BlueCard>
{(() => {
if (this.state.isRefill) {
return (
<View>
<Dropdown
label={loc.lnd.choose_source_wallet}
data={data}
onChangeText={async value => {
/** @type {LightningCustodianWallet} */
let fromWallet = this.state.fromWallet;
let toAddress = false;
if (fromWallet.refill_addressess.length > 0) {
toAddress = fromWallet.refill_addressess[0];
} else {
try {
await fromWallet.fetchBtcAddress();
toAddress = fromWallet.refill_addressess[0];
} catch (Err) {
return alert(Err.message);
}
}
let wallet = BlueApp.getWallets()[value];
if (wallet) {
console.log(wallet.getSecret());
setTimeout(() => {
console.log({ toAddress });
this.props.navigation.navigate('SendDetails', {
memo: loc.lnd.refill_lnd_balance,
fromSecret: wallet.getSecret(),
address: toAddress,
});
}, 750);
} else {
return alert('Internal error');
}
}}
/>
</View>
);
} else {
return (
<View>
<ListItem
titleStyle={{ color: BlueApp.settings.foregroundColor }}
component={TouchableOpacity}
onPress={a => {
this.setState({ isRefill: true });
}}
title={loc.lnd.refill}
/>
<ListItem
titleStyle={{ color: BlueApp.settings.foregroundColor }}
component={TouchableOpacity}
onPress={a => {
alert('Coming soon');
}}
title={loc.lnd.withdraw}
/>
</View>
);
}
})()}
<ListItem
titleStyle={{ color: BlueApp.settings.foregroundColor }}
component={TouchableOpacity}
onPress={a => {
this.props.navigation.navigate('SelectWallet', { onWalletSelect: this.onWalletSelect });
}}
title={loc.lnd.refill}
/>
<ListItem
titleStyle={{ color: BlueApp.settings.foregroundColor }}
component={TouchableOpacity}
onPress={a => {
Linking.openURL('https://zigzag.io');
}}
title={loc.lnd.withdraw}
/>
<View />
</BlueCard>
@ -144,7 +83,9 @@ export default class ManageFunds extends Component {
ManageFunds.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.function,
dismiss: PropTypes.function,
navigate: PropTypes.function,
getParam: PropTypes.function,
state: PropTypes.shape({
params: PropTypes.shape({
fromSecret: PropTypes.string,

View file

@ -3,50 +3,58 @@ import React from 'react';
import { Text, Dimensions, ActivityIndicator, View, TouchableOpacity, TouchableWithoutFeedback, TextInput, Keyboard } from 'react-native';
import { Icon } from 'react-native-elements';
import PropTypes from 'prop-types';
import { BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueHeaderDefaultSub } from '../../BlueComponents';
import { BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueNavigationStyle, BlueBitcoinAmount } from '../../BlueComponents';
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
import { BitcoinUnit } from '../../models/bitcoinUnits';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
let currency = require('../../currency');
let EV = require('../../events');
let loc = require('../../loc');
const { width } = Dimensions.get('window');
export default class ScanLndInvoice extends React.Component {
static navigationOptions = {
header: ({ navigation }) => {
return <BlueHeaderDefaultSub leftText={'Pay invoice'} onClose={() => navigation.goBack(null)} />;
},
};
static navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
title: loc.send.header,
headerLeft: null,
});
state = {
isLoading: false,
isAmountInitiallyEmpty: false,
};
constructor(props) {
super(props);
let fromSecret;
if (props.navigation.state.params.fromSecret) fromSecret = props.navigation.state.params.fromSecret;
let fromWallet = {};
if (!fromSecret) {
const lightningWallets = BlueApp.getWallets().filter(item => item.type === LightningCustodianWallet.type);
if (lightningWallets.length > 0) {
fromSecret = lightningWallets[0].getSecret();
if (!BlueApp.getWallets().some(item => item.type === LightningCustodianWallet.type)) {
alert('Before paying a Lightning invoice, you must first add a Lightning wallet.');
props.navigation.dismiss();
} else {
let fromSecret;
if (props.navigation.state.params.fromSecret) fromSecret = props.navigation.state.params.fromSecret;
let fromWallet = {};
if (!fromSecret) {
const lightningWallets = BlueApp.getWallets().filter(item => item.type === LightningCustodianWallet.type);
if (lightningWallets.length > 0) {
fromSecret = lightningWallets[0].getSecret();
}
}
}
for (let w of BlueApp.getWallets()) {
if (w.getSecret() === fromSecret) {
fromWallet = w;
break;
for (let w of BlueApp.getWallets()) {
if (w.getSecret() === fromSecret) {
fromWallet = w;
break;
}
}
}
this.state = {
fromWallet,
fromSecret,
};
this.state = {
fromWallet,
fromSecret,
destination: '',
};
}
}
async componentDidMount() {
@ -71,7 +79,7 @@ export default class ScanLndInvoice extends React.Component {
}, 6000);
if (!this.state.fromWallet) {
alert('Error: cant find source wallet (this should never happen)');
alert('Before paying a Lightning invoice, you must first add a Lightning wallet.');
return this.props.navigation.goBack();
}
@ -82,7 +90,7 @@ export default class ScanLndInvoice extends React.Component {
* @type {LightningCustodianWallet}
*/
let w = this.state.fromWallet;
let decoded = false;
let decoded;
try {
decoded = await w.decodeInvoice(data);
@ -94,10 +102,11 @@ export default class ScanLndInvoice extends React.Component {
}
Keyboard.dismiss();
this.setState({
isPaying: true,
invoice: data,
decoded,
expiresIn,
destination: data,
isAmountInitiallyEmpty: decoded.num_satoshis === '0',
});
} catch (Err) {
alert(Err.message);
@ -108,62 +117,89 @@ export default class ScanLndInvoice extends React.Component {
if (!this.state.hasOwnProperty('decoded')) {
return null;
}
let decoded = this.state.decoded;
/** @type {LightningCustodianWallet} */
let fromWallet = this.state.fromWallet;
this.setState(
{
isLoading: true,
},
async () => {
let decoded = this.state.decoded;
let expiresIn = (decoded.timestamp * 1 + decoded.expiry * 1) * 1000; // ms
if (+new Date() > expiresIn) {
return alert('Invoice expired');
}
/** @type {LightningCustodianWallet} */
let fromWallet = this.state.fromWallet;
this.setState({
isPayingInProgress: true,
});
let expiresIn = (decoded.timestamp * 1 + decoded.expiry * 1) * 1000; // ms
if (+new Date() > expiresIn) {
this.setState({ isLoading: false });
return alert('Invoice expired');
}
let start = +new Date();
let end;
try {
await fromWallet.payInvoice(this.state.invoice);
end = +new Date();
} catch (Err) {
console.log(Err.message);
this.props.navigation.goBack();
return alert('Error');
}
const currentUserInvoices = await fromWallet.getUserInvoices();
if (currentUserInvoices.some(invoice => invoice.payment_hash === decoded.payment_hash)) {
this.setState({ isLoading: false });
return alert(loc.lnd.sameWalletAsInvoiceError);
}
console.log('payInvoice took', (end - start) / 1000, 'sec');
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs
let start = +new Date();
let end;
try {
await fromWallet.payInvoice(this.state.invoice, this.state.invoice.num_satoshis);
end = +new Date();
} catch (Err) {
console.log(Err.message);
this.setState({ isLoading: false });
this.props.navigation.goBack();
return alert('Error');
}
alert('Success');
this.props.navigation.goBack();
console.log('payInvoice took', (end - start) / 1000, 'sec');
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs
alert('Success');
this.props.navigation.goBack();
},
);
}
processTextForInvoice = text => {
if (text.toLowerCase().startsWith('lnb') || text.toLowerCase().startsWith('lightning:lnb')) {
this.processInvoice(text);
} else {
this.setState({ decoded: undefined, expiresIn: undefined });
this.setState({ decoded: undefined, expiresIn: undefined, destination: text });
}
};
shouldDisablePayButton = () => {
if (typeof this.state.decoded !== 'object') {
return true;
} else {
if (!this.state.decoded.hasOwnProperty('num_satoshis')) {
return true;
}
}
return this.state.decoded.num_satoshis <= 0 || this.state.isLoading || isNaN(this.state.decoded.num_satoshis);
};
render() {
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
<Text style={{ textAlign: 'center', fontSize: 50, fontWeight: '700', color: '#2f5fb3' }}>
{this.state.hasOwnProperty('decoded') &&
this.state.decoded !== undefined &&
currency.satoshiToLocalCurrency(this.state.decoded.num_satoshis)}
</Text>
<Text style={{ textAlign: 'center', fontSize: 25, fontWeight: '600', color: '#d4d4d4' }}>
{this.state.hasOwnProperty('decoded') &&
this.state.decoded !== undefined &&
currency.satoshiToBTC(this.state.decoded.num_satoshis)}
</Text>
<BlueBitcoinAmount
pointerEvents={this.state.isAmountInitiallyEmpty ? 'auto' : 'none'}
isLoading={this.state.isLoading}
amount={typeof this.state.decoded === 'object' ? this.state.decoded.num_satoshis : 0}
onChangeText={text => {
if (typeof this.state.decoded === 'object') {
text = parseInt(text);
let decoded = this.state.decoded;
decoded.num_satoshis = text;
this.setState({ decoded: decoded });
}
}}
disabled={typeof this.state.decoded !== 'object' || this.state.isLoading}
unit={BitcoinUnit.SATS}
/>
<BlueSpacing20 />
<BlueCard>
<View
style={{
@ -182,10 +218,13 @@ export default class ScanLndInvoice extends React.Component {
}}
>
<TextInput
onChangeText={this.processTextForInvoice}
onChangeText={text => {
this.setState({ destination: text });
this.processTextForInvoice(text);
}}
placeholder={loc.wallets.details.destination}
numberOfLines={1}
value={this.state.hasOwnProperty('decoded') && this.state.decoded !== undefined ? this.state.decoded.destination : ''}
value={this.state.destination}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33, height: 33 }}
editable={!this.state.isLoading}
/>
@ -212,61 +251,40 @@ export default class ScanLndInvoice extends React.Component {
<View
style={{
flexDirection: 'row',
borderColor: '#d2d2d2',
borderBottomColor: '#d2d2d2',
borderWidth: 1.0,
borderBottomWidth: 0.5,
backgroundColor: '#f5f5f5',
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
marginVertical: 8,
borderRadius: 4,
}}
>
<TextInput
onChangeText={text => {}}
placeholder={loc.wallets.details.description}
numberOfLines={1}
value={this.state.hasOwnProperty('decoded') && this.state.decoded !== undefined ? this.state.decoded.description : ''}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33, height: 33 }}
editable={!this.state.isLoading}
/>
<Text numberOfLines={0} style={{ color: '#81868e', fontWeight: '500', fontSize: 14 }}>
{this.state.hasOwnProperty('decoded') && this.state.decoded !== undefined ? this.state.decoded.description : ''}
</Text>
</View>
{this.state.expiresIn !== undefined && (
<Text style={{ color: '#81868e', fontSize: 12, left: 20, top: 10 }}>Expires in: {this.state.expiresIn}</Text>
)}
</BlueCard>
<BlueSpacing20 />
{this.state.hasOwnProperty('decoded') &&
this.state.decoded !== undefined &&
(() => {
if (this.state.isPayingInProgress) {
return (
<View>
<ActivityIndicator />
</View>
);
} else {
return (
<BlueButton
icon={{
name: 'bolt',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
title={'Pay'}
buttonStyle={{ width: 150, left: (width - 150) / 2 - 20 }}
onPress={() => {
this.pay();
}}
/>
);
}
})()}
{this.state.isLoading ? (
<View>
<ActivityIndicator />
</View>
) : (
<BlueButton
icon={{
name: 'bolt',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
title={'Pay'}
buttonStyle={{ width: 150, left: (width - 150) / 2 - 20 }}
onPress={() => {
this.pay();
}}
disabled={this.shouldDisablePayButton()}
/>
)}
</SafeBlueArea>
</TouchableWithoutFeedback>
);
@ -278,6 +296,7 @@ ScanLndInvoice.propTypes = {
goBack: PropTypes.function,
navigate: PropTypes.function,
getParam: PropTypes.function,
dismiss: PropTypes.function,
state: PropTypes.shape({
params: PropTypes.shape({
uri: PropTypes.string,

View file

@ -1,8 +1,7 @@
/* global alert */
import React, { Component } from 'react';
import { ScrollView } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { BlueLoading, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueHeader, BlueSpacing20 } from '../BlueComponents';
import { BlueLoading, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle, BlueSpacing20 } from '../BlueComponents';
import PropTypes from 'prop-types';
/** @type {AppStorage} */
let BlueApp = require('../BlueApp');
@ -12,10 +11,8 @@ let loc = require('../loc');
export default class PlausibleDeniability extends Component {
static navigationOptions = {
tabBarLabel: loc.plausibledeniability.title,
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons name={focused ? 'ios-settings' : 'ios-settings-outline'} size={26} style={{ color: tintColor }} />
),
...BlueNavigationStyle(),
title: loc.plausibledeniability.title,
};
constructor(props) {
@ -38,14 +35,6 @@ export default class PlausibleDeniability extends Component {
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
<BlueHeader
backgroundColor={BlueApp.settings.brandingColor}
centerComponent={{
text: loc.plausibledeniability.title,
style: { color: BlueApp.settings.foregroundColor, fontSize: 23 },
}}
/>
<BlueCard>
<ScrollView maxHeight={450}>
<BlueText>{loc.plausibledeniability.help}</BlueText>
@ -85,20 +74,6 @@ export default class PlausibleDeniability extends Component {
this.props.navigation.navigate('Wallets');
}}
/>
<BlueSpacing20 />
<BlueButton
icon={{
name: 'arrow-left',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
title={loc.plausibledeniability.go_back}
onPress={() => {
this.props.navigation.goBack();
}}
/>
</ScrollView>
</BlueCard>
</SafeBlueArea>

View file

@ -146,9 +146,15 @@ export default class SendDetails extends Component {
let memo = '';
parsedBitcoinUri = bip21.decode(this.props.navigation.state.params.uri);
address = parsedBitcoinUri.address || address;
amount = parsedBitcoinUri.options.amount.toString() || amount;
memo = parsedBitcoinUri.options.label || memo;
address = parsedBitcoinUri.hasOwnProperty('address') ? parsedBitcoinUri.address : address;
if (parsedBitcoinUri.hasOwnProperty('options')) {
if (parsedBitcoinUri.options.hasOwnProperty('amount')) {
amount = parsedBitcoinUri.options.amount.toString();
}
if (parsedBitcoinUri.options.hasOwnProperty('label')) {
memo = parsedBitcoinUri.options.label || memo;
}
}
this.setState({ address, amount, memo });
} catch (error) {
console.log(error);
@ -223,7 +229,7 @@ export default class SendDetails extends Component {
})
.catch(error => {
alert(error.errorMessage);
this.setState({ address: text.replace(' ', ''), isLoading: false, bip70TransactionExpiration: null, amount: 0 });
this.setState({ isLoading: false, bip70TransactionExpiration: null });
});
},
);

View file

@ -50,64 +50,65 @@ export default class About extends Component {
<BlueTextCentered h4>Always backup your keys</BlueTextCentered>
<BlueSpacing20 />
</BlueCard>
<BlueButton
icon={{
name: 'mark-github',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => {
Linking.openURL('https://github.com/BlueWallet/BlueWallet');
}}
title="github.com/BlueWallet/BlueWallet"
/>
<BlueSpacing20 />
<BlueButton
icon={{
name: 'mark-github',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => {
Linking.openURL('https://github.com/BlueWallet/BlueWallet');
}}
title="github.com/BlueWallet/BlueWallet"
/>
<BlueSpacing20 />
<BlueButton
icon={{
name: 'twitter',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => {
Linking.openURL('https://twitter.com/bluewalletio');
}}
title="Follow us on Twitter"
/>
<BlueSpacing20 />
<BlueButton
icon={{
name: 'twitter',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => {
Linking.openURL('https://twitter.com/bluewalletio');
}}
title="Follow us on Twitter"
/>
<BlueSpacing20 />
<BlueButton
icon={{
name: 'telegram',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => {
Linking.openURL('https://t.me/bluewallet');
}}
title="Join Telegram chat"
/>
<BlueSpacing20 />
<BlueButton
icon={{
name: 'telegram',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => {
Linking.openURL('https://t.me/bluewallet');
}}
title="Join Telegram chat"
/>
<BlueSpacing20 />
<BlueButton
icon={{
name: 'thumbsup',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => {
if (Platform.OS === 'ios') {
Linking.openURL('https://itunes.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040?l=ru&ls=1&mt=8');
} else {
Linking.openURL('https://play.google.com/store/apps/details?id=io.bluewallet.bluewallet');
}
}}
title="Leave us a review on Appstore"
/>
<BlueSpacing20 />
<BlueButton
icon={{
name: 'thumbsup',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => {
if (Platform.OS === 'ios') {
Linking.openURL('https://itunes.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040?l=ru&ls=1&mt=8');
} else {
Linking.openURL('https://play.google.com/store/apps/details?id=io.bluewallet.bluewallet');
}
}}
title="Leave us a review on Appstore"
/>
<BlueSpacing20 />
<BlueCard>
<BlueText h3>Built with awesome:</BlueText>
<BlueSpacing20 />
<BlueText h4>* React Native</BlueText>

View file

@ -1,11 +1,9 @@
import React, { Component } from 'react';
import { FlatList, TouchableOpacity, AsyncStorage, ActivityIndicator, View } from 'react-native';
import { SafeBlueArea, BlueNavigationStyle, BlueListItem } from '../../BlueComponents';
import { FlatList, TouchableOpacity, ActivityIndicator, View } from 'react-native';
import { SafeBlueArea, BlueNavigationStyle, BlueListItem, BlueText, BlueCard } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { Icon } from 'react-native-elements';
import { AppStorage } from '../../class';
import { FiatUnit } from '../../models/fiatUnit';
/** @type {AppStorage} */
let loc = require('../../loc');
let currency = require('../../currency');
@ -22,11 +20,11 @@ export default class Currency extends Component {
async componentDidMount() {
try {
const preferredCurrency = await AsyncStorage.getItem(AppStorage.PREFERREDCURRENCY);
const preferredCurrency = await currency.getPreferredCurrency();
if (preferredCurrency === null) {
throw Error();
}
this.setState({ selectedCurrency: JSON.parse(preferredCurrency) });
this.setState({ selectedCurrency: preferredCurrency });
} catch (_error) {
this.setState({ selectedCurrency: FiatUnit.USD });
}
@ -37,15 +35,15 @@ export default class Currency extends Component {
<TouchableOpacity
onPress={() => {
this.setState({ isSavingNewPreferredCurrency: true, selectedCurrency: item }, async () => {
await AsyncStorage.setItem(AppStorage.PREFERREDCURRENCY, JSON.stringify(item));
await currency.startUpdater(true);
await currency.setPrefferedCurrency(item);
await currency.startUpdater();
this.setState({ isSavingNewPreferredCurrency: false });
});
}}
>
<BlueListItem
title={item.symbol + ' ' + item.formatterValue}
{...(this.state.selectedCurrency.formatterValue === item.formatterValue
title={item.symbol + ' ' + item.endPointKey}
{...(this.state.selectedCurrency.endPointKey === item.endPointKey
? {
rightIcon: this.state.selectedNewCurrency ? (
<ActivityIndicator />
@ -70,6 +68,9 @@ export default class Currency extends Component {
extraData={this.state.data}
renderItem={this.renderItem}
/>
<BlueCard>
<BlueText>Prices are obtained from CoinDesk</BlueText>
</BlueCard>
</SafeBlueArea>
);
}

View file

@ -1,9 +1,8 @@
import React, { Component } from 'react';
import { Picker } from 'react-native';
import { BlueLoading, SafeBlueArea, BlueCard, BlueNavigationStyle } from '../../BlueComponents';
import { FlatList, TouchableOpacity } from 'react-native';
import { BlueLoading, BlueText, SafeBlueArea, BlueListItem, BlueCard, BlueNavigationStyle } from '../../BlueComponents';
import PropTypes from 'prop-types';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
import { Icon } from 'react-native-elements';
let loc = require('../../loc');
export default class Language extends Component {
@ -17,16 +16,50 @@ export default class Language extends Component {
this.state = {
isLoading: true,
language: loc.getLanguage(),
availableLanguages: [
{ label: 'English', value: 'en' },
{ label: 'Русский', value: 'ru' },
{ label: 'Українська', value: 'ua' },
{ label: 'Spanish', value: 'es' },
{ label: 'Portuguese (BR)', value: 'pt_br' },
{ label: 'Portuguese (PT)', value: 'pt_pt' },
{ label: 'Deutsch (DE)', value: 'de_de' },
{ label: 'Česky (CZ)', value: 'cs_cz' },
{ label: 'Thai (TH)', value: 'th_th' },
{ label: 'Dutch (NL)', value: 'nl_nl' },
{ label: 'Français (FR)', value: 'fr_fr' },
],
};
}
async componentDidMount() {
this.setState({
isLoading: false,
storageIsEncrypted: await BlueApp.storageIsEncrypted(),
});
}
renderItem = ({ item }) => {
return (
<TouchableOpacity
onPress={() => {
console.log('setLanguage', item.value);
loc.setLanguage(item.value);
loc.saveLanguage(item.value);
return this.setState({ language: item.value });
}}
>
<BlueListItem
title={item.label}
{...(this.state.language === item.value
? {
rightIcon: <Icon name="check" type="font-awesome" color="#0c2550" />,
}
: { hideChevron: true })}
/>
</TouchableOpacity>
);
};
render() {
if (this.state.isLoading) {
return <BlueLoading />;
@ -34,24 +67,15 @@ export default class Language extends Component {
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
<FlatList
style={{ flex: 1 }}
keyExtractor={(_item, index) => `${index}`}
data={this.state.availableLanguages}
extraData={this.state.availableLanguages}
renderItem={this.renderItem}
/>
<BlueCard>
<Picker
selectedValue={this.state.language}
onValueChange={(itemValue, itemIndex) => {
console.log('setLanguage', itemValue);
loc.setLanguage(itemValue);
loc.saveLanguage(itemValue);
return this.setState({ language: itemValue });
}}
>
<Picker.Item color={BlueApp.settings.foregroundColor} label="English" value="en" />
<Picker.Item color={BlueApp.settings.foregroundColor} label="Русский" value="ru" />
<Picker.Item color={BlueApp.settings.foregroundColor} label="Українська" value="ua" />
<Picker.Item color={BlueApp.settings.foregroundColor} label="Spanish" value="es" />
<Picker.Item color={BlueApp.settings.foregroundColor} label="Portuguese (BR)" value="pt_br" />
<Picker.Item color={BlueApp.settings.foregroundColor} label="Portuguese (PT)" value="pt_pt" />
<Picker.Item color={BlueApp.settings.foregroundColor} label="Deutsch (DE)" value="de_DE" />
</Picker>
<BlueText>When selecting a new language, restarting Blue Wallet may be required for the change to take effect.</BlueText>
</BlueCard>
</SafeBlueArea>
);

View file

@ -1,16 +1,7 @@
import React, { Component } from 'react';
import { AsyncStorage, View, TextInput, Linking } from 'react-native';
import { AppStorage } from '../../class';
import {
BlueLoading,
BlueSpacing20,
BlueButton,
SafeBlueArea,
BlueCard,
BlueNavigationStyle,
BlueSpacing40,
BlueText,
} from '../../BlueComponents';
import { BlueLoading, BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueNavigationStyle, BlueText } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
/** @type {AppStorage} */
@ -65,25 +56,25 @@ export default class LightningSettings extends Component {
To connect to your own LND node please install LndHub and put its URL here in settings. Leave blank to use default LndHub
(lndhub.io)
</BlueText>
</BlueCard>
<BlueButton
icon={{
name: 'mark-github',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
backgroundColor: '#FFFFFF',
}}
onPress={() => {
Linking.openURL('https://github.com/BlueWallet/LndHub');
}}
title="github.com/BlueWallet/LndHub"
buttonStyle={{
backgroundColor: '#FFFFFF',
}}
/>
<BlueSpacing40 />
<BlueButton
icon={{
name: 'mark-github',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
backgroundColor: '#FFFFFF',
}}
onPress={() => {
Linking.openURL('https://github.com/BlueWallet/LndHub');
}}
title="github.com/BlueWallet/LndHub"
buttonStyle={{
backgroundColor: '#FFFFFF',
}}
/>
<BlueCard>
<View
style={{
flexDirection: 'row',

View file

@ -11,6 +11,7 @@ import {
BlueNavigationStyle,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
import { SegwitBech32Wallet } from '../../class';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
@ -39,13 +40,20 @@ export default class RBF extends Component {
}
let destinationAddress;
for (let o of sourceTx.outputs) {
if (o.addresses[0] === sourceWallet.getAddress()) {
if (!o.addresses && o.script) {
// probably bech32 output, so we need to decode address
o.addresses = [SegwitBech32Wallet.scriptPubKeyToAddress(o.script)];
}
if (o.addresses && o.addresses[0] === sourceWallet.getAddress()) {
// change
// nop
} else {
// DESTINATION address
destinationAddress = o.addresses[0];
destinationAddress = (o.addresses && o.addresses[0]) || '';
console.log('dest = ', destinationAddress);
}
}

View file

@ -16,6 +16,7 @@ import PropTypes from 'prop-types';
let BlueApp = require('../../BlueApp');
let loc = require('../../loc');
const dayjs = require('dayjs');
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}

View file

@ -180,65 +180,72 @@ export default class WalletsAdd extends Component {
alignItems: 'center',
}}
>
<BlueButton
title={loc.wallets.add.create}
buttonStyle={{
width: width / 1.5,
}}
onPress={() => {
this.props.navigation.goBack();
setTimeout(async () => {
let w;
{!this.state.isLoading ? (
<BlueButton
title={loc.wallets.add.create}
buttonStyle={{
width: width / 1.5,
}}
onPress={() => {
this.setState(
{ isLoading: true },
async () => {
let w;
if (this.state.activeLightning) {
// lightning was selected
if (this.state.activeLightning) {
// lightning was selected
// eslint-disable-next-line
for (let t of BlueApp.getWallets()) {
if (t.type === LightningCustodianWallet.type) {
// already exist
this.setState({ isLoading: false });
return alert('Only 1 Lightning wallet allowed for now');
}
}
global.lightning_create_try = global.lightning_create_try || 1;
if (global.lightning_create_try++ < 9 && +new Date() < 1545264000000) return alert('Coming soon');
// eslint-disable-next-line
for (let t of BlueApp.getWallets()) {
if (t.type === LightningCustodianWallet.type) {
// already exist
return alert('Only 1 Ligthning wallet allowed for now');
w = new LightningCustodianWallet();
w.setLabel(this.state.label || w.typeReadable);
try {
let lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB);
if (lndhub) {
w.setBaseURI(lndhub);
w.init();
}
await w.createAccount();
await w.authorize();
} catch (Err) {
this.setState({ isLoading: false });
console.warn('lnd create failure', Err);
// giving app, not adding anything
}
A(A.ENUM.CREATED_LIGHTNING_WALLET);
} else if (this.state.selectedIndex === 1) {
// btc was selected
// index 1 radio - segwit single address
w = new SegwitP2SHWallet();
w.setLabel(this.state.label || loc.wallets.add.label_new_segwit);
} else {
// zero index radio - HD segwit
w = new HDSegwitP2SHWallet();
w.setLabel((this.state.label || loc.wallets.add.label_new_segwit) + ' HD');
}
}
w = new LightningCustodianWallet();
w.setLabel(this.state.label || w.typeReadable);
try {
let lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB);
if (lndhub) {
w.setBaseURI(lndhub);
w.init();
}
await w.createAccount();
await w.authorize();
} catch (Err) {
console.warn('lnd create failure', Err);
// giving app, not adding anything
}
A(A.ENUM.CREATED_LIGHTNING_WALLET);
} else if (this.state.selectedIndex === 1) {
// btc was selected
// index 1 radio - segwit single address
w = new SegwitP2SHWallet();
w.setLabel(this.state.label || loc.wallets.add.label_new_segwit);
} else {
// zero index radio - HD segwit
w = new HDSegwitP2SHWallet();
w.setLabel((this.state.label || loc.wallets.add.label_new_segwit) + ' HD');
}
await w.generate();
BlueApp.wallets.push(w);
await BlueApp.saveToDisk();
EV(EV.enum.WALLETS_COUNT_CHANGED);
A(A.ENUM.CREATED_WALLET);
ReactNativeHapticFeedback.trigger('notificationSuccess', false);
}, 1);
}}
/>
await w.generate();
BlueApp.wallets.push(w);
await BlueApp.saveToDisk();
EV(EV.enum.WALLETS_COUNT_CHANGED);
A(A.ENUM.CREATED_WALLET);
ReactNativeHapticFeedback.trigger('notificationSuccess', false);
this.props.navigation.dismiss();
},
1,
);
}}
/>
) : (
<ActivityIndicator />
)}
<BlueButtonLink
title={loc.wallets.add.import_wallet}
@ -258,5 +265,6 @@ WalletsAdd.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
goBack: PropTypes.func,
dismiss: PropTypes.func,
}),
};

View file

@ -90,9 +90,7 @@ export default class BuyBitcoin extends Component {
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => {
Linking.openURL(
'https://payments.changelly.com/?crypto=USD&fiat=USD&ref_id=rtagfcvnwiwvhm99&address=' + this.state.address,
);
Linking.openURL('https://old.changelly.com/?ref_id=rtagfcvnwiwvhm99');
}}
title="Buy from Changelly"
/>

View file

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { Dimensions, Platform, ActivityIndicator, View } from 'react-native';
import { QRCode as QRSlow } from 'react-native-custom-qr-codes';
import { BlueSpacing40, SafeBlueArea, BlueCard, BlueText, BlueHeaderDefaultSub } from '../../BlueComponents';
import { BlueSpacing40, SafeBlueArea, BlueNavigationStyle, BlueCard, BlueText } from '../../BlueComponents';
import PropTypes from 'prop-types';
const QRFast = require('react-native-qrcode');
/** @type {AppStorage} */
@ -17,11 +17,11 @@ if (aspectRatio > 1.6) {
}
export default class WalletExport extends Component {
static navigationOptions = {
header: ({ navigation }) => {
return <BlueHeaderDefaultSub leftText={loc.wallets.export.title} onClose={() => navigation.goBack(null)} />;
},
};
static navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
title: loc.wallets.export.title,
headerLeft: null,
});
constructor(props) {
super(props);
@ -29,7 +29,6 @@ export default class WalletExport extends Component {
let address = props.navigation.state.params.address;
let secret = props.navigation.state.params.secret;
let wallet;
for (let w of BlueApp.getWallets()) {
if ((address && w.getAddress() === address) || w.getSecret() === secret) {
// found our wallet

View file

@ -17,7 +17,7 @@ import {
BlueButton,
SafeBlueArea,
BlueSpacing20,
BlueHeaderDefaultSub,
BlueNavigationStyle,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
@ -30,11 +30,10 @@ let loc = require('../../loc');
const { width } = Dimensions.get('window');
export default class WalletsImport extends Component {
static navigationOptions = {
header: ({ navigation }) => {
return <BlueHeaderDefaultSub leftText={loc.wallets.import.title} onClose={() => navigation.goBack(null)} />;
},
};
static navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(),
title: loc.wallets.import.title,
});
constructor(props) {
super(props);
@ -43,7 +42,7 @@ export default class WalletsImport extends Component {
};
}
async componentDidMount() {
componentDidMount() {
this.setState({
isLoading: false,
label: '',
@ -51,14 +50,18 @@ export default class WalletsImport extends Component {
}
async _saveWallet(w) {
alert(loc.wallets.import.success);
ReactNativeHapticFeedback.trigger('notificationSuccess', false);
w.setLabel(loc.wallets.import.imported + ' ' + w.typeReadable);
BlueApp.wallets.push(w);
await BlueApp.saveToDisk();
EV(EV.enum.WALLETS_COUNT_CHANGED);
A(A.ENUM.CREATED_WALLET);
this.props.navigation.dismiss();
if (BlueApp.getWallets().some(wallet => wallet.getSecret() === w.secret)) {
alert('This wallet has been previously imported.');
} else {
alert(loc.wallets.import.success);
ReactNativeHapticFeedback.trigger('notificationSuccess', false);
w.setLabel(loc.wallets.import.imported + ' ' + w.typeReadable);
BlueApp.wallets.push(w);
await BlueApp.saveToDisk();
EV(EV.enum.WALLETS_COUNT_CHANGED);
A(A.ENUM.CREATED_WALLET);
this.props.navigation.dismiss();
}
}
async importMnemonic(text) {
@ -227,6 +230,7 @@ export default class WalletsImport extends Component {
}}
>
<BlueButton
disabled={!this.state.label}
title={loc.wallets.import.do_import}
buttonStyle={{
width: width / 1.5,
@ -259,7 +263,7 @@ export default class WalletsImport extends Component {
WalletsImport.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
dismiss: PropTypes.func,
goBack: PropTypes.func,
dismiss: PropTypes.func,
}),
};

View file

@ -9,15 +9,18 @@ import {
BlueTransactionOutgoingIcon,
BlueTransactionPendingIcon,
BlueTransactionOffchainIcon,
BlueTransactionExpiredIcon,
BlueList,
BlueListItem,
BlueHeaderDefaultMain,
BlueTransactionOffchainIncomingIcon,
} from '../../BlueComponents';
import { Icon } from 'react-native-elements';
import { NavigationEvents } from 'react-navigation';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import PropTypes from 'prop-types';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { LightningCustodianWallet } from '../../class';
let EV = require('../../events');
let A = require('../../analytics');
/** @type {AppStorage} */
@ -46,6 +49,7 @@ export default class WalletsList extends Component {
this.state = {
isLoading: true,
wallets: BlueApp.getWallets().concat(false),
lastSnappedTo: 0,
};
EV(EV.enum.WALLETS_COUNT_CHANGED, this.refreshFunction.bind(this));
@ -59,7 +63,8 @@ export default class WalletsList extends Component {
}
/**
* Forcefully fetches TXs and balance for lastSnappedTo (i.e. current) wallet
* Forcefully fetches TXs and balance for lastSnappedTo (i.e. current) wallet.
* Triggered manually by user on pull-to-refresh.
*/
refreshTransactions() {
if (!(this.lastSnappedTo < BlueApp.getWallets().length)) {
@ -135,6 +140,7 @@ export default class WalletsList extends Component {
onSnapToItem(index) {
console.log('onSnapToItem', index);
this.lastSnappedTo = index;
this.setState({ lastSnappedTo: index });
if (index < BlueApp.getWallets().length) {
// not the last
@ -177,6 +183,9 @@ export default class WalletsList extends Component {
if (wallets[index].fetchPendingTransactions) {
await wallets[index].fetchPendingTransactions();
}
if (wallets[index].fetchUserInvoices) {
await wallets[index].fetchUserInvoices();
}
this.refreshFunction();
didRefresh = true;
} else {
@ -221,13 +230,63 @@ export default class WalletsList extends Component {
}
};
rowTitle = item => {
if (item.type === 'user_invoice' || item.type === 'payment_request') {
if (isNaN(item.value)) {
item.value = "0"
}
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = item.timestamp + item.expire_time;
if (invoiceExpiration > now) {
return loc.formatBalanceWithoutSuffix(item.value && item.value, BitcoinUnit.BTC).toString();
} else if (invoiceExpiration < now) {
if (item.ispaid) {
return loc.formatBalanceWithoutSuffix(item.value && item.value, BitcoinUnit.BTC).toString();
} else {
return loc.lnd.expired;
}
}
} else {
return loc.formatBalanceWithoutSuffix(item.value && item.value, BitcoinUnit.BTC).toString();
}
};
rowTitleStyle = item => {
let color = '#37c0a1';
if (item.type === 'user_invoice' || item.type === 'payment_request') {
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = item.timestamp + item.expire_time;
if (invoiceExpiration > now) {
color = '#37c0a1';
} else if (invoiceExpiration < now) {
if (item.ispaid) {
color = '#37c0a1';
} else {
color = '#FF0000';
}
}
} else if (item.value / 100000000 < 0) {
color = BlueApp.settings.foregroundColor;
}
return {
fontWeight: '600',
fontSize: 16,
color: color,
};
};
render() {
const { navigate } = this.props.navigation;
if (this.state.isLoading) {
return <BlueLoading />;
}
return (
<SafeBlueArea style={{ flex: 1, backgroundColor: '#FFFFFF' }}>
<NavigationEvents
@ -306,6 +365,27 @@ export default class WalletsList extends Component {
);
}
if (rowData.item.type === 'user_invoice' || rowData.item.type === 'payment_request') {
if (!rowData.item.ispaid) {
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = rowData.item.timestamp + rowData.item.expire_time;
if (invoiceExpiration < now) {
return (
<View style={{ width: 25 }}>
<BlueTransactionExpiredIcon />
</View>
);
}
} else {
return (
<View style={{ width: 25 }}>
<BlueTransactionOffchainIncomingIcon />
</View>
);
}
}
if (!rowData.item.confirmations) {
return (
<View style={{ width: 25 }}>
@ -337,6 +417,20 @@ export default class WalletsList extends Component {
navigate('TransactionDetails', {
hash: rowData.item.hash,
});
} else if (
rowData.item.type === 'user_invoice' ||
rowData.item.type === 'payment_request' ||
rowData.item.type === 'paid_invoice'
) {
const lightningWallet = this.state.wallets.filter(wallet => wallet.type === LightningCustodianWallet.type);
if (typeof lightningWallet === 'object') {
if (lightningWallet.length === 1) {
this.props.navigation.navigate('LNDViewInvoice', {
invoice: rowData.item,
fromWallet: lightningWallet[0],
});
}
}
}
}}
badge={{
@ -345,15 +439,8 @@ export default class WalletsList extends Component {
containerStyle: { marginTop: 0 },
}}
hideChevron
rightTitle={loc.formatBalanceWithoutSuffix(rowData.item.value && rowData.item.value, BitcoinUnit.BTC)}
rightTitleStyle={{
fontWeight: '600',
fontSize: 16,
color:
rowData.item.value / 100000000 < 0 || rowData.item.type === 'paid_invoice'
? BlueApp.settings.foregroundColor
: '#37c0a1',
}}
rightTitle={this.rowTitle(rowData.item)}
rightTitleStyle={this.rowTitleStyle(rowData.item)}
/>
);
}}

View file

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { View, ActivityIndicator, Image, Text, TouchableOpacity, FlatList } from 'react-native';
import { SafeBlueArea, BlueNavigationStyle } from '../../BlueComponents';
import { SafeBlueArea, BlueNavigationStyle, BlueText, BlueSpacing20 } from '../../BlueComponents';
import LinearGradient from 'react-native-linear-gradient';
import PropTypes from 'prop-types';
import { WatchOnlyWallet, LegacyWallet } from '../../class';
@ -157,12 +157,24 @@ export default class SelectWallet extends Component {
};
render() {
if (this.state.isLoading || this.state.data.length <= 0) {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
<View style={{ flex: 1, justifyContent: 'center', alignContent: 'center', paddingTop: 20 }}>
<ActivityIndicator />
</View>
);
} else if (this.state.data.length <= 0) {
return (
<SafeBlueArea style={{ flex: 1 }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', paddingTop: 20 }}>
<BlueText style={{ textAlign: 'center' }}>There are currently no Bitcoin wallets available.</BlueText>
<BlueSpacing20 />
<BlueText style={{ textAlign: 'center' }}>
A Bitcoin wallet is required to refill Lightning wallets. Please, create or import one.
</BlueText>
</View>
</SafeBlueArea>
);
}
return (

View file

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { Text, View, Image, FlatList, RefreshControl, TouchableOpacity } from 'react-native';
import { Text, View, Image, FlatList, RefreshControl, TouchableOpacity, StatusBar } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import PropTypes from 'prop-types';
import { NavigationEvents } from 'react-navigation';
@ -7,6 +7,7 @@ import {
BlueText,
BlueTransactionOnchainIcon,
ManageFundsBigButton,
BlueTransactionExpiredIcon,
BlueTransactionIncommingIcon,
BlueTransactionOutgoingIcon,
BlueTransactionPendingIcon,
@ -14,13 +15,13 @@ import {
BlueSendButtonIcon,
BlueReceiveButtonIcon,
BlueListItem,
BlueTransactionOffchainIncomingIcon,
} from '../../BlueComponents';
import { Icon } from 'react-native-elements';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { LightningCustodianWallet } from '../../class';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
let loc = require('../../loc');
let EV = require('../../events');
@ -155,6 +156,9 @@ export default class WalletTransactions extends Component {
if (wallet.fetchPendingTransactions) {
await wallet.fetchPendingTransactions();
}
if (wallet.fetchUserInvoices) {
await wallet.fetchUserInvoices();
}
let end = +new Date();
console.log(wallet.getLabel(), 'fetch tx took', (end - start) / 1000, 'sec');
} catch (err) {
@ -289,15 +293,72 @@ export default class WalletTransactions extends Component {
};
async onWillBlur() {
StatusBar.setBarStyle('dark-content');
await BlueApp.saveToDisk();
}
componentWillUnmount() {
this.onWillBlur();
}
rowTitle = item => {
if (item.type === 'user_invoice' || item.type === 'payment_request') {
if (isNaN(item.value)) {
item.value = 0
}
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = item.timestamp + item.expire_time;
if (invoiceExpiration > now) {
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.state.wallet.getPreferredBalanceUnit()).toString();
} else if (invoiceExpiration < now) {
if (item.ispaid) {
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.state.wallet.getPreferredBalanceUnit()).toString();
} else {
return loc.lnd.expired;
}
}
} else {
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.state.wallet.getPreferredBalanceUnit()).toString();
}
};
rowTitleStyle = item => {
let color = '#37c0a1';
if (item.type === 'user_invoice' || item.type === 'payment_request') {
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = item.timestamp + item.expire_time;
if (invoiceExpiration > now) {
color = '#37c0a1';
} else if (invoiceExpiration < now) {
if (item.ispaid) {
color = '#37c0a1';
} else {
color = '#FF0000';
}
}
} else if (item.value / 100000000 < 0) {
color = BlueApp.settings.foregroundColor;
}
return {
fontWeight: '600',
fontSize: 16,
color: color,
};
};
render() {
const { navigate } = this.props.navigation;
return (
<View style={{ flex: 1 }}>
<NavigationEvents
onWillFocus={() => {
StatusBar.setBarStyle('light-content');
this.refreshFunction();
}}
onWillBlur={() => this.onWillBlur()}
@ -311,7 +372,7 @@ export default class WalletTransactions extends Component {
style={{ alignSelf: 'flex-end', right: 10, flexDirection: 'row' }}
onPress={() => {
console.log('navigating to', this.state.wallet.getLabel());
navigate('ManageFunds', { fromSecret: this.state.wallet.getSecret() });
navigate('ManageFunds', { fromWallet: this.state.wallet });
}}
>
<BlueText style={{ fontWeight: '600', fontSize: 16 }}>{loc.lnd.title}</BlueText>
@ -378,7 +439,6 @@ export default class WalletTransactions extends Component {
}
refreshControl={<RefreshControl onRefresh={() => this.refreshTransactions()} refreshing={this.state.isTransactionsLoading} />}
data={this.state.dataSource}
extraData={this.state.dataSource}
keyExtractor={this._keyExtractor}
renderItem={rowData => {
return (
@ -409,6 +469,27 @@ export default class WalletTransactions extends Component {
);
}
if (rowData.item.type === 'user_invoice' || rowData.item.type === 'payment_request') {
if (!rowData.item.ispaid) {
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = rowData.item.timestamp + rowData.item.expire_time;
if (invoiceExpiration < now) {
return (
<View style={{ width: 25 }}>
<BlueTransactionExpiredIcon />
</View>
);
}
} else {
return (
<View style={{ width: 25 }}>
<BlueTransactionOffchainIncomingIcon />
</View>
);
}
}
if (!rowData.item.confirmations) {
return (
<View style={{ width: 25 }}>
@ -440,6 +521,15 @@ export default class WalletTransactions extends Component {
navigate('TransactionDetails', {
hash: rowData.item.hash,
});
} else if (
rowData.item.type === 'user_invoice' ||
rowData.item.type === 'payment_request' ||
rowData.item.type === 'paid_invoice'
) {
this.props.navigation.navigate('LNDViewExistingInvoice', {
invoice: rowData.item,
fromWallet: this.state.wallet,
});
}
}}
badge={{
@ -448,20 +538,8 @@ export default class WalletTransactions extends Component {
containerStyle: { marginTop: 0 },
}}
hideChevron
rightTitle={loc
.formatBalanceWithoutSuffix(
(rowData.item.value && rowData.item.value) || 0,
this.state.wallet.getPreferredBalanceUnit(),
)
.toString()}
rightTitleStyle={{
fontWeight: '600',
fontSize: 16,
color:
rowData.item.value / 100000000 < 0 || rowData.item.type === 'paid_invoice'
? BlueApp.settings.foregroundColor
: '#37c0a1',
}}
rightTitle={this.rowTitle(rowData.item)}
rightTitleStyle={this.rowTitleStyle(rowData.item)}
/>
);
}}
@ -474,7 +552,9 @@ export default class WalletTransactions extends Component {
backgroundColor: 'transparent',
position: 'absolute',
bottom: 30,
borderRadius: 15,
borderRadius: 30,
minHeight: 48,
flex: 0.84,
overflow: 'hidden',
}}
>
@ -483,9 +563,10 @@ export default class WalletTransactions extends Component {
return (
<BlueReceiveButtonIcon
onPress={() => {
navigate('ReceiveDetails', { address: this.state.wallet.getAddress(), secret: this.state.wallet.getSecret() });
if (this.state.wallet.getAddress()) {
// EV(EV.enum.RECEIVE_ADDRESS_CHANGED, w.getAddress());
if (this.state.wallet.type === new LightningCustodianWallet().type) {
navigate('LNDCreateInvoice', { fromWallet: this.state.wallet });
} else {
navigate('ReceiveDetails', { address: this.state.wallet.getAddress(), secret: this.state.wallet.getSecret() });
}
}}
/>
@ -515,7 +596,7 @@ export default class WalletTransactions extends Component {
<ManageFundsBigButton
onPress={() => {
console.log('navigating to', this.state.wallet.getLabel());
navigate('ManageFunds', { fromSecret: this.state.wallet.getSecret() });
navigate('ManageFunds', { fromWallet: this.state.wallet });
}}
/>
);

View file

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { Dimensions, Platform, ActivityIndicator, View, Clipboard, Animated, TouchableOpacity } from 'react-native';
import { QRCode as QRSlow } from 'react-native-custom-qr-codes';
import { BlueSpacing40, SafeBlueArea, BlueCard, BlueText, BlueHeaderDefaultSub } from '../../BlueComponents';
import { BlueSpacing40, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle } from '../../BlueComponents';
import PropTypes from 'prop-types';
const QRFast = require('react-native-qrcode');
/** @type {AppStorage} */
@ -17,11 +17,11 @@ if (aspectRatio > 1.6) {
}
export default class WalletXpub extends Component {
static navigationOptions = {
header: ({ navigation }) => {
return <BlueHeaderDefaultSub leftText={loc.wallets.xpub.title} onClose={() => navigation.goBack(null)} />;
},
};
static navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
title: loc.wallets.xpub.title,
headerLeft: null,
});
constructor(props) {
super(props);