Browse Source

Merge pull request #11 from sansegkh/master

merged from master
pulltorefresh
San Segkhoonthod 6 years ago
committed by GitHub
parent
commit
bb39995f11
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .buckconfig
  2. 73
      .flowconfig
  3. 1
      .gitattributes
  4. 55
      .gitignore
  5. 2
      App.js
  6. 46
      App.test.js
  7. 16
      App2.test.js
  8. 243
      BlueComponents.js
  9. 144
      HDWallet.test.js
  10. 42
      LightningCustodianWallet.test.js
  11. 21
      NavigationService.js
  12. 18
      android/app/BUCK
  13. 97
      android/app/app.iml
  14. 49
      android/app/build.gradle
  15. 19
      android/app/build_defs.bzl
  16. 11
      android/app/src/main/AndroidManifest.xml
  17. BIN
      android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf
  18. BIN
      android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf
  19. BIN
      android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf
  20. 71
      android/app/src/main/java/com/bluewallet/MainApplication.java
  21. 0
      android/app/src/main/java/io/bluewallet/bluewallet/MainActivity.java
  22. 1
      android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java
  23. 92
      android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java
  24. 2
      android/app/src/main/res/values/strings.xml
  25. 23
      android/build.gradle
  26. 93
      android/build/intermediates/lint-cache/maven.google/com/android/support/group-index.xml
  27. 120
      android/build/intermediates/lint-cache/maven.google/master-index.xml
  28. 2
      android/gradle/wrapper/gradle-wrapper.properties
  29. 2
      android/metadata/en-US/full_description.txt
  30. 18
      android/settings.gradle
  31. 5
      app.json
  32. 3
      babel.config.js
  33. 12
      class/lightning-custodian-wallet.js
  34. 79
      class/walletGradient.js
  35. 4
      events.js
  36. 1355
      ios/BlueWallet.xcodeproj/project.pbxproj
  37. 2
      ios/BlueWallet/AppDelegate.h
  38. 17
      ios/BlueWallet/AppDelegate.m
  39. 10
      ios/BlueWallet/Info.plist
  40. 2
      ios/BlueWallet/main.m
  41. 2
      ios/BlueWalletTests/BlueWalletTests.m
  42. 2
      ios/BlueWalletTests/Info.plist
  43. 2
      ios/fastlane/metadata/en-US/description.txt
  44. 2
      ios/fastlane/metadata/es-ES/description.txt
  45. 2
      ios/fastlane/metadata/pt-BR/description.txt
  46. 2
      ios/fastlane/metadata/pt-PT/description.txt
  47. 2
      loc/cs_CZ.js
  48. 2
      loc/da_DK.js
  49. 2
      loc/de_DE.js
  50. 2
      loc/en.js
  51. 2
      loc/es.js
  52. 2
      loc/fr_FR.js
  53. 54
      loc/hr_HR.js
  54. 7
      loc/index.js
  55. 2
      loc/nl_NL.js
  56. 2
      loc/pt_BR.js
  57. 2
      loc/pt_PT.js
  58. 2
      models/fiatUnit.js
  59. 5469
      package-lock.json
  60. 59
      package.json
  61. 1
      patches/fix_mangle.sh
  62. 16
      patches/minifier.js.patch
  63. 448
      screen/lnd/browser.js
  64. 6
      screen/lnd/lndCreateInvoice.js
  65. 36
      screen/lnd/lndViewAdditionalInvoiceInformation.js
  66. 48
      screen/lnd/lndViewInvoice.js
  67. 126
      screen/lnd/scanLndInvoice.js
  68. 36
      screen/receive/details.js
  69. 100
      screen/receive/receiveAmount.js
  70. 150
      screen/send/details.js
  71. 27
      screen/send/scanQrAddress.js
  72. 122
      screen/settings/about.js
  73. 2
      screen/settings/language.js
  74. 6
      screen/settings/lightningSettings.js
  75. 2
      screen/transactions/details.js
  76. 3
      screen/wallets/add.js
  77. 34
      screen/wallets/buyBitcoin.js
  78. 144
      screen/wallets/details.js
  79. 9
      screen/wallets/list.js
  80. 40
      screen/wallets/reorderWallets.js
  81. 40
      screen/wallets/selectWallet.js
  82. 30
      screen/wallets/transactions.js
  83. 2
      screen/wallets/walletMigrate.js
  84. 17
      screen/wallets/xpub.js

6
.buckconfig

@ -0,0 +1,6 @@
[android]
target = Google Inc.:Google APIs:23
[maven_repositories]
central = https://repo1.maven.org/maven2

73
.flowconfig

@ -2,74 +2,69 @@
; We fork some components by platform ; We fork some components by platform
.*/*[.]android.js .*/*[.]android.js
; Ignore templates for 'react-native init'
<PROJECT_ROOT>/node_modules/react-native/local-cli/templates/.*
; Ignore RN jest
<PROJECT_ROOT>/node_modules/react-native/jest/.*
; Ignore RNTester
<PROJECT_ROOT>/node_modules/react-native/RNTester/.*
; Ignore the website subdir
<PROJECT_ROOT>/node_modules/react-native/website/.*
; Ignore the Dangerfile
<PROJECT_ROOT>/node_modules/react-native/danger/dangerfile.js
; Ignore Fbemitter
<PROJECT_ROOT>/node_modules/fbemitter/.*
; Ignore "BUCK" generated dirs ; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/node_modules/react-native/\.buckd/ <PROJECT_ROOT>/\.buckd/
; Ignore unexpected extra "@providesModule" ; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.* .*/node_modules/.*/node_modules/fbjs/.*
; Ignore duplicate module providers
; For RN Apps installed via npm, "Libraries" folder is inside
; "node_modules/react-native" but in the source repo it is in the root
.*/Libraries/react-native/React.js
; Ignore polyfills ; Ignore polyfills
<PROJECT_ROOT>/node_modules/react-native/Libraries/polyfills/.* .*/Libraries/polyfills/.*
; Ignore various node_modules ; Ignore metro
<PROJECT_ROOT>/node_modules/react-native-gesture-handler/.* .*/node_modules/metro/.*
<PROJECT_ROOT>/node_modules/expo/.*
<PROJECT_ROOT>/node_modules/react-navigation/.*
<PROJECT_ROOT>/node_modules/xdl/.*
<PROJECT_ROOT>/node_modules/reqwest/.*
<PROJECT_ROOT>/node_modules/metro-bundler/.*
[include] [include]
[libs] [libs]
node_modules/react-native/Libraries/react-native/react-native-interface.js node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow/ node_modules/react-native/flow/
node_modules/expo/flow/ node_modules/react-native/flow-github/
[options] [options]
emoji=true emoji=true
module.system=haste esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable
module.file_ext=.js module.system=haste
module.file_ext=.jsx module.system.haste.use_name_reducers=true
module.file_ext=.json # get basename
module.file_ext=.ios.js module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
# strip .js or .js.flow suffix
module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
# strip .ios suffix
module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
module.system.haste.paths.blacklist=.*/__tests__/.*
module.system.haste.paths.blacklist=.*/__mocks__/.*
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
munge_underscores=true munge_underscores=true
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
module.file_ext=.js
module.file_ext=.jsx
module.file_ext=.json
module.file_ext=.native.js
suppress_type=$FlowIssue suppress_type=$FlowIssue
suppress_type=$FlowFixMe suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState suppress_type=$FlowFixMeState
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\) suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
unsafe.enable_getters_and_setters=true
[version] [version]
^0.56.0 ^0.85.0

1
.gitattributes

@ -0,0 +1 @@
*.pbxproj -text

55
.gitignore

@ -1,29 +1,10 @@
# See https://help.github.com/ignore-files/ for more about ignoring files. # OSX
# dependencies
/node_modules
node_modules/
# misc
.env.local
.env.development.local
.env.test.local
.env.production.local
.idea/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
class/constants.js
# OSX# OSX
# #
.DS_Store .DS_Store
# Xcode # Xcode
# #
build/ build/
ios/build/*
*.pbxuser *.pbxuser
!default.pbxuser !default.pbxuser
*.mode1v3 *.mode1v3
@ -39,13 +20,37 @@ DerivedData
*.hmap *.hmap
*.ipa *.ipa
*.xcuserstate *.xcuserstate
# project.xcworkspace
# Android # Android/IntelliJ
# #
android/local.properties build/
android/app/bluewallet-release-key.keystore
.idea .idea
.gradle .gradle
local.properties
*.iml *.iml
build/
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# Bundle artifact
*.jsbundle

2
App.js

@ -2,6 +2,7 @@ import React from 'react';
import { Linking } from 'react-native'; import { Linking } from 'react-native';
import { NavigationActions } from 'react-navigation'; import { NavigationActions } from 'react-navigation';
import MainBottomTabs from './MainBottomTabs'; import MainBottomTabs from './MainBottomTabs';
import NavigationService from './NavigationService';
export default class App extends React.Component { export default class App extends React.Component {
navigator = null; navigator = null;
@ -53,6 +54,7 @@ export default class App extends React.Component {
<MainBottomTabs <MainBottomTabs
ref={nav => { ref={nav => {
this.navigator = nav; this.navigator = nav;
NavigationService.setTopLevelNavigator(nav);
}} }}
/> />
); );

46
App.test.js

@ -57,7 +57,7 @@ describe('unit - LegacyWallet', function() {
let b = LegacyWallet.fromJson(key); let b = LegacyWallet.fromJson(key);
assert(key === JSON.stringify(b)); assert(key === JSON.stringify(b));
assert.equal(key, JSON.stringify(b)); assert.strictEqual(key, JSON.stringify(b));
}); });
it('can validate addresses', () => { it('can validate addresses', () => {
@ -118,8 +118,8 @@ it('Appstorage - loadFromDisk works', async () => {
let Storage2 = new AppStorage(); let Storage2 = new AppStorage();
await Storage2.loadFromDisk(); await Storage2.loadFromDisk();
assert.equal(Storage2.wallets.length, 1); assert.strictEqual(Storage2.wallets.length, 1);
assert.equal(Storage2.wallets[0].getLabel(), 'testlabel'); assert.strictEqual(Storage2.wallets[0].getLabel(), 'testlabel');
let isEncrypted = await Storage2.storageIsEncrypted(); let isEncrypted = await Storage2.storageIsEncrypted();
assert.ok(!isEncrypted); assert.ok(!isEncrypted);
@ -146,7 +146,7 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => {
assert.ok(!isEncrypted); assert.ok(!isEncrypted);
await Storage.encryptStorage('password'); await Storage.encryptStorage('password');
isEncrypted = await Storage.storageIsEncrypted(); isEncrypted = await Storage.storageIsEncrypted();
assert.equal(Storage.cachedPassword, 'password'); assert.strictEqual(Storage.cachedPassword, 'password');
assert.ok(isEncrypted); assert.ok(isEncrypted);
// saved, now trying to load, using good password // saved, now trying to load, using good password
@ -156,8 +156,8 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => {
assert.ok(isEncrypted); assert.ok(isEncrypted);
let loadResult = await Storage2.loadFromDisk('password'); let loadResult = await Storage2.loadFromDisk('password');
assert.ok(loadResult); assert.ok(loadResult);
assert.equal(Storage2.wallets.length, 1); assert.strictEqual(Storage2.wallets.length, 1);
assert.equal(Storage2.wallets[0].getLabel(), 'testlabel'); assert.strictEqual(Storage2.wallets[0].getLabel(), 'testlabel');
// now trying to load, using bad password // now trying to load, using bad password
@ -166,7 +166,7 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => {
assert.ok(isEncrypted); assert.ok(isEncrypted);
loadResult = await Storage2.loadFromDisk('passwordBAD'); loadResult = await Storage2.loadFromDisk('passwordBAD');
assert.ok(!loadResult); assert.ok(!loadResult);
assert.equal(Storage2.wallets.length, 0); assert.strictEqual(Storage2.wallets.length, 0);
// now, trying case with adding data after decrypt. // now, trying case with adding data after decrypt.
// saveToDisk should be handled correctly // saveToDisk should be handled correctly
@ -176,14 +176,14 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => {
assert.ok(isEncrypted); assert.ok(isEncrypted);
loadResult = await Storage2.loadFromDisk('password'); loadResult = await Storage2.loadFromDisk('password');
assert.ok(loadResult); assert.ok(loadResult);
assert.equal(Storage2.wallets.length, 1); assert.strictEqual(Storage2.wallets.length, 1);
assert.equal(Storage2.wallets[0].getLabel(), 'testlabel'); assert.strictEqual(Storage2.wallets[0].getLabel(), 'testlabel');
w = new SegwitP2SHWallet(); w = new SegwitP2SHWallet();
w.setLabel('testlabel2'); w.setLabel('testlabel2');
await w.generate(); await w.generate();
Storage2.wallets.push(w); Storage2.wallets.push(w);
assert.equal(Storage2.wallets.length, 2); assert.strictEqual(Storage2.wallets.length, 2);
assert.equal(Storage2.wallets[1].getLabel(), 'testlabel2'); assert.strictEqual(Storage2.wallets[1].getLabel(), 'testlabel2');
await Storage2.saveToDisk(); await Storage2.saveToDisk();
// saved to encrypted storage after load. next load should be successfull // saved to encrypted storage after load. next load should be successfull
Storage2 = new AppStorage(); Storage2 = new AppStorage();
@ -191,15 +191,15 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => {
assert.ok(isEncrypted); assert.ok(isEncrypted);
loadResult = await Storage2.loadFromDisk('password'); loadResult = await Storage2.loadFromDisk('password');
assert.ok(loadResult); assert.ok(loadResult);
assert.equal(Storage2.wallets.length, 2); assert.strictEqual(Storage2.wallets.length, 2);
assert.equal(Storage2.wallets[0].getLabel(), 'testlabel'); assert.strictEqual(Storage2.wallets[0].getLabel(), 'testlabel');
assert.equal(Storage2.wallets[1].getLabel(), 'testlabel2'); assert.strictEqual(Storage2.wallets[1].getLabel(), 'testlabel2');
// next, adding new `fake` storage which should be unlocked with `fake` password // next, adding new `fake` storage which should be unlocked with `fake` password
let createFakeStorageResult = await Storage2.createFakeStorage('fakePassword'); let createFakeStorageResult = await Storage2.createFakeStorage('fakePassword');
assert.ok(createFakeStorageResult); assert.ok(createFakeStorageResult);
assert.equal(Storage2.wallets.length, 0); assert.strictEqual(Storage2.wallets.length, 0);
assert.equal(Storage2.cachedPassword, 'fakePassword'); assert.strictEqual(Storage2.cachedPassword, 'fakePassword');
w = new SegwitP2SHWallet(); w = new SegwitP2SHWallet();
w.setLabel('fakewallet'); w.setLabel('fakewallet');
await w.generate(); await w.generate();
@ -210,14 +210,14 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => {
let Storage3 = new AppStorage(); let Storage3 = new AppStorage();
loadResult = await Storage3.loadFromDisk('password'); loadResult = await Storage3.loadFromDisk('password');
assert.ok(loadResult); assert.ok(loadResult);
assert.equal(Storage3.wallets.length, 2); assert.strictEqual(Storage3.wallets.length, 2);
assert.equal(Storage3.wallets[0].getLabel(), 'testlabel'); assert.strictEqual(Storage3.wallets[0].getLabel(), 'testlabel');
// fake: // fake:
Storage3 = new AppStorage(); Storage3 = new AppStorage();
loadResult = await Storage3.loadFromDisk('fakePassword'); loadResult = await Storage3.loadFromDisk('fakePassword');
assert.ok(loadResult); assert.ok(loadResult);
assert.equal(Storage3.wallets.length, 1); assert.strictEqual(Storage3.wallets.length, 1);
assert.equal(Storage3.wallets[0].getLabel(), 'fakewallet'); assert.strictEqual(Storage3.wallets[0].getLabel(), 'fakewallet');
}); });
it('Wallet can fetch UTXO', async () => { it('Wallet can fetch UTXO', async () => {
@ -245,7 +245,7 @@ it('Wallet can fetch TXs', async () => {
let w = new LegacyWallet(); let w = new LegacyWallet();
w._address = '12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'; w._address = '12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG';
await w.fetchTransactions(); await w.fetchTransactions();
assert.equal(w.getTransactions().length, 2); assert.strictEqual(w.getTransactions().length, 2);
let tx0 = w.getTransactions()[0]; let tx0 = w.getTransactions()[0];
let txExpected = { let txExpected = {
@ -296,7 +296,7 @@ it('Wallet can fetch TXs', async () => {
delete txExpected.confirmations; delete txExpected.confirmations;
delete tx0.preference; // that bs is not always the same delete tx0.preference; // that bs is not always the same
delete txExpected.preference; delete txExpected.preference;
assert.deepEqual(tx0, txExpected); assert.deepStrictEqual(tx0, txExpected);
}); });
describe('currency', () => { describe('currency', () => {
@ -321,7 +321,7 @@ describe('currency', () => {
await currency.setPrefferedCurrency(FiatUnit.EUR); await currency.setPrefferedCurrency(FiatUnit.EUR);
await currency.startUpdater(); await currency.startUpdater();
let preferred = await currency.getPreferredCurrency(); let preferred = await currency.getPreferredCurrency();
assert.equal(preferred.endPointKey, 'EUR'); assert.strictEqual(preferred.endPointKey, 'EUR');
cur = JSON.parse(AsyncStorage.storageCache[AppStorage.EXCHANGE_RATES]); cur = JSON.parse(AsyncStorage.storageCache[AppStorage.EXCHANGE_RATES]);
assert.ok(cur['BTC_EUR'] > 0); assert.ok(cur['BTC_EUR'] > 0);
}); });

16
App2.test.js

@ -14,7 +14,10 @@ it('bip38 decodes', async () => {
{ N: 1, r: 8, p: 8 }, // using non-default parameters to speed it up (not-bip38 compliant) { N: 1, r: 8, p: 8 }, // using non-default parameters to speed it up (not-bip38 compliant)
); );
assert.equal(wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed), '5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR'); assert.strictEqual(
wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed),
'5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR',
);
}); });
it('bip38 decodes slow', async () => { it('bip38 decodes slow', async () => {
@ -29,7 +32,10 @@ it('bip38 decodes slow', async () => {
let encryptedKey = '6PnU5voARjBBykwSddwCdcn6Eu9EcsK24Gs5zWxbJbPZYW7eiYQP8XgKbN'; let encryptedKey = '6PnU5voARjBBykwSddwCdcn6Eu9EcsK24Gs5zWxbJbPZYW7eiYQP8XgKbN';
let decryptedKey = await bip38.decrypt(encryptedKey, 'qwerty', status => process.stdout.write(parseInt(status.percent) + '%\r')); let decryptedKey = await bip38.decrypt(encryptedKey, 'qwerty', status => process.stdout.write(parseInt(status.percent) + '%\r'));
assert.equal(wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed), 'KxqRtpd9vFju297ACPKHrGkgXuberTveZPXbRDiQ3MXZycSQYtjc'); assert.strictEqual(
wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed),
'KxqRtpd9vFju297ACPKHrGkgXuberTveZPXbRDiQ3MXZycSQYtjc',
);
}); });
describe('Watch only wallet', () => { describe('Watch only wallet', () => {
@ -46,16 +52,16 @@ describe('Watch only wallet', () => {
w.setSecret('167zK5iZrs1U6piDqubD3FjRqUTM2CZnb8'); w.setSecret('167zK5iZrs1U6piDqubD3FjRqUTM2CZnb8');
await w.fetchTransactions(); await w.fetchTransactions();
assert.equal(w.getTransactions().length, 233); assert.strictEqual(w.getTransactions().length, 233);
w = new WatchOnlyWallet(); w = new WatchOnlyWallet();
w.setSecret('1BiJW1jyUaxcJp2JWwbPLPzB1toPNWTFJV'); w.setSecret('1BiJW1jyUaxcJp2JWwbPLPzB1toPNWTFJV');
await w.fetchTransactions(); await w.fetchTransactions();
assert.equal(w.getTransactions().length, 2); assert.strictEqual(w.getTransactions().length, 2);
// fetch again and make sure no duplicates // fetch again and make sure no duplicates
await w.fetchTransactions(); await w.fetchTransactions();
assert.equal(w.getTransactions().length, 2); assert.strictEqual(w.getTransactions().length, 2);
}); });
it('can fetch complex TXs', async () => { it('can fetch complex TXs', async () => {

243
BlueComponents.js

@ -1,4 +1,5 @@
/* eslint react/prop-types: 0 */ /* eslint react/prop-types: 0 */
/* global alert */
/** @type {AppStorage} */ /** @type {AppStorage} */
import React, { Component } from 'react'; import React, { Component } from 'react';
import Ionicons from 'react-native-vector-icons/Ionicons'; import Ionicons from 'react-native-vector-icons/Ionicons';
@ -10,23 +11,25 @@ import {
Animated, Animated,
ActivityIndicator, ActivityIndicator,
View, View,
UIManager,
StyleSheet, StyleSheet,
Dimensions, Dimensions,
Image, Image,
SafeAreaView, SafeAreaView,
Clipboard, Clipboard,
Platform, Platform,
LayoutAnimation,
TextInput, TextInput,
} from 'react-native'; } from 'react-native';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import { WatchOnlyWallet, LegacyWallet } from './class'; import { LightningCustodianWallet } from './class';
import Carousel from 'react-native-snap-carousel'; import Carousel from 'react-native-snap-carousel';
import DeviceInfo from 'react-native-device-info'; import DeviceInfo from 'react-native-device-info';
import { HDLegacyP2PKHWallet } from './class/hd-legacy-p2pkh-wallet';
import { HDLegacyBreadwalletWallet } from './class/hd-legacy-breadwallet-wallet';
import { HDSegwitP2SHWallet } from './class/hd-segwit-p2sh-wallet';
import { LightningCustodianWallet } from './class/lightning-custodian-wallet';
import { BitcoinUnit } from './models/bitcoinUnits'; import { BitcoinUnit } from './models/bitcoinUnits';
import NavigationService from './NavigationService';
import ImagePicker from 'react-native-image-picker';
import WalletGradient from './class/walletGradient';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
let loc = require('./loc/'); let loc = require('./loc/');
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('./BlueApp'); let BlueApp = require('./BlueApp');
@ -41,30 +44,29 @@ if (aspectRatio > 1.6) {
export class BlueButton extends Component { export class BlueButton extends Component {
render() { render() {
// eslint-disable-next-line const backgroundColor = this.props.disabled ? '#99a0ab' : '#ccddf9';
this.props.buttonStyle = this.props.buttonStyle || {};
return ( return (
<Button <TouchableOpacity
activeOpacity={0.1}
delayPressIn={0}
{...this.props}
style={{ style={{
flex: 1,
borderWidth: 0.7, borderWidth: 0.7,
borderColor: 'transparent', borderColor: 'transparent',
backgroundColor: this.props.hasOwnProperty('backgroundColor') ? this.props.backgroundColor : backgroundColor,
minHeight: 45,
height: 45,
maxHeight: 45,
borderRadius: 25,
minWidth: width / 1.5,
justifyContent: 'center',
alignItems: 'center',
}} }}
buttonStyle={Object.assign( {...this.props}
{ >
backgroundColor: '#ccddf9', <View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
minHeight: 45, {this.props.icon && <Icon name={this.props.icon.name} type={this.props.icon.type} color={this.props.icon.color} />}
height: 45, {this.props.title && <Text style={{ marginHorizontal: 8, fontSize: 16, color: '#0c2550' }}>{this.props.title}</Text>}
borderWidth: 0, </View>
borderRadius: 25, </TouchableOpacity>
},
this.props.buttonStyle,
)}
color="#0c2550"
/>
); );
} }
} }
@ -193,6 +195,60 @@ export const BlueCopyToClipboardButton = ({ stringToCopy }) => {
); );
}; };
export class BlueCopyTextToClipboard extends Component {
static propTypes = {
text: PropTypes.string,
};
static defaultProps = {
text: '',
};
state = { hasTappedText: false };
constructor() {
super();
if (Platform.OS === 'android') UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
}
copyToClipboard = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring, () => {
Clipboard.setString(this.props.text);
setTimeout(() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
this.setState({ hasTappedText: false });
}, 1000);
});
this.setState({ hasTappedText: true });
};
render() {
return (
<View style={{ justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}>
<TouchableOpacity onPress={this.copyToClipboard} disabled={this.state.hasTappedText}>
<Text style={styleCopyTextToClipboard.address} numberOfLines={0}>
{this.props.text}
</Text>
{this.state.hasTappedText && (
<Text style={styleCopyTextToClipboard.address} numberOfLines={0}>
{loc.wallets.xpub.copiedToClipboard}
</Text>
)}
</TouchableOpacity>
</View>
);
}
}
const styleCopyTextToClipboard = StyleSheet.create({
address: {
marginVertical: 32,
fontSize: 15,
color: '#9aa0aa',
textAlign: 'center',
},
});
export class SafeBlueArea extends Component { export class SafeBlueArea extends Component {
render() { render() {
return ( return (
@ -215,13 +271,12 @@ export class BlueText extends Component {
render() { render() {
return ( return (
<Text <Text
style={Object.assign( style={{
{ color: BlueApp.settings.foregroundColor,
color: BlueApp.settings.foregroundColor,
},
// eslint-disable-next-line // eslint-disable-next-line
this.props.style, ...this.props.style,
)} }}
{...this.props} {...this.props}
/> />
); );
@ -883,7 +938,7 @@ export class NewWalletPanel extends Component {
style={{ marginVertical: 17 }} style={{ marginVertical: 17 }}
> >
<LinearGradient <LinearGradient
colors={['#eef0f4', '#eef0f4']} colors={WalletGradient.createWallet}
style={{ style={{
padding: 15, padding: 15,
borderRadius: 10, borderRadius: 10,
@ -964,39 +1019,6 @@ export class WalletsCarousel extends Component {
); );
} }
let gradient1 = '#65ceef';
let gradient2 = '#68bbe1';
if (WatchOnlyWallet.type === item.type) {
gradient1 = '#7d7d7d';
gradient2 = '#4a4a4a';
}
if (LegacyWallet.type === item.type) {
gradient1 = '#40fad1';
gradient2 = '#15be98';
}
if (HDLegacyP2PKHWallet.type === item.type) {
gradient1 = '#e36dfa';
gradient2 = '#bd10e0';
}
if (HDLegacyBreadwalletWallet.type === item.type) {
gradient1 = '#fe6381';
gradient2 = '#f99c42';
}
if (HDSegwitP2SHWallet.type === item.type) {
gradient1 = '#c65afb';
gradient2 = '#9053fe';
}
if (LightningCustodianWallet.type === item.type) {
gradient1 = '#f1be07';
gradient2 = '#f79056';
}
return ( return (
<Animated.View <Animated.View
style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }} style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }}
@ -1010,13 +1032,13 @@ export class WalletsCarousel extends Component {
onLongPress={WalletsCarousel.handleLongPress} onLongPress={WalletsCarousel.handleLongPress}
onPress={() => { onPress={() => {
if (WalletsCarousel.handleClick) { if (WalletsCarousel.handleClick) {
WalletsCarousel.handleClick(index, [gradient1, gradient2]); WalletsCarousel.handleClick(index);
} }
}} }}
> >
<LinearGradient <LinearGradient
shadowColor="#000000" shadowColor="#000000"
colors={[gradient1, gradient2]} colors={WalletGradient.gradientsFor(item.type)}
style={{ style={{
padding: 15, padding: 15,
borderRadius: 10, borderRadius: 10,
@ -1111,6 +1133,94 @@ export class WalletsCarousel extends Component {
} }
} }
export class BlueAddressInput extends Component {
static propTypes = {
isLoading: PropTypes.bool,
onChangeText: PropTypes.func,
onBarScanned: PropTypes.func,
address: PropTypes.string,
};
static defaultProps = {
isLoading: false,
address: '',
};
render() {
return (
<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.props.onChangeText(text);
}}
placeholder={loc.send.details.address}
numberOfLines={1}
value={this.props.address}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
editable={!this.props.isLoading}
/>
<TouchableOpacity
disabled={this.props.isLoading}
onPress={() => {
ImagePicker.showImagePicker(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
customButtons: [{ name: 'navigatetoQRScan', title: 'Use Camera' }],
},
response => {
if (response.customButton) {
NavigationService.navigate('ScanQrAddress', { onBarScanned: this.props.onBarScanned });
} else if (response.uri) {
const uri = response.uri.toString().replace('file://', '');
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
this.props.onBarScanned(result);
} else {
alert('The selected image does not contain a QR Code.');
}
});
}
},
);
}}
style={{
width: 75,
height: 36,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: '#bebebe',
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
marginHorizontal: 4,
}}
>
<Icon name="qrcode" size={22} type="font-awesome" color="#FFFFFF" />
<Text style={{ color: '#FFFFFF' }}>{loc.send.details.scan}</Text>
</TouchableOpacity>
</View>
);
}
}
export class BlueBitcoinAmount extends Component { export class BlueBitcoinAmount extends Component {
static propTypes = { static propTypes = {
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
@ -1148,6 +1258,7 @@ export class BlueBitcoinAmount extends Component {
ref={textInput => (this.textInput = textInput)} ref={textInput => (this.textInput = textInput)}
editable={!this.props.isLoading && !this.props.disabled} editable={!this.props.isLoading && !this.props.disabled}
value={amount} value={amount}
autoFocus={this.props.pointerEvents !== 'none'}
placeholderTextColor={this.props.disabled ? '#99a0ab' : '#0f5cc0'} placeholderTextColor={this.props.disabled ? '#99a0ab' : '#0f5cc0'}
style={{ style={{
color: this.props.disabled ? '#99a0ab' : '#0f5cc0', color: this.props.disabled ? '#99a0ab' : '#0f5cc0',

144
HDWallet.test.js

@ -6,13 +6,13 @@ let bitcoin = require('bitcoinjs-lib');
it('can convert witness to address', () => { it('can convert witness to address', () => {
let address = SegwitP2SHWallet.witnessToAddress('035c618df829af694cb99e664ce1b34f80ad2c3b49bcd0d9c0b1836c66b2d25fd8'); let address = SegwitP2SHWallet.witnessToAddress('035c618df829af694cb99e664ce1b34f80ad2c3b49bcd0d9c0b1836c66b2d25fd8');
assert.equal(address, '34ZVGb3gT8xMLT6fpqC6dNVqJtJmvdjbD7'); assert.strictEqual(address, '34ZVGb3gT8xMLT6fpqC6dNVqJtJmvdjbD7');
address = SegwitBech32Wallet.witnessToAddress('035c618df829af694cb99e664ce1b34f80ad2c3b49bcd0d9c0b1836c66b2d25fd8'); address = SegwitBech32Wallet.witnessToAddress('035c618df829af694cb99e664ce1b34f80ad2c3b49bcd0d9c0b1836c66b2d25fd8');
assert.equal(address, 'bc1quhnve8q4tk3unhmjts7ymxv8cd6w9xv8wy29uv'); assert.strictEqual(address, 'bc1quhnve8q4tk3unhmjts7ymxv8cd6w9xv8wy29uv');
address = SegwitBech32Wallet.scriptPubKeyToAddress('00144d757460da5fcaf84cc22f3847faaa1078e84f6a'); address = SegwitBech32Wallet.scriptPubKeyToAddress('00144d757460da5fcaf84cc22f3847faaa1078e84f6a');
assert.equal(address, 'bc1qf46hgcx6tl90snxz9uuy0742zpuwsnm27ysdh7'); assert.strictEqual(address, 'bc1qf46hgcx6tl90snxz9uuy0742zpuwsnm27ysdh7');
}); });
it('can create a Segwit HD (BIP49)', async function() { it('can create a Segwit HD (BIP49)', async function() {
@ -21,39 +21,39 @@ it('can create a Segwit HD (BIP49)', async function() {
'honey risk juice trip orient galaxy win situate shoot anchor bounce remind horse traffic exotic since escape mimic ramp skin judge owner topple erode'; 'honey risk juice trip orient galaxy win situate shoot anchor bounce remind horse traffic exotic since escape mimic ramp skin judge owner topple erode';
let hd = new HDSegwitP2SHWallet(); let hd = new HDSegwitP2SHWallet();
hd.setSecret(mnemonic); hd.setSecret(mnemonic);
assert.equal('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', hd._getExternalAddressByIndex(0)); assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', hd._getExternalAddressByIndex(0));
assert.equal('35p5LwCAE7mH2css7onyQ1VuS1jgWtQ4U3', hd._getExternalAddressByIndex(1)); assert.strictEqual('35p5LwCAE7mH2css7onyQ1VuS1jgWtQ4U3', hd._getExternalAddressByIndex(1));
assert.equal('32yn5CdevZQLk3ckuZuA8fEKBco8mEkLei', hd._getInternalAddressByIndex(0)); assert.strictEqual('32yn5CdevZQLk3ckuZuA8fEKBco8mEkLei', hd._getInternalAddressByIndex(0));
assert.equal(true, hd.validateMnemonic()); assert.strictEqual(true, hd.validateMnemonic());
await hd.fetchBalance(); await hd.fetchBalance();
assert.equal(hd.getBalance(), 0); assert.strictEqual(hd.getBalance(), 0);
assert.ok(hd._lastTxFetch === 0); assert.ok(hd._lastTxFetch === 0);
await hd.fetchTransactions(); await hd.fetchTransactions();
assert.ok(hd._lastTxFetch > 0); assert.ok(hd._lastTxFetch > 0);
assert.equal(hd.transactions.length, 4); assert.strictEqual(hd.transactions.length, 4);
assert.equal('L4MqtwJm6hkbACLG4ho5DF8GhcXdLEbbvpJnbzA9abfD6RDpbr2m', hd._getExternalWIFByIndex(0)); assert.strictEqual('L4MqtwJm6hkbACLG4ho5DF8GhcXdLEbbvpJnbzA9abfD6RDpbr2m', hd._getExternalWIFByIndex(0));
assert.equal( assert.strictEqual(
'ypub6WhHmKBmHNjcrUVNCa3sXduH9yxutMipDcwiKW31vWjcMbfhQHjXdyx4rqXbEtVgzdbhFJ5mZJWmfWwnP4Vjzx97admTUYKQt6b9D7jjSCp', 'ypub6WhHmKBmHNjcrUVNCa3sXduH9yxutMipDcwiKW31vWjcMbfhQHjXdyx4rqXbEtVgzdbhFJ5mZJWmfWwnP4Vjzx97admTUYKQt6b9D7jjSCp',
hd.getXpub(), hd.getXpub(),
); );
// checking that internal pointer and async address getter return the same address // checking that internal pointer and async address getter return the same address
let freeAddress = await hd.getAddressAsync(); let freeAddress = await hd.getAddressAsync();
assert.equal(hd._getExternalAddressByIndex(hd.next_free_address_index), freeAddress); assert.strictEqual(hd._getExternalAddressByIndex(hd.next_free_address_index), freeAddress);
let freeChangeAddress = await hd.getChangeAddressAsync(); let freeChangeAddress = await hd.getChangeAddressAsync();
assert.equal(hd._getInternalAddressByIndex(hd.next_free_change_address_index), freeChangeAddress); assert.strictEqual(hd._getInternalAddressByIndex(hd.next_free_change_address_index), freeChangeAddress);
}); });
it('Segwit HD (BIP49) can generate addressess only via ypub', async function() { it('Segwit HD (BIP49) can generate addressess only via ypub', async function() {
let ypub = 'ypub6WhHmKBmHNjcrUVNCa3sXduH9yxutMipDcwiKW31vWjcMbfhQHjXdyx4rqXbEtVgzdbhFJ5mZJWmfWwnP4Vjzx97admTUYKQt6b9D7jjSCp'; let ypub = 'ypub6WhHmKBmHNjcrUVNCa3sXduH9yxutMipDcwiKW31vWjcMbfhQHjXdyx4rqXbEtVgzdbhFJ5mZJWmfWwnP4Vjzx97admTUYKQt6b9D7jjSCp';
let hd = new HDSegwitP2SHWallet(); let hd = new HDSegwitP2SHWallet();
hd._xpub = ypub; hd._xpub = ypub;
assert.equal('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', hd._getExternalAddressByIndex(0)); assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', hd._getExternalAddressByIndex(0));
assert.equal('35p5LwCAE7mH2css7onyQ1VuS1jgWtQ4U3', hd._getExternalAddressByIndex(1)); assert.strictEqual('35p5LwCAE7mH2css7onyQ1VuS1jgWtQ4U3', hd._getExternalAddressByIndex(1));
assert.equal('32yn5CdevZQLk3ckuZuA8fEKBco8mEkLei', hd._getInternalAddressByIndex(0)); assert.strictEqual('32yn5CdevZQLk3ckuZuA8fEKBco8mEkLei', hd._getInternalAddressByIndex(0));
}); });
it('can generate Segwit HD (BIP49)', async () => { it('can generate Segwit HD (BIP49)', async () => {
@ -88,56 +88,56 @@ it('HD (BIP49) can create TX', async () => {
await hd.getChangeAddressAsync(); // to refresh internal pointer to next free address await hd.getChangeAddressAsync(); // to refresh internal pointer to next free address
await hd.getAddressAsync(); // to refresh internal pointer to next free address await hd.getAddressAsync(); // to refresh internal pointer to next free address
let txhex = hd.createTx(hd.utxo, 0.000014, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK'); let txhex = hd.createTx(hd.utxo, 0.000014, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK');
assert.equal( assert.strictEqual(
txhex, txhex,
'010000000001029d98d81fe2b596fd79e845fa9f38d7e0b6fb73303c40fac604d04df1fa137aee00000000171600142f18e8406c9d210f30c901b24e5feeae78784eb7ffffffff67fb86f310df24e508d40fce9511c7fde4dd4ee91305fd08a074279a70e2cd22000000001716001468dde644410cc789d91a7f36b823f38369755a1cffffffff02780500000000000017a914a3a65daca3064280ae072b9d6773c027b30abace87dc0500000000000017a914850f4dbc255654de2c12c6f6d79cf9cb756cad038702483045022100dc8390a9fd34c31259fa47f9fc182f20d991110ecfd5b58af1cf542fe8de257a022004c2d110da7b8c4127675beccc63b46fd65c706951f090fd381fa3b21d3c5c08012102edd141c5a27a726dda66be10a38b0fd3ccbb40e7c380034aaa43a1656d5f4dd60247304402207c0aef8313d55e72474247daad955979f62e56d1cbac5f2d14b8b022c6ce112602205d9aa3804f04624b12ab8a5ab0214b529c531c2f71c27c6f18aba6502a6ea0a80121030db3c49461a5e539e97bab62ab2b8f88151d1c2376493cf73ef1d02ef60637fd00000000', '010000000001029d98d81fe2b596fd79e845fa9f38d7e0b6fb73303c40fac604d04df1fa137aee00000000171600142f18e8406c9d210f30c901b24e5feeae78784eb7ffffffff67fb86f310df24e508d40fce9511c7fde4dd4ee91305fd08a074279a70e2cd22000000001716001468dde644410cc789d91a7f36b823f38369755a1cffffffff02780500000000000017a914a3a65daca3064280ae072b9d6773c027b30abace87dc0500000000000017a914850f4dbc255654de2c12c6f6d79cf9cb756cad038702483045022100dc8390a9fd34c31259fa47f9fc182f20d991110ecfd5b58af1cf542fe8de257a022004c2d110da7b8c4127675beccc63b46fd65c706951f090fd381fa3b21d3c5c08012102edd141c5a27a726dda66be10a38b0fd3ccbb40e7c380034aaa43a1656d5f4dd60247304402207c0aef8313d55e72474247daad955979f62e56d1cbac5f2d14b8b022c6ce112602205d9aa3804f04624b12ab8a5ab0214b529c531c2f71c27c6f18aba6502a6ea0a80121030db3c49461a5e539e97bab62ab2b8f88151d1c2376493cf73ef1d02ef60637fd00000000',
); );
txhex = hd.createTx(hd.utxo, 0.000005, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK'); txhex = hd.createTx(hd.utxo, 0.000005, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK');
var tx = bitcoin.Transaction.fromHex(txhex); var tx = bitcoin.Transaction.fromHex(txhex);
assert.equal(tx.ins.length, 1); assert.strictEqual(tx.ins.length, 1);
assert.equal(tx.outs.length, 2); assert.strictEqual(tx.outs.length, 2);
assert.equal(tx.outs[0].value, 500); assert.strictEqual(tx.outs[0].value, 500);
assert.equal(tx.outs[1].value, 400); assert.strictEqual(tx.outs[1].value, 400);
let chunksIn = bitcoin.script.decompile(tx.outs[0].script); let chunksIn = bitcoin.script.decompile(tx.outs[0].script);
let toAddress = bitcoin.address.fromOutputScript(chunksIn); let toAddress = bitcoin.address.fromOutputScript(chunksIn);
chunksIn = bitcoin.script.decompile(tx.outs[1].script); chunksIn = bitcoin.script.decompile(tx.outs[1].script);
let changeAddress = bitcoin.address.fromOutputScript(chunksIn); let changeAddress = bitcoin.address.fromOutputScript(chunksIn);
assert.equal('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress); assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress);
assert.equal(hd._getInternalAddressByIndex(hd.next_free_change_address_index), changeAddress); assert.strictEqual(hd._getInternalAddressByIndex(hd.next_free_change_address_index), changeAddress);
// //
txhex = hd.createTx(hd.utxo, 0.000015, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK'); txhex = hd.createTx(hd.utxo, 0.000015, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK');
tx = bitcoin.Transaction.fromHex(txhex); tx = bitcoin.Transaction.fromHex(txhex);
assert.equal(tx.ins.length, 2); assert.strictEqual(tx.ins.length, 2);
assert.equal(tx.outs.length, 2); assert.strictEqual(tx.outs.length, 2);
// //
txhex = hd.createTx(hd.utxo, 0.00025, 0.00001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK'); txhex = hd.createTx(hd.utxo, 0.00025, 0.00001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK');
tx = bitcoin.Transaction.fromHex(txhex); tx = bitcoin.Transaction.fromHex(txhex);
assert.equal(tx.ins.length, 7); assert.strictEqual(tx.ins.length, 7);
assert.equal(tx.outs.length, 1); assert.strictEqual(tx.outs.length, 1);
chunksIn = bitcoin.script.decompile(tx.outs[0].script); chunksIn = bitcoin.script.decompile(tx.outs[0].script);
toAddress = bitcoin.address.fromOutputScript(chunksIn); toAddress = bitcoin.address.fromOutputScript(chunksIn);
assert.equal('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress); assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress);
// checking that change amount is at least 3x of fee, otherwise screw the change, just add it to fee. // checking that change amount is at least 3x of fee, otherwise screw the change, just add it to fee.
// theres 0.00003 on UTXOs, lets transfer (0.00003 - 100sat), soo fee is equal to change (100 sat) // theres 0.00003 on UTXOs, lets transfer (0.00003 - 100sat), soo fee is equal to change (100 sat)
// which throws @dust error if broadcasted // which throws @dust error if broadcasted
txhex = hd.createTx(hd.utxo, 0.000028, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK'); txhex = hd.createTx(hd.utxo, 0.000028, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK');
tx = bitcoin.Transaction.fromHex(txhex); tx = bitcoin.Transaction.fromHex(txhex);
assert.equal(tx.ins.length, 2); assert.strictEqual(tx.ins.length, 2);
assert.equal(tx.outs.length, 1); // only 1 output, which means change is neglected assert.strictEqual(tx.outs.length, 1); // only 1 output, which means change is neglected
assert.equal(tx.outs[0].value, 2800); assert.strictEqual(tx.outs[0].value, 2800);
}); });
it('Segwit HD (BIP49) can fetch UTXO', async function() { it('Segwit HD (BIP49) can fetch UTXO', async function() {
let hd = new HDSegwitP2SHWallet(); let hd = new HDSegwitP2SHWallet();
hd.usedAddresses = ['1Ez69SnzzmePmZX3WpEzMKTrcBF2gpNQ55', '1BiTCHeYzJNMxBLFCMkwYXNdFEdPJP53ZV']; // hacking internals hd.usedAddresses = ['1Ez69SnzzmePmZX3WpEzMKTrcBF2gpNQ55', '1BiTCHeYzJNMxBLFCMkwYXNdFEdPJP53ZV']; // hacking internals
await hd.fetchUtxo(); await hd.fetchUtxo();
assert.equal(hd.utxo.length, 11); assert.strictEqual(hd.utxo.length, 11);
assert.ok(typeof hd.utxo[0].confirmations === 'number'); assert.ok(typeof hd.utxo[0].confirmations === 'number');
assert.ok(hd.utxo[0].txid); assert.ok(hd.utxo[0].txid);
assert.ok(hd.utxo[0].vout); assert.ok(hd.utxo[0].vout);
@ -168,7 +168,7 @@ it('can work with malformed mnemonic', () => {
hd = new HDSegwitP2SHWallet(); hd = new HDSegwitP2SHWallet();
hd.setSecret(mnemonic); hd.setSecret(mnemonic);
let seed2 = hd.getMnemonicToSeedHex(); let seed2 = hd.getMnemonicToSeedHex();
assert.equal(seed1, seed2); assert.strictEqual(seed1, seed2);
assert.ok(hd.validateMnemonic()); assert.ok(hd.validateMnemonic());
}); });
@ -182,28 +182,28 @@ it('can create a Legacy HD (BIP44)', async function() {
let mnemonic = process.env.HD_MNEMONIC_BREAD; let mnemonic = process.env.HD_MNEMONIC_BREAD;
let hd = new HDLegacyP2PKHWallet(); let hd = new HDLegacyP2PKHWallet();
hd.setSecret(mnemonic); hd.setSecret(mnemonic);
assert.equal(hd.validateMnemonic(), true); assert.strictEqual(hd.validateMnemonic(), true);
assert.equal(hd._getExternalAddressByIndex(0), '12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'); assert.strictEqual(hd._getExternalAddressByIndex(0), '12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG');
assert.equal(hd._getExternalAddressByIndex(1), '1QDCFcpnrZ4yrAQxmbvSgeUC9iZZ8ehcR5'); assert.strictEqual(hd._getExternalAddressByIndex(1), '1QDCFcpnrZ4yrAQxmbvSgeUC9iZZ8ehcR5');
assert.equal(hd._getInternalAddressByIndex(0), '1KZjqYHm7a1DjhjcdcjfQvYfF2h6PqatjX'); assert.strictEqual(hd._getInternalAddressByIndex(0), '1KZjqYHm7a1DjhjcdcjfQvYfF2h6PqatjX');
assert.equal(hd._getInternalAddressByIndex(1), '13CW9WWBsWpDUvLtbFqYziWBWTYUoQb4nU'); assert.strictEqual(hd._getInternalAddressByIndex(1), '13CW9WWBsWpDUvLtbFqYziWBWTYUoQb4nU');
assert.equal( assert.strictEqual(
hd.getXpub(), hd.getXpub(),
'xpub6CQdfC3v9gU86eaSn7AhUFcBVxiGhdtYxdC5Cw2vLmFkfth2KXCMmYcPpvZviA89X6DXDs4PJDk5QVL2G2xaVjv7SM4roWHr1gR4xB3Z7Ps', 'xpub6CQdfC3v9gU86eaSn7AhUFcBVxiGhdtYxdC5Cw2vLmFkfth2KXCMmYcPpvZviA89X6DXDs4PJDk5QVL2G2xaVjv7SM4roWHr1gR4xB3Z7Ps',
); );
assert.equal(hd._getExternalWIFByIndex(0), 'L1hqNoJ26YuCdujMBJfWBNfgf4Jo7AcKFvcNcKLoMtoJDdDtRq7Q'); assert.strictEqual(hd._getExternalWIFByIndex(0), 'L1hqNoJ26YuCdujMBJfWBNfgf4Jo7AcKFvcNcKLoMtoJDdDtRq7Q');
assert.equal(hd._getExternalWIFByIndex(1), 'KyyH4h59iatJWwFfiYPnYkw39SP7cBwydC3xzszsBBXHpfwz9cKb'); assert.strictEqual(hd._getExternalWIFByIndex(1), 'KyyH4h59iatJWwFfiYPnYkw39SP7cBwydC3xzszsBBXHpfwz9cKb');
assert.equal(hd._getInternalWIFByIndex(0), 'Kx3QkrfemEEV49Mj5oWfb4bsWymboPdstta7eN3kAzop9apxYEFP'); assert.strictEqual(hd._getInternalWIFByIndex(0), 'Kx3QkrfemEEV49Mj5oWfb4bsWymboPdstta7eN3kAzop9apxYEFP');
assert.equal(hd._getInternalWIFByIndex(1), 'Kwfg1EDjFapN9hgwafdNPEH22z3vkd4gtG785vXXjJ6uvVWAJGtr'); assert.strictEqual(hd._getInternalWIFByIndex(1), 'Kwfg1EDjFapN9hgwafdNPEH22z3vkd4gtG785vXXjJ6uvVWAJGtr');
await hd.fetchBalance(); await hd.fetchBalance();
assert.equal(hd.balance, 0); assert.strictEqual(hd.balance, 0);
assert.ok(hd._lastTxFetch === 0); assert.ok(hd._lastTxFetch === 0);
await hd.fetchTransactions(); await hd.fetchTransactions();
assert.ok(hd._lastTxFetch > 0); assert.ok(hd._lastTxFetch > 0);
assert.equal(hd.transactions.length, 4); assert.strictEqual(hd.transactions.length, 4);
assert.equal(hd.next_free_address_index, 1); assert.strictEqual(hd.next_free_address_index, 1);
assert.equal(hd.next_free_change_address_index, 1); assert.strictEqual(hd.next_free_change_address_index, 1);
for (let tx of hd.getTransactions()) { for (let tx of hd.getTransactions()) {
assert.ok(tx.value === 1000 || tx.value === 1377 || tx.value === -1377 || tx.value === -1000); assert.ok(tx.value === 1000 || tx.value === 1377 || tx.value === -1377 || tx.value === -1000);
@ -211,17 +211,17 @@ it('can create a Legacy HD (BIP44)', async function() {
// checking that internal pointer and async address getter return the same address // checking that internal pointer and async address getter return the same address
let freeAddress = await hd.getAddressAsync(); let freeAddress = await hd.getAddressAsync();
assert.equal(hd._getExternalAddressByIndex(hd.next_free_address_index), freeAddress); assert.strictEqual(hd._getExternalAddressByIndex(hd.next_free_address_index), freeAddress);
}); });
it('Legacy HD (BIP44) can generate addressess based on xpub', async function() { it('Legacy HD (BIP44) can generate addressess based on xpub', async function() {
let xpub = 'xpub6CQdfC3v9gU86eaSn7AhUFcBVxiGhdtYxdC5Cw2vLmFkfth2KXCMmYcPpvZviA89X6DXDs4PJDk5QVL2G2xaVjv7SM4roWHr1gR4xB3Z7Ps'; let xpub = 'xpub6CQdfC3v9gU86eaSn7AhUFcBVxiGhdtYxdC5Cw2vLmFkfth2KXCMmYcPpvZviA89X6DXDs4PJDk5QVL2G2xaVjv7SM4roWHr1gR4xB3Z7Ps';
let hd = new HDLegacyP2PKHWallet(); let hd = new HDLegacyP2PKHWallet();
hd._xpub = xpub; hd._xpub = xpub;
assert.equal(hd._getExternalAddressByIndex(0), '12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'); assert.strictEqual(hd._getExternalAddressByIndex(0), '12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG');
assert.equal(hd._getInternalAddressByIndex(0), '1KZjqYHm7a1DjhjcdcjfQvYfF2h6PqatjX'); assert.strictEqual(hd._getInternalAddressByIndex(0), '1KZjqYHm7a1DjhjcdcjfQvYfF2h6PqatjX');
assert.equal(hd._getExternalAddressByIndex(1), '1QDCFcpnrZ4yrAQxmbvSgeUC9iZZ8ehcR5'); assert.strictEqual(hd._getExternalAddressByIndex(1), '1QDCFcpnrZ4yrAQxmbvSgeUC9iZZ8ehcR5');
assert.equal(hd._getInternalAddressByIndex(1), '13CW9WWBsWpDUvLtbFqYziWBWTYUoQb4nU'); assert.strictEqual(hd._getInternalAddressByIndex(1), '13CW9WWBsWpDUvLtbFqYziWBWTYUoQb4nU');
}); });
it('Legacy HD (BIP44) can create TX', async () => { it('Legacy HD (BIP44) can create TX', async () => {
@ -239,38 +239,38 @@ it('Legacy HD (BIP44) can create TX', async () => {
await hd.getAddressAsync(); // to refresh internal pointer to next free address await hd.getAddressAsync(); // to refresh internal pointer to next free address
let txhex = hd.createTx(hd.utxo, 0.0008, 0.000005, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK'); let txhex = hd.createTx(hd.utxo, 0.0008, 0.000005, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK');
assert.equal( assert.strictEqual(
txhex, txhex,
'01000000045fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f030000006b4830450221009be5dbe37db5a8409ddce3570140c95d162a07651b1e48cf39a6a741892adc53022061a25b8024d8f3cb1b94f264245de0c6e9a103ea557ddeb66245b40ec8e9384b012102ad7b2216f3a2b38d56db8a7ee5c540fd12c4bbb7013106eff78cc2ace65aa002ffffffff5fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f000000006a47304402207106e9fa4e2e35d351fbccc9c0fad3356d85d0cd35a9d7e9cbcefce5440da1e5022073c1905b5927447378c0f660e62900c1d4b2691730799458889fb87d86f5159101210316e84a2556f30a199541633f5dda6787710ccab26771b7084f4c9e1104f47667ffffffff5fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f020000006a4730440220250b15094096c4d4fe6793da8e45fa118ed057cc2759a480c115e76e23590791022079cdbdc9e630d713395602071e2837ecc1d192a36a24d8ec71bc51d5e62b203b01210316e84a2556f30a199541633f5dda6787710ccab26771b7084f4c9e1104f47667ffffffff5fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f010000006b483045022100879da610e6ed12c84d55f12baf3bf6222d59b5282502b3c7f4db1d22152c16900220759a1c88583cbdaf7fde21c273ad985dfdf94a2fa85e42ee41dcea2fd69136fd012102ad7b2216f3a2b38d56db8a7ee5c540fd12c4bbb7013106eff78cc2ace65aa002ffffffff02803801000000000017a914a3a65daca3064280ae072b9d6773c027b30abace872c4c0000000000001976a9146ee5e3e66dc73587a3a2d77a1a6c8554fae21b8a88ac00000000', '01000000045fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f030000006b4830450221009be5dbe37db5a8409ddce3570140c95d162a07651b1e48cf39a6a741892adc53022061a25b8024d8f3cb1b94f264245de0c6e9a103ea557ddeb66245b40ec8e9384b012102ad7b2216f3a2b38d56db8a7ee5c540fd12c4bbb7013106eff78cc2ace65aa002ffffffff5fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f000000006a47304402207106e9fa4e2e35d351fbccc9c0fad3356d85d0cd35a9d7e9cbcefce5440da1e5022073c1905b5927447378c0f660e62900c1d4b2691730799458889fb87d86f5159101210316e84a2556f30a199541633f5dda6787710ccab26771b7084f4c9e1104f47667ffffffff5fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f020000006a4730440220250b15094096c4d4fe6793da8e45fa118ed057cc2759a480c115e76e23590791022079cdbdc9e630d713395602071e2837ecc1d192a36a24d8ec71bc51d5e62b203b01210316e84a2556f30a199541633f5dda6787710ccab26771b7084f4c9e1104f47667ffffffff5fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f010000006b483045022100879da610e6ed12c84d55f12baf3bf6222d59b5282502b3c7f4db1d22152c16900220759a1c88583cbdaf7fde21c273ad985dfdf94a2fa85e42ee41dcea2fd69136fd012102ad7b2216f3a2b38d56db8a7ee5c540fd12c4bbb7013106eff78cc2ace65aa002ffffffff02803801000000000017a914a3a65daca3064280ae072b9d6773c027b30abace872c4c0000000000001976a9146ee5e3e66dc73587a3a2d77a1a6c8554fae21b8a88ac00000000',
); );
var tx = bitcoin.Transaction.fromHex(txhex); var tx = bitcoin.Transaction.fromHex(txhex);
assert.equal(tx.ins.length, 4); assert.strictEqual(tx.ins.length, 4);
assert.equal(tx.outs.length, 2); assert.strictEqual(tx.outs.length, 2);
assert.equal(tx.outs[0].value, 80000); // payee assert.strictEqual(tx.outs[0].value, 80000); // payee
assert.equal(tx.outs[1].value, 19500); // change assert.strictEqual(tx.outs[1].value, 19500); // change
let chunksIn = bitcoin.script.decompile(tx.outs[0].script); let chunksIn = bitcoin.script.decompile(tx.outs[0].script);
let toAddress = bitcoin.address.fromOutputScript(chunksIn); let toAddress = bitcoin.address.fromOutputScript(chunksIn);
chunksIn = bitcoin.script.decompile(tx.outs[1].script); chunksIn = bitcoin.script.decompile(tx.outs[1].script);
let changeAddress = bitcoin.address.fromOutputScript(chunksIn); let changeAddress = bitcoin.address.fromOutputScript(chunksIn);
assert.equal('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress); assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress);
assert.equal(hd._getInternalAddressByIndex(hd.next_free_change_address_index), changeAddress); assert.strictEqual(hd._getInternalAddressByIndex(hd.next_free_change_address_index), changeAddress);
// checking that change amount is at least 3x of fee, otherwise screw the change, just add it to fee. // checking that change amount is at least 3x of fee, otherwise screw the change, just add it to fee.
// theres 0.001 on UTXOs, lets transfer (0.001 - 100sat), soo fee is equal to change (100 sat) // theres 0.001 on UTXOs, lets transfer (0.001 - 100sat), soo fee is equal to change (100 sat)
// which throws @dust error if broadcasted // which throws @dust error if broadcasted
txhex = hd.createTx(hd.utxo, 0.000998, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK'); txhex = hd.createTx(hd.utxo, 0.000998, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK');
tx = bitcoin.Transaction.fromHex(txhex); tx = bitcoin.Transaction.fromHex(txhex);
assert.equal(tx.ins.length, 4); assert.strictEqual(tx.ins.length, 4);
assert.equal(tx.outs.length, 1); // only 1 output, which means change is neglected assert.strictEqual(tx.outs.length, 1); // only 1 output, which means change is neglected
assert.equal(tx.outs[0].value, 99800); assert.strictEqual(tx.outs[0].value, 99800);
}); });
it('Legacy HD (BIP44) can fetch UTXO', async function() { it('Legacy HD (BIP44) can fetch UTXO', async function() {
let hd = new HDLegacyP2PKHWallet(); let hd = new HDLegacyP2PKHWallet();
hd.usedAddresses = ['1Ez69SnzzmePmZX3WpEzMKTrcBF2gpNQ55', '1BiTCHeYzJNMxBLFCMkwYXNdFEdPJP53ZV']; // hacking internals hd.usedAddresses = ['1Ez69SnzzmePmZX3WpEzMKTrcBF2gpNQ55', '1BiTCHeYzJNMxBLFCMkwYXNdFEdPJP53ZV']; // hacking internals
await hd.fetchUtxo(); await hd.fetchUtxo();
assert.equal(hd.utxo.length, 11); assert.strictEqual(hd.utxo.length, 11);
assert.ok(typeof hd.utxo[0].confirmations === 'number'); assert.ok(typeof hd.utxo[0].confirmations === 'number');
assert.ok(hd.utxo[0].txid); assert.ok(hd.utxo[0].txid);
assert.ok(hd.utxo[0].vout); assert.ok(hd.utxo[0].vout);
@ -290,31 +290,31 @@ it('HD breadwallet works', async function() {
let hdBread = new HDLegacyBreadwalletWallet(); let hdBread = new HDLegacyBreadwalletWallet();
hdBread.setSecret(process.env.HD_MNEMONIC_BREAD); hdBread.setSecret(process.env.HD_MNEMONIC_BREAD);
assert.equal(hdBread.validateMnemonic(), true); assert.strictEqual(hdBread.validateMnemonic(), true);
assert.equal(hdBread._getExternalAddressByIndex(0), '1ARGkNMdsBE36fJhddSwf8PqBXG3s4d2KU'); assert.strictEqual(hdBread._getExternalAddressByIndex(0), '1ARGkNMdsBE36fJhddSwf8PqBXG3s4d2KU');
assert.equal(hdBread._getInternalAddressByIndex(0), '1JLvA5D7RpWgChb4A5sFcLNrfxYbyZdw3V'); assert.strictEqual(hdBread._getInternalAddressByIndex(0), '1JLvA5D7RpWgChb4A5sFcLNrfxYbyZdw3V');
assert.equal( assert.strictEqual(
hdBread.getXpub(), hdBread.getXpub(),
'xpub68nLLEi3KERQY7jyznC9PQSpSjmekrEmN8324YRCXayMXaavbdEJsK4gEcX2bNf9vGzT4xRks9utZ7ot1CTHLtdyCn9udvv1NWvtY7HXroh', 'xpub68nLLEi3KERQY7jyznC9PQSpSjmekrEmN8324YRCXayMXaavbdEJsK4gEcX2bNf9vGzT4xRks9utZ7ot1CTHLtdyCn9udvv1NWvtY7HXroh',
); );
await hdBread.fetchBalance(); await hdBread.fetchBalance();
assert.equal(hdBread.balance, 0); assert.strictEqual(hdBread.balance, 0);
assert.ok(hdBread._lastTxFetch === 0); assert.ok(hdBread._lastTxFetch === 0);
await hdBread.fetchTransactions(); await hdBread.fetchTransactions();
assert.ok(hdBread._lastTxFetch > 0); assert.ok(hdBread._lastTxFetch > 0);
assert.equal(hdBread.transactions.length, 177); assert.strictEqual(hdBread.transactions.length, 177);
for (let tx of hdBread.getTransactions()) { for (let tx of hdBread.getTransactions()) {
assert.ok(tx.confirmations); assert.ok(tx.confirmations);
} }
assert.equal(hdBread.next_free_address_index, 10); assert.strictEqual(hdBread.next_free_address_index, 10);
assert.equal(hdBread.next_free_change_address_index, 118); assert.strictEqual(hdBread.next_free_change_address_index, 118);
// checking that internal pointer and async address getter return the same address // checking that internal pointer and async address getter return the same address
let freeAddress = await hdBread.getAddressAsync(); let freeAddress = await hdBread.getAddressAsync();
assert.equal(hdBread._getExternalAddressByIndex(hdBread.next_free_address_index), freeAddress); assert.strictEqual(hdBread._getExternalAddressByIndex(hdBread.next_free_address_index), freeAddress);
}); });
it('can convert blockchain.info TX to blockcypher TX format', () => { it('can convert blockchain.info TX to blockcypher TX format', () => {

42
LightningCustodianWallet.test.js

@ -152,7 +152,7 @@ describe('LightningCustodianWallet', () => {
} }
await l2.fetchTransactions(); await l2.fetchTransactions();
assert.equal(l2.transactions_raw.length, txLen + 1); assert.strictEqual(l2.transactions_raw.length, txLen + 1);
// transactions became more after paying an invoice // transactions became more after paying an invoice
// now, trying to pay duplicate invoice // now, trying to pay duplicate invoice
@ -165,7 +165,7 @@ describe('LightningCustodianWallet', () => {
} }
assert.ok(caughtError); assert.ok(caughtError);
await l2.fetchTransactions(); await l2.fetchTransactions();
assert.equal(l2.transactions_raw.length, txLen + 1); assert.strictEqual(l2.transactions_raw.length, txLen + 1);
// havent changed since last time // havent changed since last time
end = +new Date(); end = +new Date();
if ((end - start) / 1000 > 9) { if ((end - start) / 1000 > 9) {
@ -191,21 +191,21 @@ describe('LightningCustodianWallet', () => {
await lNew.createAccount(true); await lNew.createAccount(true);
await lNew.authorize(); await lNew.authorize();
await lNew.fetchBalance(); await lNew.fetchBalance();
assert.equal(lNew.balance, 0); assert.strictEqual(lNew.balance, 0);
let invoices = await lNew.getUserInvoices(); let invoices = await lNew.getUserInvoices();
let invoice = await lNew.addInvoice(1, 'test memo'); let invoice = await lNew.addInvoice(1, 'test memo');
let invoices2 = await lNew.getUserInvoices(); let invoices2 = await lNew.getUserInvoices();
assert.equal(invoices2.length, invoices.length + 1); assert.strictEqual(invoices2.length, invoices.length + 1);
assert.ok(invoices2[0].ispaid === false); assert.ok(invoices2[0].ispaid === false);
assert.ok(invoices2[0].description); assert.ok(invoices2[0].description);
assert.equal(invoices2[0].description, 'test memo'); assert.strictEqual(invoices2[0].description, 'test memo');
assert.ok(invoices2[0].payment_request); assert.ok(invoices2[0].payment_request);
assert.ok(invoices2[0].timestamp); assert.ok(invoices2[0].timestamp);
assert.ok(invoices2[0].expire_time); assert.ok(invoices2[0].expire_time);
assert.equal(invoices2[0].amt, 1); assert.strictEqual(invoices2[0].amt, 1);
for (let inv of invoices2) { for (let inv of invoices2) {
assert.equal(inv.type, 'user_invoice'); assert.strictEqual(inv.type, 'user_invoice');
} }
await lOld.fetchBalance(); await lOld.fetchBalance();
@ -225,11 +225,11 @@ describe('LightningCustodianWallet', () => {
await lOld.fetchBalance(); await lOld.fetchBalance();
await lNew.fetchBalance(); await lNew.fetchBalance();
assert.equal(oldBalance - lOld.balance, 1); assert.strictEqual(oldBalance - lOld.balance, 1);
assert.equal(lNew.balance, 1); assert.strictEqual(lNew.balance, 1);
await lOld.fetchTransactions(); await lOld.fetchTransactions();
assert.equal(lOld.transactions_raw.length, txLen + 1, 'internal invoice should also produce record in payer`s tx list'); assert.strictEqual(lOld.transactions_raw.length, txLen + 1, 'internal invoice should also produce record in payer`s tx list');
let newTx = lOld.transactions_raw.slice().pop(); let newTx = lOld.transactions_raw.slice().pop();
assert.ok(typeof newTx.fee !== 'undefined'); assert.ok(typeof newTx.fee !== 'undefined');
assert.ok(newTx.value); assert.ok(newTx.value);
@ -244,8 +244,8 @@ describe('LightningCustodianWallet', () => {
await lNew.payInvoice(invoice); await lNew.payInvoice(invoice);
await lOld.fetchBalance(); await lOld.fetchBalance();
await lNew.fetchBalance(); await lNew.fetchBalance();
assert.equal(lOld.balance - oldBalance, 1); assert.strictEqual(lOld.balance - oldBalance, 1);
assert.equal(lNew.balance, 0); assert.strictEqual(lNew.balance, 0);
// now, paying same internal invoice. should fail: // now, paying same internal invoice. should fail:
@ -261,16 +261,16 @@ describe('LightningCustodianWallet', () => {
assert.ok(coughtError); assert.ok(coughtError);
await lOld.fetchTransactions(); await lOld.fetchTransactions();
assert.equal(txLen, lOld.transactions_raw.length, 'tx count should not be changed'); assert.strictEqual(txLen, lOld.transactions_raw.length, 'tx count should not be changed');
assert.equal(invLen, (await lNew.getUserInvoices()).length, 'invoices count should not be changed'); assert.strictEqual(invLen, (await lNew.getUserInvoices()).length, 'invoices count should not be changed');
// testing how limiting works: // testing how limiting works:
assert.equal(lNew.user_invoices_raw.length, 1); assert.strictEqual(lNew.user_invoices_raw.length, 1);
await lNew.addInvoice(666, 'test memo 2'); await lNew.addInvoice(666, 'test memo 2');
invoices = await lNew.getUserInvoices(1); invoices = await lNew.getUserInvoices(1);
assert.equal(invoices.length, 2); assert.strictEqual(invoices.length, 2);
assert.equal(invoices[0].amt, 1); assert.strictEqual(invoices[0].amt, 1);
assert.equal(invoices[1].amt, 666); assert.strictEqual(invoices[1].amt, 666);
}); });
it('can pay free amount (tip) invoice', async function() { it('can pay free amount (tip) invoice', async function() {
@ -319,7 +319,7 @@ describe('LightningCustodianWallet', () => {
let decoded = await l2.decodeInvoice(invoice); let decoded = await l2.decodeInvoice(invoice);
assert.ok(decoded.payment_hash); assert.ok(decoded.payment_hash);
assert.ok(decoded.description); assert.ok(decoded.description);
assert.equal(+decoded.num_satoshis, 0); assert.strictEqual(+decoded.num_satoshis, 0);
await l2.checkRouteInvoice(invoice); await l2.checkRouteInvoice(invoice);
@ -342,10 +342,10 @@ describe('LightningCustodianWallet', () => {
} }
await l2.fetchTransactions(); await l2.fetchTransactions();
assert.equal(l2.transactions_raw.length, txLen + 1); assert.strictEqual(l2.transactions_raw.length, txLen + 1);
// transactions became more after paying an invoice // transactions became more after paying an invoice
await l2.fetchBalance(); await l2.fetchBalance();
assert.equal(oldBalance - l2.balance, 3); assert.strictEqual(oldBalance - l2.balance, 3);
}); });
}); });

21
NavigationService.js

@ -0,0 +1,21 @@
import { NavigationActions } from 'react-navigation';
let _navigator;
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
}),
);
}
export default {
navigate,
setTopLevelNavigator,
};

18
android/app/BUCK

@ -8,23 +8,13 @@
# - `buck install -r android/app` - compile, install and run application # - `buck install -r android/app` - compile, install and run application
# #
load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
lib_deps = [] lib_deps = []
for jarfile in glob(['libs/*.jar']): create_aar_targets(glob(["libs/*.aar"]))
name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')]
lib_deps.append(':' + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)
for aarfile in glob(['libs/*.aar']): create_jar_targets(glob(["libs/*.jar"]))
name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')]
lib_deps.append(':' + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
android_library( android_library(
name = "all-libs", name = "all-libs",

97
android/app/app.iml

@ -23,12 +23,11 @@
</facet> </facet>
</component> </component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7"> <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> <output url="file://$MODULE_DIR$/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" /> <output-test url="file://$MODULE_DIR$/build/intermediates/javac/debugUnitTest/compileDebugUnitTestJavaWithJavac/classes" />
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
@ -36,7 +35,6 @@
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
@ -85,18 +83,47 @@
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/generated/not_namespaced_r_class_sources" />
<excludeFolder url="file://$MODULE_DIR$/build/generated/source/r" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/annotation_processor_list" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/apk_list" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/build-info" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-libraries" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-manifest" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-manifest" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/checkDebugClasspath" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/checkReleaseClasspath" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/compatible_screen_manifest" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-apk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant_run_main_apk_resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant_run_merged_manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant_run_split_apk_resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaPrecompile" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaPrecompile" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javac" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/linked_res_for_bundle" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifest-checker" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/merged_assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/merged_manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/module_bundle" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/prebuild" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/prebuild" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/processed_res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shader_assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/split-apk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/split_list" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/splits-support" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/splits-support" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
@ -105,59 +132,53 @@
</content> </content>
<orderEntry type="jdk" jdkName="Android API 27 Platform" jdkType="Android SDK" /> <orderEntry type="jdk" jdkName="Android API 27 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:fbcore-1.10.0" level="project" /> <orderEntry type="library" name="Gradle: org.webkit:android-jsc:r174650@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:fresco-1.10.0" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:support-vector-drawable:27.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:drawee-1.10.0" level="project" /> <orderEntry type="library" name="Gradle: com.facebook.fresco:fresco:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.react:react-native:0.57.8@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-core:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-core-utils:27.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:runtime:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:fbcore:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:common:1.1.0@jar" level="project" /> <orderEntry type="library" name="Gradle: android.arch.lifecycle:common:1.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-okhttp3-1.10.0" level="project" /> <orderEntry type="library" name="Gradle: android.arch.lifecycle:viewmodel:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-analytics-impl-16.0.5" level="project" /> <orderEntry type="library" name="Gradle: com.facebook.fresco:drawee:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-annotations:27.1.1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-tasks-16.0.1" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:animated-vector-drawable-27.1.1" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp:3.11.0@jar" level="project" /> <orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp:3.11.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-ads-identifier-16.0.0" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-compat-27.1.1" level="project" />
<orderEntry type="library" name="Gradle: com.parse.bolts:bolts-tasks:1.4.0@jar" level="project" /> <orderEntry type="library" name="Gradle: com.parse.bolts:bolts-tasks:1.4.0@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:viewmodel-1.1.0" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:exifinterface:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-measurement-base-16.0.4" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:support-v4:27.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-analytics-16.0.5" level="project" /> <orderEntry type="library" name="Gradle: android.arch.core:runtime:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-stats-16.0.1" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp-urlconnection:3.11.0@jar" level="project" /> <orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp-urlconnection:3.11.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-vector-drawable-27.1.1" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:animated-vector-drawable:27.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-core-ui-27.1.1" level="project" /> <orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-okhttp3:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-basement-16.0.1" level="project" /> <orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-base:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-media-compat-26.1.0" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-core-utils-27.1.1" level="project" />
<orderEntry type="library" name="Gradle: org.webkit:android-jsc-r174650" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-v4-26.1.0" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-tagmanager-v4-impl-16.0.5" level="project" />
<orderEntry type="library" name="Gradle: io.sentry:sentry:1.7.5@jar" level="project" /> <orderEntry type="library" name="Gradle: io.sentry:sentry:1.7.5@jar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-base-16.0.1" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okio:okio:1.14.0@jar" level="project" /> <orderEntry type="library" name="Gradle: com.squareup.okio:okio:1.14.0@jar" level="project" />
<orderEntry type="library" name="Gradle: javax.inject:javax.inject:1@jar" level="project" /> <orderEntry type="library" name="Gradle: javax.inject:javax.inject:1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-fragment-27.1.1" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:support-core-ui:27.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.core:runtime-1.1.0" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:support-compat:27.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.soloader:soloader-0.5.1" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.react:react-native-0.57.5" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.infer.annotation:infer-annotation:0.11.2@jar" level="project" /> <orderEntry type="library" name="Gradle: com.facebook.infer.annotation:infer-annotation:0.11.2@jar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: org.slf4j:slf4j-api:1.7.24@jar" level="project" /> <orderEntry type="library" name="Gradle: org.slf4j:slf4j-api:1.7.24@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:appcompat-v7-27.1.1" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:support-media-compat:27.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.code.findbugs:jsr305:3.0.2@jar" level="project" /> <orderEntry type="library" name="Gradle: com.google.code.findbugs:jsr305:3.0.2@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-core-1.1.0" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:support-fragment:27.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: io.sentry:sentry-android:1.7.5@jar" level="project" /> <orderEntry type="library" name="Gradle: io.sentry:sentry-android:1.7.5@jar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-1.10.0" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:support-annotations:28.0.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:appcompat-v7:27.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.core:common:1.1.0@jar" level="project" /> <orderEntry type="library" name="Gradle: android.arch.core:common:1.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.fasterxml.jackson.core:jackson-core:2.8.7@jar" level="project" /> <orderEntry type="library" name="Gradle: com.fasterxml.jackson.core:jackson-core:2.8.7@jar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-base-1.10.0" level="project" /> <orderEntry type="library" name="Gradle: com.facebook.soloader:soloader:0.5.1@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:runtime-1.1.0" level="project" /> <orderEntry type="module" module-name="react-native-webview" />
<orderEntry type="module" module-name="react-native-linear-gradient" />
<orderEntry type="module" module-name="react-native-svg" />
<orderEntry type="module" module-name="react-native-sentry" /> <orderEntry type="module" module-name="react-native-sentry" />
<orderEntry type="module" module-name="react-native-google-analytics-bridge" /> <orderEntry type="module" module-name="react-native-google-analytics-bridge" />
<orderEntry type="module" module-name="react-native-haptic-feedback" /> <orderEntry type="module" module-name="react-native-haptic-feedback" />
<orderEntry type="module" module-name="react-native-gesture-handler" /> <orderEntry type="module" module-name="react-native-gesture-handler" />
<orderEntry type="module" module-name="react-native-fs" />
<orderEntry type="module" module-name="react-native-prompt-android" /> <orderEntry type="module" module-name="react-native-prompt-android" />
<orderEntry type="module" module-name="react-native-linear-gradient" />
<orderEntry type="module" module-name="react-native-vector-icons" /> <orderEntry type="module" module-name="react-native-vector-icons" />
<orderEntry type="module" module-name="react-native-svg" />
<orderEntry type="module" module-name="react-native-device-info" /> <orderEntry type="module" module-name="react-native-device-info" />
<orderEntry type="module" module-name="react-native-randombytes" /> <orderEntry type="module" module-name="react-native-randombytes" />
<orderEntry type="module" module-name="react-native-camera" /> <orderEntry type="module" module-name="react-native-camera" />

49
android/app/build.gradle

@ -77,7 +77,6 @@ project.ext.react = [
] ]
apply from: "../../node_modules/react-native/react.gradle" apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-sentry/sentry.gradle"
/** /**
* Set this to true to create two separate APKs instead of one: * Set this to true to create two separate APKs instead of one:
@ -102,22 +101,12 @@ android {
applicationId "io.bluewallet.bluewallet" applicationId "io.bluewallet.bluewallet"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 18 versionCode 1
versionName "3.6.0" versionName "3.7.0"
ndk { ndk {
abiFilters "armeabi-v7a", "x86" abiFilters "armeabi-v7a", "x86"
} }
} }
signingConfigs {
release {
if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
}
}
}
splits { splits {
abi { abi {
reset() reset()
@ -130,7 +119,6 @@ android {
release { release {
minifyEnabled enableProguardInReleaseBuilds minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
signingConfig signingConfigs.release
} }
} }
// applicationVariants are e.g. debug, release // applicationVariants are e.g. debug, release
@ -138,7 +126,7 @@ android {
variant.outputs.each { output -> variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here: // For each separate APK per architecture, set a unique version code as described here:
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
def versionCodes = ["armeabi-v7a":1, "x86":2] def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3]
def abi = output.getFilter(OutputFile.ABI) def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride = output.versionCodeOverride =
@ -149,22 +137,21 @@ android {
} }
dependencies { dependencies {
compile project(':react-native-webview') implementation project(':@remobile_react-native-qrcode-local-image')
compile project(':react-native-camera') implementation project(':react-native-image-picker')
compile project(':react-native-fs') implementation project(':react-native-webview')
compile project(':react-native-gesture-handler') implementation project(':react-native-svg')
compile project(':react-native-vector-icons') implementation project(':react-native-vector-icons')
compile project(':react-native-svg') implementation project(':react-native-sentry')
compile project(':react-native-sentry') implementation project(':react-native-randombytes')
compile project(':react-native-randombytes') implementation project(':react-native-prompt-android')
compile project(':react-native-prompt-android') implementation project(':react-native-linear-gradient')
compile project(':react-native-linear-gradient') implementation project(':react-native-haptic-feedback')
compile project(':react-native-haptic-feedback') implementation project(':react-native-google-analytics-bridge')
compile project(':react-native-google-analytics-bridge') implementation project(':react-native-gesture-handler')
compile project(':react-native-device-info') implementation project(':react-native-fs')
implementation (project(':react-native-camera')) { implementation project(':react-native-device-info')
exclude group: "com.android.support" implementation project(':react-native-camera')
}
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation "com.facebook.react:react-native:+" // From node_modules implementation "com.facebook.react:react-native:+" // From node_modules

19
android/app/build_defs.bzl

@ -0,0 +1,19 @@
"""Helper definitions to glob .aar and .jar targets"""
def create_aar_targets(aarfiles):
for aarfile in aarfiles:
name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
lib_deps.append(":" + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
def create_jar_targets(jarfiles):
for jarfile in jarfiles:
name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
lib_deps.append(":" + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)

11
android/app/src/main/AndroidManifest.xml

@ -3,12 +3,12 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA"/>
<application <application
android:name=".MainApplication" android:name=".MainApplication"
android:label="@string/app_name" android:label="@string/app_name"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false" android:allowBackup="false"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity <activity
@ -20,13 +20,6 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<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>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application> </application>

BIN
android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf

Binary file not shown.

BIN
android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf

Binary file not shown.

BIN
android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf

Binary file not shown.

71
android/app/src/main/java/com/bluewallet/MainApplication.java

@ -1,71 +0,0 @@
package io.bluewallet.bluewallet;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.reactnativecommunity.webview.RNCWebViewPackage;
import com.oblador.vectoricons.VectorIconsPackage;
import com.horcrux.svg.SvgPackage;
import io.sentry.RNSentryPackage;
import com.bitgo.randombytes.RandomBytesPackage;
import im.shimo.react.prompt.RNPromptPackage;
import com.BV.LinearGradient.LinearGradientPackage;
import com.mkuczera.RNReactNativeHapticFeedbackPackage;
import com.idehub.GoogleAnalyticsBridge.GoogleAnalyticsBridgePackage;
import com.learnium.RNDeviceInfo.RNDeviceInfo;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import org.reactnative.camera.RNCameraPackage;
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
import com.rnfs.RNFSPackage;
import java.util.Arrays;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNCWebViewPackage(),
new RNFSPackage() ,
new VectorIconsPackage(),
new SvgPackage(),
new RNSentryPackage(),
new RandomBytesPackage(),
new RNPromptPackage(),
new LinearGradientPackage(),
new RNReactNativeHapticFeedbackPackage(),
new GoogleAnalyticsBridgePackage(),
new RNDeviceInfo(),
new RNCameraPackage(),
new RNGestureHandlerPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}

0
android/app/src/main/java/com/bluewallet/MainActivity.java → android/app/src/main/java/io/bluewallet/bluewallet/MainActivity.java

1
android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java

@ -1 +0,0 @@
../../../../../../../.././android/app/src/main/java/com/bluewallet/MainApplication.java

92
android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java

@ -0,0 +1,92 @@
package io.bluewallet.bluewallet;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.remobile.qrcodeLocalImage.RCTQRCodeLocalImagePackage;
import com.imagepicker.ImagePickerPackage;
import com.reactnativecommunity.webview.RNCWebViewPackage;
import io.sentry.RNSentryPackage;
import com.bitgo.randombytes.RandomBytesPackage;
import im.shimo.react.prompt.RNPromptPackage;
import com.BV.LinearGradient.LinearGradientPackage;
import com.mkuczera.RNReactNativeHapticFeedbackPackage;
import com.idehub.GoogleAnalyticsBridge.GoogleAnalyticsBridgePackage;
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
import com.rnfs.RNFSPackage;
import com.learnium.RNDeviceInfo.RNDeviceInfo;
import org.reactnative.camera.RNCameraPackage;
import io.sentry.RNSentryPackage;
import com.bitgo.randombytes.RandomBytesPackage;
import im.shimo.react.prompt.RNPromptPackage;
import com.BV.LinearGradient.LinearGradientPackage;
import com.mkuczera.RNReactNativeHapticFeedbackPackage;
import com.idehub.GoogleAnalyticsBridge.GoogleAnalyticsBridgePackage;
import com.reactnativecommunity.webview.RNCWebViewPackage;
import com.oblador.vectoricons.VectorIconsPackage;
import com.horcrux.svg.SvgPackage;
import io.sentry.RNSentryPackage;
import com.bitgo.randombytes.RandomBytesPackage;
import im.shimo.react.prompt.RNPromptPackage;
import com.BV.LinearGradient.LinearGradientPackage;
import com.mkuczera.RNReactNativeHapticFeedbackPackage;
import com.idehub.GoogleAnalyticsBridge.GoogleAnalyticsBridgePackage;
import com.learnium.RNDeviceInfo.RNDeviceInfo;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import org.reactnative.camera.RNCameraPackage;
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
import com.rnfs.RNFSPackage;
import java.util.Arrays;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RCTQRCodeLocalImagePackage(),
new ImagePickerPackage(),
new RNCWebViewPackage(),
new RNSentryPackage(),
new RandomBytesPackage(),
new RNPromptPackage(),
new RNReactNativeHapticFeedbackPackage(),
new GoogleAnalyticsBridgePackage(),
new RNDeviceInfo(),
new LinearGradientPackage(),
new RNFSPackage() ,
new VectorIconsPackage(),
new SvgPackage(),
new RNCameraPackage(),
new RNGestureHandlerPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}

2
android/app/src/main/res/values/strings.xml

@ -1,3 +1,3 @@
<resources> <resources>
<string name="app_name">Blue Wallet</string> <string name="app_name">BlueWallet</string>
</resources> </resources>

23
android/build.gradle

@ -2,18 +2,18 @@
buildscript { buildscript {
ext { ext {
buildToolsVersion = "27.0.3" buildToolsVersion = "28.0.2"
minSdkVersion = 16 minSdkVersion = 16
compileSdkVersion = 27 compileSdkVersion = 28
targetSdkVersion = 26 targetSdkVersion = 27
supportLibVersion = "27.1.1" supportLibVersion = "28.0.0"
} }
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.1.4' classpath 'com.android.tools.build:gradle:3.2.1'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
@ -34,17 +34,6 @@ allprojects {
task wrapper(type: Wrapper) { task wrapper(type: Wrapper) {
gradleVersion = '4.4' gradleVersion = '4.7'
distributionUrl = distributionUrl.replace("bin", "all") distributionUrl = distributionUrl.replace("bin", "all")
} }
subprojects {
project.configurations.all {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'com.android.support'
&& !details.requested.name.contains('multidex') ) {
details.useVersion "26.1.0"
}
}
}
}

93
android/build/intermediates/lint-cache/maven.google/com/android/support/group-index.xml

@ -1,93 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<com.android.support>
<support-compat versions="24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<leanback-v17 versions="21.0.0,21.0.2,21.0.3,22.0.0,22.1.0,22.1.1,22.2.0,22.2.1,23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<recommendation versions="23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-tv-provider versions="26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-vector-drawable versions="23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<recyclerview-v7 versions="21.0.0,21.0.2,21.0.3,22.0.0,22.1.0,22.1.1,22.2.0,22.2.1,23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<preference-leanback-v17 versions="23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<preference-v14 versions="23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<percent versions="22.2.0,23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-media-compat versions="24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<cardview-v7 versions="21.0.0,21.0.2,21.0.3,22.0.0,22.1.0,22.1.1,22.2.0,22.2.1,23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<wearable versions="26.0.0-alpha1"/>
<exifinterface versions="25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-annotations versions="19.1.0,20.0.0,21.0.0,21.0.2,21.0.3,22.0.0,22.1.0,22.1.1,22.2.0,22.2.1,23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<appcompat-v7 versions="18.0.0,19.0.0,19.0.1,19.1.0,20.0.0,21.0.0,21.0.2,21.0.3,22.0.0,22.1.0,22.1.1,22.2.0,22.2.1,23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<palette-v7 versions="21.0.0,21.0.2,21.0.3,22.0.0,22.1.0,22.1.1,22.2.0,22.2.1,23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<multidex-instrumentation versions="1.0.0,1.0.1,1.0.2,1.0.3"/>
<multidex versions="1.0.0,1.0.1,1.0.2,1.0.3"/>
<mediarouter-v7 versions="18.0.0,19.0.0,19.0.1,19.1.0,20.0.0,21.0.0,21.0.2,21.0.3,22.0.0,22.1.0,22.1.1,22.2.0,22.2.1,23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-alpha4,28.0.0-alpha5,28.0.0-beta01,28.0.0"/>
<preference-v7 versions="23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-dynamic-animation versions="25.3.0,25.3.1,25.4.0,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-fragment versions="24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<design versions="22.2.0,22.2.1,23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<transition versions="24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<customtabs versions="23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-core-ui versions="24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<gridlayout-v7 versions="13.0.0,18.0.0,19.0.0,19.0.1,19.1.0,20.0.0,21.0.0,21.0.2,21.0.3,22.0.0,22.1.0,22.1.1,22.2.0,22.2.1,23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<animated-vector-drawable versions="23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-core-utils versions="24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-v13 versions="13.0.0,18.0.0,19.0.0,19.0.1,19.1.0,20.0.0,21.0.0,21.0.2,21.0.3,22.0.0,22.1.0,22.1.1,22.2.0,22.2.1,23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<instantvideo versions="26.0.0-alpha1"/>
<support-v4 versions="13.0.0,18.0.0,19.0.0,19.0.1,19.1.0,20.0.0,21.0.0,21.0.2,21.0.3,22.0.0,22.1.0,22.1.1,22.2.0,22.2.1,23.0.0,23.0.1,23.1.0,23.1.1,23.2.0,23.2.1,23.3.0,23.4.0,24.0.0-alpha1,24.0.0-alpha2,24.0.0-beta1,24.0.0,24.1.0,24.1.1,24.2.0,24.2.1,25.0.0,25.0.1,25.1.0,25.1.1,25.2.0,25.3.0,25.3.1,25.4.0,26.0.0-alpha1,26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-emoji versions="26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<wear versions="26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-emoji-appcompat versions="26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-emoji-bundled versions="26.0.0-beta1,26.0.0-beta2,26.0.0,26.0.1,26.0.2,26.1.0,27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<support-content versions="27.0.0,27.0.1,27.0.2,27.1.0,27.1.1,28.0.0-alpha1"/>
<design-bottomnavigation versions="28.0.0-alpha1"/>
<design-button versions="28.0.0-alpha1"/>
<design-circularreveal-cardview versions="28.0.0-alpha1"/>
<design-bottomappbar versions="28.0.0-alpha1"/>
<design-card versions="28.0.0-alpha1"/>
<design-shape versions="28.0.0-alpha1"/>
<design-drawable versions="28.0.0-alpha1"/>
<design-bottomsheet versions="28.0.0-alpha1"/>
<design-floatingactionbutton versions="28.0.0-alpha1"/>
<design-circularreveal-coordinatorlayout versions="28.0.0-alpha1"/>
<design-textfield versions="28.0.0-alpha1"/>
<design-stateful versions="28.0.0-alpha1"/>
<design-circularreveal versions="28.0.0-alpha1"/>
<design-expandable versions="28.0.0-alpha1"/>
<design-navigation versions="28.0.0-alpha1"/>
<design-dialog versions="28.0.0-alpha1"/>
<design-canvas versions="28.0.0-alpha1"/>
<design-tabs versions="28.0.0-alpha1"/>
<design-chip versions="28.0.0-alpha1"/>
<design-snackbar versions="28.0.0-alpha1"/>
<design-theme versions="28.0.0-alpha1"/>
<design-math versions="28.0.0-alpha1"/>
<design-transformation versions="28.0.0-alpha1"/>
<design-widget versions="28.0.0-alpha1"/>
<design-animation versions="28.0.0-alpha1"/>
<design-typography versions="28.0.0-alpha1"/>
<design-color versions="28.0.0-alpha1"/>
<design-internal versions="28.0.0-alpha1"/>
<design-resources versions="28.0.0-alpha1"/>
<design-ripple versions="28.0.0-alpha1"/>
<coordinatorlayout versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<collections versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<slidingpanelayout versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<asynclayoutinflater versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<slices-view versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<recyclerview-selection versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<viewpager versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<cursoradapter versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<localbroadcastmanager versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<heifwriter versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<customview versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<print versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<slices-builders versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<interpolator versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<slices-core versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<loader versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<swiperefreshlayout versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<drawerlayout versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<documentfile versions="28.0.0-alpha1,28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<webkit versions="28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<car versions="28.0.0-alpha3,28.0.0-alpha4,28.0.0-alpha5"/>
<versionedparcelable versions="28.0.0-alpha3,28.0.0-beta01,28.0.0-rc01,28.0.0-rc02,28.0.0"/>
<media2 versions="28.0.0-alpha01,28.0.0-alpha02,28.0.0-alpha03"/>
</com.android.support>

120
android/build/intermediates/lint-cache/maven.google/master-index.xml

@ -1,120 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<com.android.support.constraint/>
<com.android.databinding/>
<com.android.support/>
<com.android.support.test/>
<com.android.support.test.janktesthelper/>
<com.android.support.test.uiautomator/>
<com.android.support.test.espresso/>
<android.arch.persistence.room/>
<android.arch.lifecycle/>
<android.arch.core/>
<com.google.android.instantapps/>
<com.google.android.instantapps.thirdpartycompat/>
<com.android.java.tools.build/>
<com.android.tools/>
<com.android.tools.layoutlib/>
<com.android.tools.ddms/>
<com.android.tools.external.com-intellij/>
<com.android.tools.build/>
<com.android.tools.analytics-library/>
<com.android.tools.internal.build.test/>
<com.android.tools.lint/>
<com.android.tools.external.org-jetbrains/>
<com.android.support.test.espresso.idling/>
<com.android.support.test.services/>
<com.google.firebase/>
<com.google.android.gms/>
<com.google.gms/>
<android.arch.paging/>
<com.crashlytics.sdk.android/>
<io.fabric.sdk.android/>
<android.arch.persistence/>
<com.google.android.wearable/>
<com.google.android.support/>
<com.android.installreferrer/>
<com.google.ar/>
<androidx.core/>
<com.google.android.things/>
<com.android.tools.build.jetifier/>
<tools.base.build-system.debug/>
<androidx.databinding/>
<androidx.constraintlayout/>
<org.chromium.net/>
<com.google.android.play/>
<androidx.multidex/>
<com.google.android.material/>
<androidx.test.services/>
<androidx.test.janktesthelper/>
<androidx.test/>
<androidx.test.espresso/>
<androidx.test.espresso.idling/>
<androidx.test.uiautomator/>
<androidx.room/>
<androidx.paging/>
<androidx.lifecycle/>
<androidx.sqlite/>
<androidx.arch.core/>
<android.arch.work/>
<android.arch.navigation/>
<androidx.mediarouter/>
<androidx.percentlayout/>
<androidx.emoji/>
<androidx.cardview/>
<androidx.preference/>
<androidx.wear/>
<androidx.legacy/>
<androidx.documentfile/>
<androidx.car/>
<androidx.swiperefreshlayout/>
<androidx.leanback/>
<androidx.appcompat/>
<androidx.customview/>
<androidx.gridlayout/>
<androidx.vectordrawable/>
<androidx.heifwriter/>
<androidx.transition/>
<androidx.print/>
<androidx.viewpager/>
<androidx.annotation/>
<androidx.exifinterface/>
<androidx.dynamicanimation/>
<androidx.browser/>
<androidx.localbroadcastmanager/>
<androidx.asynclayoutinflater/>
<androidx.contentpager/>
<androidx.slidingpanelayout/>
<androidx.cursoradapter/>
<androidx.media/>
<androidx.loader/>
<androidx.interpolator/>
<androidx.coordinatorlayout/>
<androidx.fragment/>
<androidx.tvprovider/>
<androidx.slice/>
<androidx.collection/>
<androidx.recommendation/>
<androidx.drawerlayout/>
<androidx.recyclerview/>
<androidx.webkit/>
<androidx.palette/>
<com.google.ar.sceneform/>
<com.google.ar.sceneform.ux/>
<androidx.test.ext/>
<com.google.android.ads.consent/>
<androidx.versionedparcelable/>
<androidx.media2/>
<com.google.ads.afsn/>
<com.google.android.ads/>
<androidx.biometric/>
<androidx.concurrent/>
<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>

2
android/gradle/wrapper/gradle-wrapper.properties

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip

2
android/metadata/en-US/full_description.txt

@ -1,6 +1,6 @@
Store, send and receive bitcoin with the wallet focus on security and simplicity. Store, send and receive bitcoin with the wallet focus on security and simplicity.
On Blue Wallet you own you private keys. On BlueWallet you own you private keys.
You can instantly transact with anyone in the world and transform the financial system right from your pocket. You can instantly transact with anyone in the world and transform the financial system right from your pocket.

18
android/settings.gradle

@ -1,16 +1,14 @@
rootProject.name = 'BlueWallet' rootProject.name = 'BlueWallet'
include ':@remobile_react-native-qrcode-local-image'
project(':@remobile_react-native-qrcode-local-image').projectDir = new File(rootProject.projectDir, '../node_modules/@remobile/react-native-qrcode-local-image/android')
include ':react-native-image-picker'
project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')
include ':react-native-webview' include ':react-native-webview'
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android') project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
include ':react-native-camera'
project(':react-native-camera').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-camera/android')
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')
include ':react-native-gesture-handler'
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':react-native-svg' include ':react-native-svg'
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android') project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':react-native-sentry' include ':react-native-sentry'
project(':react-native-sentry').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sentry/android') project(':react-native-sentry').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sentry/android')
include ':react-native-randombytes' include ':react-native-randombytes'
@ -23,6 +21,10 @@ include ':react-native-haptic-feedback'
project(':react-native-haptic-feedback').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-haptic-feedback/android') project(':react-native-haptic-feedback').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-haptic-feedback/android')
include ':react-native-google-analytics-bridge' include ':react-native-google-analytics-bridge'
project(':react-native-google-analytics-bridge').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-google-analytics-bridge/android') project(':react-native-google-analytics-bridge').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-google-analytics-bridge/android')
include ':react-native-gesture-handler'
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')
include ':react-native-device-info' include ':react-native-device-info'
project(':react-native-device-info').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-device-info/android') project(':react-native-device-info').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-device-info/android')
include ':react-native-camera' include ':react-native-camera'

5
app.json

@ -1,7 +1,4 @@
{ {
"displayName": "Blue Wallet",
"name": "BlueWallet", "name": "BlueWallet",
"ios": { "displayName": "BlueWallet"
"buildNumber": "118"
}
} }

3
babel.config.js

@ -0,0 +1,3 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
};

12
class/lightning-custodian-wallet.js

@ -7,8 +7,8 @@ export class LightningCustodianWallet extends LegacyWallet {
static type = 'lightningCustodianWallet'; static type = 'lightningCustodianWallet';
static typeReadable = 'Lightning'; static typeReadable = 'Lightning';
constructor() { constructor(props) {
super(); super(props);
this.setBaseURI(); // no args to init with default value this.setBaseURI(); // no args to init with default value
this.init(); this.init();
this.refresh_token = ''; this.refresh_token = '';
@ -49,13 +49,13 @@ export class LightningCustodianWallet extends LegacyWallet {
} }
timeToRefreshBalance() { timeToRefreshBalance() {
// lndhub calls are cheap, so why not refresh constantly // only manual refresh for now
return true; return false;
} }
timeToRefreshTransaction() { timeToRefreshTransaction() {
// lndhub calls are cheap, so why not refresh the list constantly // only manual refresh for now
return true; return false;
} }
static fromJson(param) { static fromJson(param) {

79
class/walletGradient.js

@ -0,0 +1,79 @@
import { LegacyWallet } from './legacy-wallet';
import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet';
import { LightningCustodianWallet } from './lightning-custodian-wallet';
import { HDLegacyBreadwalletWallet } from './hd-legacy-breadwallet-wallet';
import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet';
import { WatchOnlyWallet } from './watch-only-wallet';
export default class WalletGradient {
static defaultGradients = ['#65ceef', '#68bbe1'];
static watchOnlyWallet = ['#7d7d7d', '#4a4a4a'];
static legacyWallet = ['#40fad1', '#15be98'];
static hdLegacyP2PKHWallet = ['#e36dfa', '#bd10e0'];
static hdLegacyBreadWallet = ['#fe6381', '#f99c42'];
static hdSegwitP2SHWallet = ['#c65afb', '#9053fe'];
static lightningCustodianWallet = ['#f1be07', '#f79056'];
static createWallet = ['#eef0f4', '#eef0f4'];
static gradientsFor(type) {
let gradient;
switch (type) {
case WatchOnlyWallet.type:
gradient = WalletGradient.watchOnlyWallet;
break;
case LegacyWallet.type:
gradient = WalletGradient.legacyWallet;
break;
case HDLegacyP2PKHWallet.type:
gradient = WalletGradient.hdLegacyP2PKHWallet;
break;
case HDLegacyBreadwalletWallet.type:
gradient = WalletGradient.hdLegacyBreadWallet;
break;
case HDSegwitP2SHWallet.type:
gradient = WalletGradient.hdSegwitP2SHWallet;
break;
case LightningCustodianWallet.type:
gradient = WalletGradient.lightningCustodianWallet;
break;
case 'CreateWallet':
gradient = WalletGradient.createWallet;
break;
default:
gradient = WalletGradient.defaultGradients;
break;
}
return gradient;
}
static headerColorFor(type) {
let gradient;
switch (type) {
case WatchOnlyWallet.type:
gradient = WalletGradient.watchOnlyWallet;
break;
case LegacyWallet.type:
gradient = WalletGradient.legacyWallet;
break;
case HDLegacyP2PKHWallet.type:
gradient = WalletGradient.hdLegacyP2PKHWallet;
break;
case HDLegacyBreadwalletWallet.type:
gradient = WalletGradient.hdLegacyBreadWallet;
break;
case HDSegwitP2SHWallet.type:
gradient = WalletGradient.hdSegwitP2SHWallet;
break;
case LightningCustodianWallet.type:
gradient = WalletGradient.lightningCustodianWallet;
break;
case 'CreateWallet':
gradient = WalletGradient.createWallet;
break;
default:
gradient = WalletGradient.defaultGradients;
break;
}
return gradient[0];
}
}

4
events.js

@ -36,10 +36,6 @@ EV.enum = {
// changed (usually for current wallet) // changed (usually for current wallet)
REMOTE_TRANSACTIONS_COUNT_CHANGED: 'REMOTE_TRANSACTIONS_COUNT_CHANGED', REMOTE_TRANSACTIONS_COUNT_CHANGED: 'REMOTE_TRANSACTIONS_COUNT_CHANGED',
// emitted when QR scanner scanned address that should be used in CREATE TRANSACTION screen
// thus, previous screen (CREATE TRANSACTION screen) will update it's input content
CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS: 'CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS',
// RECEIVE_ADDRESS_CHANGED: 'RECEIVE_ADDRESS_CHANGED', // RECEIVE_ADDRESS_CHANGED: 'RECEIVE_ADDRESS_CHANGED',
}; };

1355
ios/BlueWallet.xcodeproj/project.pbxproj

File diff suppressed because it is too large

2
ios/BlueWallet/AppDelegate.h

@ -1,5 +1,5 @@
/** /**
* Copyright (c) 2015-present, Facebook, Inc. * Copyright (c) Facebook, Inc. and its affiliates.
* *
* This source code is licensed under the MIT license found in the * This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.

17
ios/BlueWallet/AppDelegate.m

@ -1,5 +1,5 @@
/** /**
* Copyright (c) 2015-present, Facebook, Inc. * Copyright (c) Facebook, Inc. and its affiliates.
* *
* This source code is licensed under the MIT license found in the * This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
@ -23,14 +23,12 @@
NSURL *jsCodeLocation; NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"BlueWallet" moduleName:@"BlueWallet"
initialProperties:nil initialProperties:nil
launchOptions:launchOptions]; launchOptions:launchOptions];
rootView.backgroundColor = [UIColor blackColor];
[RNSentry installWithRootView:rootView];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new]; UIViewController *rootViewController = [UIViewController new];
@ -40,11 +38,8 @@ RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
return YES; return YES;
} }
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation return [RCTLinkingManager application:app openURL:url options:options];
{
return [RCTLinkingManager application:application openURL:url
sourceApplication:sourceApplication annotation:annotation];
} }
@end @end

10
ios/BlueWallet/Info.plist

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>en</string> <string>en</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Blue Wallet</string> <string>BlueWallet</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@ -17,7 +17,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.6.0</string> <string>3.7.0</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@ -56,7 +56,7 @@
<key>NSCalendarsUsageDescription</key> <key>NSCalendarsUsageDescription</key>
<string>This alert should not show up as we do not require this data</string> <string>This alert should not show up as we do not require this data</string>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code.</string> <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>
<key>NSLocationWhenInUseUsageDescription</key> <key>NSLocationWhenInUseUsageDescription</key>
<string>This alert should not show up as we do not require this data</string> <string>This alert should not show up as we do not require this data</string>
<key>NSMotionUsageDescription</key> <key>NSMotionUsageDescription</key>
@ -64,9 +64,11 @@
<key>NSPhotoLibraryAddUsageDescription</key> <key>NSPhotoLibraryAddUsageDescription</key>
<string>This alert should not show up as we do not require this data</string> <string>This alert should not show up as we do not require this data</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>This alert should not show up as we do not require this data</string> <string>In order to import an image for scanning, we need your permission to access your photo library.</string>
<key>NSSpeechRecognitionUsageDescription</key> <key>NSSpeechRecognitionUsageDescription</key>
<string>This alert should not show up as we do not require this data</string> <string>This alert should not show up as we do not require this data</string>
<key>NSMicrophoneUsageDescription</key>
<string>This alert should not show up as we do not require this data</string>
<key>UIAppFonts</key> <key>UIAppFonts</key>
<array> <array>
<string>AntDesign.ttf</string> <string>AntDesign.ttf</string>

2
ios/BlueWallet/main.m

@ -1,5 +1,5 @@
/** /**
* Copyright (c) 2015-present, Facebook, Inc. * Copyright (c) Facebook, Inc. and its affiliates.
* *
* This source code is licensed under the MIT license found in the * This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.

2
ios/BlueWalletTests/BlueWalletTests.m

@ -1,5 +1,5 @@
/** /**
* Copyright (c) 2015-present, Facebook, Inc. * Copyright (c) Facebook, Inc. and its affiliates.
* *
* This source code is licensed under the MIT license found in the * This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.

2
ios/BlueWalletTests/Info.plist

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>

2
ios/fastlane/metadata/en-US/description.txt

@ -1,6 +1,6 @@
Store, send and receive bitcoin with the wallet focus on security and simplicity. Store, send and receive bitcoin with the wallet focus on security and simplicity.
On Blue Wallet you own you private keys. A Bitcoin wallet focused on us the users. On BlueWallet you own you private keys. A Bitcoin wallet focused on us the users.
You can instantly transact with anyone in the world and transform the financial system right from your pocket. You can instantly transact with anyone in the world and transform the financial system right from your pocket.

2
ios/fastlane/metadata/es-ES/description.txt

@ -1,6 +1,6 @@
Store, send and receive bitcoin with the wallet focus on security and simplicity. Store, send and receive bitcoin with the wallet focus on security and simplicity.
On Blue Wallet you own you private keys. A Bitcoin wallet focused on us the users. On BlueWallet you own you private keys. A Bitcoin wallet focused on us the users.
You can instantly transact with anyone in the world and transform the financial system right from your pocket. You can instantly transact with anyone in the world and transform the financial system right from your pocket.

2
ios/fastlane/metadata/pt-BR/description.txt

@ -1,6 +1,6 @@
Guardar, enviar e receber bitcoin com uma carteira focada na segurança e simplicidade. 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. Na BlueWallet 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. Você pode instantaneamente transacionar com qualquer pessoa no mundo e transformar o sistema financeiro diretamente do seu bolso.

2
ios/fastlane/metadata/pt-PT/description.txt

@ -1,6 +1,6 @@
Guardar, enviar e receber bitcoin com uma carteira focada na segurança e simplicidade. 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. Na BlueWallet 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. Você pode instantaneamente transacionar com qualquer pessoa no mundo e transformar o sistema financeiro diretamente do seu bolso.

2
loc/cs_CZ.js

@ -12,7 +12,7 @@ module.exports = {
options: 'možnosti', options: 'možnosti',
createBitcoinWallet: 'In order to use a Lightning wallet, a Bitcoin wallet is needed to fund it. Would you like to continue anyway?', createBitcoinWallet: 'In order to use a Lightning wallet, a Bitcoin wallet is needed to fund it. Would you like to continue anyway?',
list: { list: {
app_name: 'Blue Wallet', app_name: 'BlueWallet',
title: 'peněženky', 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', 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', add: 'Přidat peněženku',

2
loc/da_DK.js

@ -12,7 +12,7 @@ module.exports = {
options: 'valgmuligheder', options: 'valgmuligheder',
createBitcoinWallet: 'In order to use a Lightning wallet, a Bitcoin wallet is needed to fund it. Would you like to continue anyway?', createBitcoinWallet: 'In order to use a Lightning wallet, a Bitcoin wallet is needed to fund it. Would you like to continue anyway?',
list: { list: {
app_name: 'Blue Wallet', app_name: 'BlueWallet',
title: 'wallets', title: 'wallets',
header: 'En wallet består af par af hemmelige (private nøgler) og en adresse' + 'som du kan dele med andre for at modtage coins.', header: 'En wallet består af par af hemmelige (private nøgler) og en adresse' + 'som du kan dele med andre for at modtage coins.',
add: 'Tilføj Wallet', add: 'Tilføj Wallet',

2
loc/de_DE.js

@ -13,7 +13,7 @@ module.exports = {
createBitcoinWallet: createBitcoinWallet:
'In order to use a Lightning wallet, a Bitcoin wallet is needed in order to fund it. Please, create or import a Bitcoin wallet.', 'In order to use a Lightning wallet, a Bitcoin wallet is needed in order to fund it. Please, create or import a Bitcoin wallet.',
list: { list: {
app_name: 'Blue Wallet', app_name: 'BlueWallet',
title: 'Wallets', title: 'Wallets',
header: 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.', '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.',

2
loc/en.js

@ -13,7 +13,7 @@ module.exports = {
createBitcoinWallet: createBitcoinWallet:
'You currently do not have a Bitcoin wallet. In order to fund a Lightning wallet, a Bitcoin wallet needs to be created or imported. Would you like to continue anyway?', 'You currently do not have a Bitcoin wallet. In order to fund a Lightning wallet, a Bitcoin wallet needs to be created or imported. Would you like to continue anyway?',
list: { list: {
app_name: 'Blue Wallet', app_name: 'BlueWallet',
title: 'wallets', title: 'wallets',
header: 'A wallet represents a pair of a secret (private key) and an address' + 'you can share to receive coins.', header: 'A wallet represents a pair of a secret (private key) and an address' + 'you can share to receive coins.',
add: 'Add Wallet', add: 'Add Wallet',

2
loc/es.js

@ -13,7 +13,7 @@ module.exports = {
createBitcoinWallet: createBitcoinWallet:
'In order to use a Lightning wallet, a Bitcoin wallet is needed in order to fund it. Would you like to continue anyway?', 'In order to use a Lightning wallet, a Bitcoin wallet is needed in order to fund it. Would you like to continue anyway?',
list: { list: {
app_name: 'Blue Wallet', app_name: 'BlueWallet',
title: 'billeteras', title: 'billeteras',
header: 'Un Monedero esta representado con secreto (clave privada) y una dirección' + 'que puedes compartir para recibir monedas.', header: 'Un Monedero esta representado con secreto (clave privada) y una dirección' + 'que puedes compartir para recibir monedas.',
add: 'Añadir Carterqa', add: 'Añadir Carterqa',

2
loc/fr_FR.js

@ -13,7 +13,7 @@ module.exports = {
createBitcoinWallet: 'In order to use a Lightning wallet, a Bitcoin wallet is needed to fund it. Would you like to continue anyway?', createBitcoinWallet: 'In order to use a Lightning wallet, a Bitcoin wallet is needed to fund it. Would you like to continue anyway?',
list: { list: {
app_name: 'Blue Wallet', app_name: 'BlueWallet',
title: 'portefeuilles', title: 'portefeuilles',
header: header:
'Un portefeuille represente une paire de clées (publique/privée) et une adresse que vous pouvez partager pour recevoir des transactions.', 'Un portefeuille represente une paire de clées (publique/privée) et une adresse que vous pouvez partager pour recevoir des transactions.',

54
loc/hr_HR.js

@ -4,16 +4,16 @@ module.exports = {
enter_password: 'Unesi lozinku', enter_password: 'Unesi lozinku',
bad_password: 'Kriva lozinka, pokušaj ponovo', bad_password: 'Kriva lozinka, pokušaj ponovo',
never: 'nikad', never: 'nikad',
continue: 'Continue', continue: 'Nastavi',
ok: 'OK', ok: 'U redu',
}, },
wallets: { wallets: {
select_wallet: 'Odaberi volet', select_wallet: 'Odaberi volet',
options: 'opcije', options: 'opcije',
list: { list: {
app_name: 'Blue Wallet', app_name: 'BlueWallet',
title: 'Voleti', title: 'Voleti',
header: 'Volet je par privatnog ključa (tajna!) i javne adrese' + 'koju slobodno možete dijeliti kada primate novce.', header: 'Volet je par privatnog ključa (tajna!) i javne adrese ' + 'koju slobodno možete dijeliti kada primate novce.',
add: 'Dodaj volet', add: 'Dodaj volet',
create_a_wallet: 'Stvori novi volet', create_a_wallet: 'Stvori novi volet',
create_a_wallet1: 'Ne košta ništa i možete', create_a_wallet1: 'Ne košta ništa i možete',
@ -33,10 +33,10 @@ module.exports = {
create: 'Stvori', create: 'Stvori',
label_new_segwit: 'Novi SegWit', label_new_segwit: 'Novi SegWit',
label_new_lightning: 'Novi Lightning', label_new_lightning: 'Novi Lightning',
wallet_name: 'ime voleta', wallet_name: 'ime voleta:',
wallet_type: 'tip', wallet_type: 'tip:',
or: 'ili', or: 'ili',
import_wallet: 'Unesi volet', import_wallet: 'Unesi vanjski volet',
imported: 'Unešeno', imported: 'Unešeno',
coming_soon: 'Dolazi uskoro', coming_soon: 'Dolazi uskoro',
lightning: 'Lightning', lightning: 'Lightning',
@ -53,10 +53,10 @@ module.exports = {
yes_delete: 'Da, briši', yes_delete: 'Da, briši',
no_cancel: 'Ne, otiaži', no_cancel: 'Ne, otiaži',
delete: 'Obriši', delete: 'Obriši',
save: 'Pohrani', save: 'Spremi',
delete_this_wallet: 'Obriši ovaj volet', delete_this_wallet: 'Obriši ovaj volet',
export_backup: 'Izvoz / bekap', export_backup: 'Izvoz / bekap',
buy_bitcoin: 'Kupi Bitcoin', buy_bitcoin: 'Kupovina Bitcoina',
show_xpub: 'Prikaži voletov XPUB', show_xpub: 'Prikaži voletov XPUB',
}, },
export: { export: {
@ -147,22 +147,22 @@ module.exports = {
satoshi_per_byte: 'Satoshi / byte', satoshi_per_byte: 'Satoshi / byte',
memo: 'Bilješka', memo: 'Bilješka',
broadcast: 'Objavi', broadcast: 'Objavi',
not_enough_fee: 'Trošak slanja je premal. Povećaj ga.', not_enough_fee: 'Trošak slanja je premalen. Povećaj ga.',
}, },
}, },
receive: { receive: {
header: 'Primanje', header: 'Primi',
details: { details: {
title: 'Pokaži ovu adresu platitelju', title: 'Pokaži ovu adresu platitelju',
share: 'pokaži', share: 'podijeli',
copiedToClipboard: 'Kopirano u međuspremnik.', copiedToClipboard: 'Kopirano u međuspremnik.',
label: 'Opis', label: 'Opis',
create: 'Create', create: 'Stvori',
setAmount: 'Odredi iznos za primiti', setAmount: 'Odredi iznos za primiti',
}, },
}, },
buyBitcoin: { buyBitcoin: {
header: 'Kupi Bitcoin', header: 'Kupovina Bitcoina',
tap_your_address: 'Klikni na adresu za spremanje u međuspremnik:', tap_your_address: 'Klikni na adresu za spremanje u međuspremnik:',
copied: 'Spremljeno u međuspremnik!', copied: 'Spremljeno u međuspremnik!',
}, },
@ -176,26 +176,26 @@ module.exports = {
retype_password: 'Ponovi lozinku', retype_password: 'Ponovi lozinku',
passwords_do_not_match: 'Lozinke su različite', passwords_do_not_match: 'Lozinke su različite',
encrypt_storage: 'Kriptiraj spremnik', encrypt_storage: 'Kriptiraj spremnik',
lightning_settings: 'Lightning settings', lightning_settings: 'Lightning postavke',
lightning_settings_explain: lightning_settings_explain:
'To connect to your own LND node please install LndHub' + 'Za spajanje na tvoj vlastiti LND čvor trebaš instalirati LndHub' +
' and put its URL here in settings. Leave blank to use default ' + ' i upisati njegov URL ovdje. Ostavi prazno za standardni ' +
'ndHub\n (lndhub.io)', 'ndHub\n (lndhub.io)',
save: 'save', save: 'Spremi',
about: 'Iznos', about: 'Informacije',
language: 'Jezik', language: 'Jezik',
currency: 'Valuta', currency: 'Valuta',
}, },
plausibledeniability: { plausibledeniability: {
title: 'Fejk volet', title: 'Fejk volet',
help: help:
'U iznimnim okolnostima netko gadan (pa još ako drži oklagiju) te' + 'Pazi. Netko gadan te može u iznimnim okolnostima (pljačka, prijevremeni izbori, itd.) ' +
'može neljubazno pritisnuti da mu otkriješ lozinku za ovaj volet.' + 'brutalno pritisnuti da mu otkriješ lozinku za svoj volet. ' +
'BlueWallet ti čuva leđa buraz. Nemaš brige. Gledaj.' + 'BlueWallet ti čuva leđa buraz. Nemaš brige. Gledaj, ' +
'Stvoriti ćemo dupli volet sa drugačijom lozinkom. Haha, žišku?' + 'stvoriti ćemo fejk volet sa drugačijom lozinkom. Haha, žišku? ' +
'Pa kad se ovaj počne pjeniti a ti vidiš da je vrag odnio šalu' + 'Pa kad se ovaj počne pjeniti, a ti vidiš da je vrag odnio šalu, ' +
'ti mu podvali ovaj drugi volet. Eto mu ga. Nek si cucla.', 'samo mu podvali lozinku za ovaj drugi volet. Eto mu ga. Nek si cucla. ',
help2: 'Novi spremnik će biti posve funkcionalan, možeš pohraniti koliko' + 'misliš da je potrebno da izgleda uvjerljivo.', help2: 'Novi spremnik će biti posve funkcionalan, možeš pohraniti koliko ' + 'misliš da je potrebno da izgleda uvjerljivo.',
create_fake_storage: 'Stvori fejk enkriptirani spremnik', create_fake_storage: 'Stvori fejk enkriptirani spremnik',
go_back: 'Povratak', go_back: 'Povratak',
create_password: 'Unesi lozinku', create_password: 'Unesi lozinku',
@ -212,6 +212,6 @@ module.exports = {
refill: 'Dopuni', refill: 'Dopuni',
withdraw: 'Isprazni', withdraw: 'Isprazni',
expired: 'Isteklo', expired: 'Isteklo',
sameWalletAsInvoiceError: 'Ne možeš platiti račun s istim voletom s kojim si račun stvorio, ono.', sameWalletAsInvoiceError: 'Buraz! Ne možeš platiti račun s istim voletom s kojim si račun stvorio, ono.',
}, },
}; };

7
loc/index.js

@ -156,6 +156,13 @@ strings.transactionTimeToReadable = time => {
return dayjs(time).fromNow(); return dayjs(time).fromNow();
}; };
strings.transactionTimeToReadableToFuture = time => {
if (time === 0) {
return strings._.never;
}
return dayjs(time).toNow();
};
function removeTrailingZeros(value) { function removeTrailingZeros(value) {
value = value.toString(); value = value.toString();

2
loc/nl_NL.js

@ -13,7 +13,7 @@ module.exports = {
createBitcoinWallet: createBitcoinWallet:
'Om een Lightning-portemonnee te kunnen gebruiken, is een Bitcoin-portemonnee nodig om deze te financieren. Wil je toch doorgaan?', 'Om een Lightning-portemonnee te kunnen gebruiken, is een Bitcoin-portemonnee nodig om deze te financieren. Wil je toch doorgaan?',
list: { list: {
app_name: 'Blue Wallet', app_name: 'BlueWallet',
title: 'portemonnees', title: 'portemonnees',
header: 'Een portemonnee vertegenwoordigt een geheime (privésleutel) en een adres' + 'dat u kunt delen om munten te ontvangen.', header: 'Een portemonnee vertegenwoordigt een geheime (privésleutel) en een adres' + 'dat u kunt delen om munten te ontvangen.',
add: 'Portemonnee toevoegen', add: 'Portemonnee toevoegen',

2
loc/pt_BR.js

@ -14,7 +14,7 @@ module.exports = {
list: { list: {
tabBarLabel: 'Carteiras', tabBarLabel: 'Carteiras',
app_name: 'Blue Wallet', app_name: 'BlueWallet',
title: 'carteiras', title: 'carteiras',
header: 'Uma carteira representa um par composto de uma chave privada e um endereço que você pode .', header: 'Uma carteira representa um par composto de uma chave privada e um endereço que você pode .',
add: 'adicionar wallet', add: 'adicionar wallet',

2
loc/pt_PT.js

@ -13,7 +13,7 @@ module.exports = {
createBitcoinWallet: 'In order to use a Lightning wallet, a Bitcoin wallet is needed to fund it. Would you like to continue anyway?', createBitcoinWallet: 'In order to use a Lightning wallet, a Bitcoin wallet is needed to fund it. Would you like to continue anyway?',
list: { list: {
app_name: 'Blue Wallet', app_name: 'BlueWallet',
title: 'wallets', title: 'wallets',
header: 'Uma wallet representa um par entre um segredo (chave privada) e um endereço' + 'que pode partilhar para receber Bitcoin.', header: 'Uma wallet representa um par entre um segredo (chave privada) e um endereço' + 'que pode partilhar para receber Bitcoin.',
add: 'adicionar wallet', add: 'adicionar wallet',

2
models/fiatUnit.js

@ -1,6 +1,7 @@
export const FiatUnit = Object.freeze({ export const FiatUnit = Object.freeze({
USD: { endPointKey: 'USD', symbol: '$', locale: 'en-US' }, USD: { endPointKey: 'USD', symbol: '$', locale: 'en-US' },
AUD: { endPointKey: 'AUD', symbol: '$', locale: 'en-AU' }, AUD: { endPointKey: 'AUD', symbol: '$', locale: 'en-AU' },
BRL: { endPointKey: 'BRL', symbol: 'R$', locale: 'pt-BR' },
CAD: { endPointKey: 'CAD', symbol: '$', locale: 'en-CA' }, CAD: { endPointKey: 'CAD', symbol: '$', locale: 'en-CA' },
CZK: { endPointKey: 'CZK', symbol: 'Kč', locale: 'cs-CZ' }, CZK: { endPointKey: 'CZK', symbol: 'Kč', locale: 'cs-CZ' },
CNY: { endPointKey: 'CNY', symbol: '¥', locale: 'zh-CN' }, CNY: { endPointKey: 'CNY', symbol: '¥', locale: 'zh-CN' },
@ -9,6 +10,7 @@ export const FiatUnit = Object.freeze({
HRK: { endPointKey: 'HRK', symbol: 'HRK', locale: 'hr-HR' }, HRK: { endPointKey: 'HRK', symbol: 'HRK', locale: 'hr-HR' },
INR: { endPointKey: 'INR', symbol: '₹', locale: 'hi-HN' }, INR: { endPointKey: 'INR', symbol: '₹', locale: 'hi-HN' },
JPY: { endPointKey: 'JPY', symbol: '¥', locale: 'ja-JP' }, JPY: { endPointKey: 'JPY', symbol: '¥', locale: 'ja-JP' },
MXN: { endPointKey: 'MXN', symbol: '$', locale: 'es-MX' },
PLN: { endPointKey: 'PLN', symbol: 'zł', locale: 'pl-PL' }, PLN: { endPointKey: 'PLN', symbol: 'zł', locale: 'pl-PL' },
RUB: { endPointKey: 'RUB', symbol: '₽', locale: 'ru-RU' }, RUB: { endPointKey: 'RUB', symbol: '₽', locale: 'ru-RU' },
SGD: { endPointKey: 'SGD', symbol: 'S$', locale: 'zh-SG' }, SGD: { endPointKey: 'SGD', symbol: 'S$', locale: 'zh-SG' },

5469
package-lock.json

File diff suppressed because it is too large

59
package.json

@ -1,24 +1,24 @@
{ {
"name": "BlueWallet", "name": "BlueWallet",
"version": "3.6.0", "version": "3.7.0",
"devDependencies": { "devDependencies": {
"babel-eslint": "^8.2.6", "babel-eslint": "^10.0.1",
"babel-jest": "23.6.0", "babel-jest": "^24.0.0",
"eslint": "^4.19.1", "eslint": "^5.12.1",
"eslint-plugin-babel": "^4.1.2", "eslint-plugin-babel": "^5.3.0",
"eslint-plugin-import": "^2.14.0", "eslint-plugin-import": "^2.15.0",
"eslint-plugin-node": "^6.0.1", "eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^3.8.0", "eslint-plugin-promise": "^4.0.1",
"eslint-plugin-react": "^7.12.3", "eslint-plugin-react": "^7.12.3",
"jest": "23.6.0", "jest": "^24.0.0",
"metro-react-native-babel-preset": "^0.49.1", "metro-react-native-babel-preset": "^0.51.1",
"prettier-eslint-cli": "^4.7.1", "prettier-eslint-cli": "^4.7.1",
"react-test-renderer": "^16.7.0", "react-test-renderer": "^16.7.0",
"rn-nodeify": "github:tradle/rn-nodeify" "rn-nodeify": "github:tradle/rn-nodeify"
}, },
"scripts": { "scripts": {
"prepare": "./patches/fix_mangle.sh; git apply patches/minifier.js.patch; git apply patches/minify.js.patch; git apply patches/transaction_builder.js.patch; git apply ./patches/transaction.js.patch; test -f sentry.sh && ./sentry.sh || true", "prepare": "./patches/fix_mangle.sh; git apply patches/minifier.js.patch; git apply patches/minify.js.patch; git apply patches/transaction_builder.js.patch; git apply ./patches/transaction.js.patch",
"clean": "rm -r -f node_modules/", "clean": "cd android/; ./gradlew clean; cd ..; rm -r -f /tmp/metro-cache/; rm -r -f node_modules/; npm cache clean --force; npm i; npm start -- --reset-cache",
"start": "node node_modules/react-native/local-cli/cli.js start", "start": "node node_modules/react-native/local-cli/cli.js start",
"android": "react-native run-android", "android": "react-native run-android",
"ios": "react-native run-ios", "ios": "react-native run-ios",
@ -35,8 +35,9 @@
} }
}, },
"dependencies": { "dependencies": {
"@babel/preset-env": "^7.2.3", "@babel/preset-env": "^7.3.1",
"bignumber.js": "^7.0.0", "@remobile/react-native-qrcode-local-image": "^1.0.4",
"bignumber.js": "^8.0.2",
"bip21": "^2.0.2", "bip21": "^2.0.2",
"bip39": "^2.5.0", "bip39": "^2.5.0",
"bitcoinjs-lib": "^3.3.2", "bitcoinjs-lib": "^3.3.2",
@ -44,52 +45,54 @@
"buffer-reverse": "^1.0.1", "buffer-reverse": "^1.0.1",
"crypto-js": "^3.1.9-1", "crypto-js": "^3.1.9-1",
"dayjs": "^1.8.0", "dayjs": "^1.8.0",
"eslint-config-prettier": "^2.10.0", "eslint-config-prettier": "^3.6.0",
"eslint-config-standard": "^12.0.0", "eslint-config-standard": "^12.0.0",
"eslint-config-standard-react": "^7.0.2", "eslint-config-standard-react": "^7.0.2",
"eslint-plugin-prettier": "^2.6.2", "eslint-plugin-prettier": "^3.0.1",
"eslint-plugin-standard": "^4.0.0", "eslint-plugin-standard": "^4.0.0",
"frisbee": "^1.6.4", "frisbee": "^1.6.4",
"intl": "^1.2.5", "intl": "^1.2.5",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"node-libs-react-native": "^1.0.1", "node-libs-react-native": "^1.0.1",
"path-browserify": "0.0.0", "path-browserify": "0.0.0",
"prettier": "^1.14.2", "prettier": "^1.16.1",
"process": "^0.11.10", "process": "^0.11.10",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.7.0", "react": "^16.7.0",
"react-localization": "^1.0.10", "react-localization": "^1.0.10",
"react-native": "^0.57.8", "react-native": "^0.58.1",
"react-native-camera": "^1.8.0", "react-native-camera": "^1.9.2",
"react-native-custom-qr-codes": "^2.0.0", "react-native-custom-qr-codes": "^2.0.0",
"react-native-device-info": "^0.24.3", "react-native-device-info": "^0.25.1",
"react-native-elements": "^0.19.0", "react-native-elements": "^0.19.0",
"react-native-flexi-radio-button": "^0.2.2", "react-native-flexi-radio-button": "^0.2.2",
"react-native-fs": "^2.13.3", "react-native-fs": "^2.13.3",
"react-native-gesture-handler": "^1.0.12", "react-native-gesture-handler": "^1.0.15",
"react-native-google-analytics-bridge": "^6.1.2", "react-native-google-analytics-bridge": "^7.0.0",
"react-native-haptic-feedback": "^1.4.2", "react-native-haptic-feedback": "^1.4.2",
"react-native-image-picker": "^0.28.0",
"react-native-level-fs": "^3.0.1", "react-native-level-fs": "^3.0.1",
"react-native-linear-gradient": "^2.5.3", "react-native-linear-gradient": "^2.5.3",
"react-native-modal": "^7.0.2", "react-native-modal": "^7.0.2",
"react-native-permissions": "^1.1.1", "react-native-permissions": "^1.1.1",
"react-native-prompt-android": "^0.3.4", "react-native-prompt-android": "^0.3.4",
"react-native-qrcode": "^0.2.7", "react-native-qrcode": "^0.2.7",
"react-native-randombytes": "^3.5.1", "react-native-randombytes": "^3.5.2",
"react-native-rate": "^1.1.6", "react-native-rate": "^1.1.6",
"react-native-sentry": "^0.40.2", "react-native-sentry": "^0.40.2",
"react-native-snap-carousel": "^3.7.4", "react-native-snap-carousel": "^3.7.5",
"react-native-sortable-list": "0.0.22", "react-native-sortable-list": "0.0.22",
"react-native-svg": "^8.0.10", "react-native-svg": "^9.0.4",
"react-native-vector-icons": "^6.0.2", "react-native-vector-icons": "^6.2.0",
"react-native-webview": "2.8.0", "react-native-webview": "^3.2.1",
"react-native-wkwebview-reborn": "^2.0.0",
"react-navigation": "^3.0.9", "react-navigation": "^3.0.9",
"react-test-render": "^1.1.1", "react-test-render": "^1.1.1",
"readable-stream": "^1.1.14", "readable-stream": "^1.1.14",
"request-promise-native": "^1.0.5", "request-promise-native": "^1.0.5",
"secure-random": "^1.1.1", "secure-random": "^1.1.1",
"stream-browserify": "^1.0.0", "stream-browserify": "^1.0.0",
"util": "^0.10.4", "util": "^0.11.1",
"wif": "^2.0.1" "wif": "^2.0.1"
}, },
"react-native": { "react-native": {

1
patches/fix_mangle.sh

@ -2,3 +2,4 @@
grep -rl "mangle: { toplevel: true }" ./node_modules/ | xargs sed -i '' -e "s/mangle: { toplevel: true }/mangle: false/g" || true grep -rl "mangle: { toplevel: true }" ./node_modules/ | xargs sed -i '' -e "s/mangle: { toplevel: true }/mangle: false/g" || true
grep -rl "mangle: {toplevel: true}" ./node_modules/ | xargs sed -i '' -e "s/mangle: {toplevel: true}/mangle: false/g" || true grep -rl "mangle: {toplevel: true}" ./node_modules/ | xargs sed -i '' -e "s/mangle: {toplevel: true}/mangle: false/g" || true
grep -rl "BASE_MAP.fill(255)" ./node_modules/ | xargs sed -i '' -e "s/BASE_MAP.fill(255)/for (let c = 0 ; c< 256; c++) BASE_MAP[c] = 255;/g" || true grep -rl "BASE_MAP.fill(255)" ./node_modules/ | xargs sed -i '' -e "s/BASE_MAP.fill(255)/for (let c = 0 ; c< 256; c++) BASE_MAP[c] = 255;/g" || true
echo fix_mangle.sh done

16
patches/minifier.js.patch

@ -1,15 +1,15 @@
diff --git a/node_modules/metro-minify-uglify/src/minifier.js b/node_modules/metro-minify-uglify/src/minifier.js diff --git a/node_modules/metro-minify-uglify/src/minifier.js b/node_modules/metro-minify-uglify/src/minifier.js
index 021df4c..dcea186 100644 index b703ee4..fadc077 100644
--- a/node_modules/metro-minify-uglify/src/minifier.js --- a/node_modules/metro-minify-uglify/src/minifier.js
+++ b/node_modules/metro-minify-uglify/src/minifier.js +++ b/node_modules/metro-minify-uglify/src/minifier.js
@@ -44,9 +44,7 @@ function minify(_ref) { @@ -67,9 +67,7 @@ function minify(_ref) {
reserved = _ref.reserved,
config = _ref.config; config = _ref.config;
const options = _extends({}, config, {
- mangle: _extends({}, config.mangle, { const options = _objectSpread({}, config, {
- mangle: _objectSpread({}, config.mangle, {
- reserved - reserved
- }), - }),
+ mangle: false, // !!!!!!!!!!!!!!!! + mangle: false, // !!!!!!!!!!!!!!!!!!!!!!!!
sourceMap: _objectSpread({}, config.sourceMap, {
sourceMap: _extends({}, config.sourceMap, {
content: map content: map
})

448
screen/lnd/browser.js

@ -1,6 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { TouchableOpacity, ActivityIndicator, View, Alert, Dimensions } from 'react-native'; import { TouchableOpacity, ActivityIndicator, View, Platform, Alert, Dimensions } from 'react-native';
import { WebView } from 'react-native-webview'; import { WebView } from 'react-native-webview';
import WKWebView from 'react-native-wkwebview-reborn';
import { BlueNavigationStyle, SafeBlueArea } from '../../BlueComponents'; import { BlueNavigationStyle, SafeBlueArea } from '../../BlueComponents';
import { FormInput } from 'react-native-elements'; import { FormInput } from 'react-native-elements';
import Ionicons from 'react-native-vector-icons/Ionicons'; import Ionicons from 'react-native-vector-icons/Ionicons';
@ -17,7 +18,7 @@ let bluewalletResponses = {};
// eslint-disable-next-line // eslint-disable-next-line
var webln = { var webln = {
enable: function() { enable: function() {
window.postMessage('enable'); window.postMessage(JSON.stringify({ enable: true }));
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
resolve(true); resolve(true);
}); });
@ -66,166 +67,8 @@ var webln = {
/// ///////////////// /// /////////////////
/// ///////////////// /// /////////////////
export default class Browser extends Component { let alreadyInjected = false;
static navigationOptions = ({ navigation }) => ({ const injectedParadise = `
...BlueNavigationStyle(navigation, true),
title: 'Lapp Browser',
headerLeft: null,
});
constructor(props) {
super(props);
if (!props.navigation.getParam('fromSecret')) throw new Error('Invalid param');
if (!props.navigation.getParam('fromWallet')) throw new Error('Invalid param');
this.state = {
url: 'https://bluewallet.io/marketplace/',
pageIsLoading: false,
fromSecret: props.navigation.getParam('fromSecret'),
fromWallet: props.navigation.getParam('fromWallet'),
};
}
render() {
return (
<SafeBlueArea>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<TouchableOpacity
onPress={() => {
this.webview.goBack();
}}
>
<Ionicons
name={'ios-arrow-round-back'}
size={36}
style={{
color: 'red',
backgroundColor: 'transparent',
paddingLeft: 10,
}}
/>
</TouchableOpacity>
<FormInput
inputStyle={{ color: '#0c2550', maxWidth: width - 150, fontSize: 16 }}
containerStyle={{
maxWidth: width - 150,
borderColor: '#d2d2d2',
borderWidth: 0.5,
backgroundColor: '#f5f5f5',
}}
value={this.state.url}
/>
<TouchableOpacity
onPress={() => {
this.setState({ url: 'https://bluewallet.io/marketplace/' });
}}
>
<Ionicons
name={'ios-home'}
size={36}
style={{
color: 'red',
backgroundColor: 'transparent',
}}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
this.webview.reload();
}}
>
{(!this.state.pageIsLoading && (
<Ionicons
name={'ios-sync'}
size={36}
style={{
color: 'red',
backgroundColor: 'transparent',
paddingLeft: 15,
}}
/>
)) || (
<View style={{ paddingLeft: 20 }}>
<ActivityIndicator />
</View>
)}
</TouchableOpacity>
</View>
<WebView
ref={ref => (this.webview = ref)}
source={{ uri: this.state.url }}
mixedContentMode={'compatibility'}
onMessage={e => {
// this is a handler which receives messages sent from within the browser
console.log('---- message from the bus:', e.nativeEvent.data);
let json = false;
try {
json = JSON.parse(e.nativeEvent.data);
} catch (_) {}
// message from browser has ln invoice
if (json && json.sendPayment) {
// checking that we do not trigger alert too often:
if (+new Date() - lastTimeTriedToPay < 3000) {
return;
}
lastTimeTriedToPay = +new Date();
// checking that already asked about this invoice:
if (processedInvoices[json.sendPayment]) {
return;
} else {
processedInvoices[json.sendPayment] = 1;
}
Alert.alert(
'Page',
'This page asks for permission to pay an invoice',
[
{ text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel' },
{
text: 'Pay',
onPress: () => {
console.log('OK Pressed');
this.props.navigation.navigate({
routeName: 'ScanLndInvoice',
params: {
uri: json.sendPayment,
fromSecret: this.state.fromSecret,
},
});
},
},
],
{ cancelable: false },
);
}
if (json && json.makeInvoice) {
let amount = Math.max(+json.makeInvoice.minimumAmount, +json.makeInvoice.maximumAmount, +json.makeInvoice.defaultAmount);
Alert.alert(
'Page',
'This page wants to pay you ' + amount + ' sats (' + json.makeInvoice.defaultMemo + ')',
[
{ text: 'No thanks', onPress: () => console.log('Cancel Pressed'), style: 'cancel' },
{
text: 'Accept',
onPress: async () => {
/** @type {LightningCustodianWallet} */
const fromWallet = this.state.fromWallet;
const payreq = await fromWallet.addInvoice(amount, json.makeInvoice.defaultMemo || ' ');
this.webview.postMessage(JSON.stringify({ bluewalletResponse: { paymentRequest: payreq }, id: json.id }));
},
},
],
{ cancelable: false },
);
}
}}
injectedJavaScript={`
/* rules: /* rules:
no 'let', only 'var' no 'let', only 'var'
@ -251,7 +94,7 @@ bluewalletResponses = {};
webln = { webln = {
enable : function () { enable : function () {
window.postMessage('enable'); window.postMessage(JSON.stringify({'enable': true}));
return new Promise(function(resolve, reject){ return new Promise(function(resolve, reject){
resolve(true); resolve(true);
}) })
@ -327,6 +170,7 @@ function tryToPay(invoice) {
searching for all bolt11 manually */ searching for all bolt11 manually */
setInterval(function() { setInterval(function() {
window.postMessage('interval');
var searchText = "lnbc"; var searchText = "lnbc";
@ -362,14 +206,288 @@ setInterval(function() {
}, 1000); }, 1000);
`} `;
export default class Browser extends Component {
static navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
title: 'Lapp Browser',
headerLeft: null,
});
constructor(props) {
super(props);
if (!props.navigation.getParam('fromSecret')) throw new Error('Invalid param');
if (!props.navigation.getParam('fromWallet')) throw new Error('Invalid param');
this.state = {
url: 'https://bluewallet.io/marketplace/',
pageIsLoading: false,
fromSecret: props.navigation.getParam('fromSecret'),
fromWallet: props.navigation.getParam('fromWallet'),
};
}
renderWebView = () => {
if (Platform.OS === 'android') {
return (
<WebView
ref={ref => (this.webview = ref)}
source={{ uri: this.state.url }}
onMessage={e => {
// this is a handler which receives messages sent from within the browser
console.log('---- message from the bus:', e.nativeEvent.data);
let json = false;
try {
json = JSON.parse(e.nativeEvent.data);
} catch (_) {}
// message from browser has ln invoice
if (json && json.sendPayment) {
// checking that already asked about this invoice:
if (processedInvoices[json.sendPayment]) {
return;
} else {
// checking that we do not trigger alert too often:
if (+new Date() - lastTimeTriedToPay < 3000) {
return;
}
lastTimeTriedToPay = +new Date();
//
processedInvoices[json.sendPayment] = 1;
}
Alert.alert(
'Page',
'This page asks for permission to pay an invoice',
[
{ text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel' },
{
text: 'Pay',
onPress: () => {
console.log('OK Pressed');
this.props.navigation.navigate({
routeName: 'ScanLndInvoice',
params: {
uri: json.sendPayment,
fromSecret: this.state.fromSecret,
},
});
},
},
],
{ cancelable: false },
);
}
if (json && json.makeInvoice) {
let amount = Math.max(+json.makeInvoice.minimumAmount, +json.makeInvoice.maximumAmount, +json.makeInvoice.defaultAmount);
Alert.alert(
'Page',
'This page wants to pay you ' + amount + ' sats (' + json.makeInvoice.defaultMemo + ')',
[
{ text: 'No thanks', onPress: () => console.log('Cancel Pressed'), style: 'cancel' },
{
text: 'Accept',
onPress: async () => {
/** @type {LightningCustodianWallet} */
const fromWallet = this.state.fromWallet;
const payreq = await fromWallet.addInvoice(amount, json.makeInvoice.defaultMemo || ' ');
this.webview.postMessage(JSON.stringify({ bluewalletResponse: { paymentRequest: payreq }, id: json.id }));
},
},
],
{ cancelable: false },
);
}
if (json && json.enable) {
console.log('webln enabled');
this.setState({ weblnEnabled: true });
}
}}
onLoadStart={e => { onLoadStart={e => {
this.setState({ pageIsLoading: true }); alreadyInjected = false;
console.log('load start');
this.setState({ pageIsLoading: true, weblnEnabled: false });
}} }}
onLoadEnd={e => { onLoadEnd={e => {
console.log('load end');
this.setState({ url: e.nativeEvent.url, pageIsLoading: false }); this.setState({ url: e.nativeEvent.url, pageIsLoading: false });
}} }}
onLoadProgress={e => {
console.log('progress:', e.nativeEvent.progress);
if (!alreadyInjected && e.nativeEvent.progress > 0.5) {
this.webview.injectJavaScript(injectedParadise);
alreadyInjected = true;
console.log('injected');
}
}}
/> />
);
} else if (Platform.OS === 'ios') {
return (
<WKWebView
ref={ref => (this.webview = ref)}
source={{ uri: this.state.url }}
injectJavaScript={injectedParadise}
onMessage={e => {
// this is a handler which receives messages sent from within the browser
console.log('---- message from the bus:', e.nativeEvent.data);
let json = false;
try {
json = JSON.parse(e.nativeEvent.data);
} catch (_) {}
// message from browser has ln invoice
if (json && json.sendPayment) {
// checking that we do not trigger alert too often:
if (+new Date() - lastTimeTriedToPay < 3000) {
return;
}
lastTimeTriedToPay = +new Date();
// checking that already asked about this invoice:
if (processedInvoices[json.sendPayment]) {
return;
} else {
processedInvoices[json.sendPayment] = 1;
}
Alert.alert(
'Page',
'This page asks for permission to pay an invoice',
[
{ text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel' },
{
text: 'Pay',
onPress: () => {
console.log('OK Pressed');
this.props.navigation.navigate({
routeName: 'ScanLndInvoice',
params: {
uri: json.sendPayment,
fromSecret: this.state.fromSecret,
},
});
},
},
],
{ cancelable: false },
);
}
if (json && json.makeInvoice) {
let amount = Math.max(+json.makeInvoice.minimumAmount, +json.makeInvoice.maximumAmount, +json.makeInvoice.defaultAmount);
Alert.alert(
'Page',
'This page wants to pay you ' + amount + ' sats (' + json.makeInvoice.defaultMemo + ')',
[
{ text: 'No thanks', onPress: () => console.log('Cancel Pressed'), style: 'cancel' },
{
text: 'Accept',
onPress: async () => {
/** @type {LightningCustodianWallet} */
const fromWallet = this.state.fromWallet;
const payreq = await fromWallet.addInvoice(amount, json.makeInvoice.defaultMemo || ' ');
this.webview.postMessage(JSON.stringify({ bluewalletResponse: { paymentRequest: payreq }, id: json.id }));
},
},
],
{ cancelable: false },
);
}
if (json && json.enable) {
console.log('webln enabled');
this.setState({ weblnEnabled: true });
}
}}
onLoadStart={e => {
alreadyInjected = false;
console.log('load start');
this.setState({ pageIsLoading: true, weblnEnabled: false });
}}
onLoadEnd={e => {
console.log('load end');
this.setState({ url: e.nativeEvent.url, pageIsLoading: false });
}}
/>
);
}
};
render() {
return (
<SafeBlueArea>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<TouchableOpacity
onPress={() => {
this.webview.goBack();
}}
>
<Ionicons
name={'ios-arrow-round-back'}
size={36}
style={{
color: 'red',
backgroundColor: 'transparent',
paddingLeft: 10,
}}
/>
</TouchableOpacity>
<FormInput
inputStyle={{ color: '#0c2550', maxWidth: width - 150, fontSize: 16 }}
containerStyle={{
maxWidth: width - 150,
borderColor: '#d2d2d2',
borderWidth: 0.5,
backgroundColor: '#f5f5f5',
}}
value={this.state.url}
/>
<TouchableOpacity
onPress={() => {
processedInvoices = {};
this.setState({ url: 'https://bluewallet.io/marketplace/' });
}}
>
<Ionicons
name={'ios-home'}
size={36}
style={{
color: this.state.weblnEnabled ? 'green' : 'red',
backgroundColor: 'transparent',
}}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
let reloadUrl = this.state.url;
this.setState({ url: 'about:blank' });
processedInvoices = {};
setTimeout(() => this.setState({ url: reloadUrl }), 500);
// this.webview.reload();
}}
>
{(!this.state.pageIsLoading && (
<Ionicons
name={'ios-sync'}
size={36}
style={{
color: 'red',
backgroundColor: 'transparent',
paddingLeft: 15,
}}
/>
)) || (
<View style={{ paddingLeft: 20 }}>
<ActivityIndicator />
</View>
)}
</TouchableOpacity>
</View>
{this.renderWebView()}
</SafeBlueArea> </SafeBlueArea>
); );
} }

6
screen/lnd/lndCreateInvoice.js

@ -51,11 +51,7 @@ export default class LNDCreateInvoice extends Component {
{this.state.isLoading ? ( {this.state.isLoading ? (
<ActivityIndicator /> <ActivityIndicator />
) : ( ) : (
<BlueButton <BlueButton disabled={!this.state.amount > 0} onPress={() => this.createInvoice()} title={loc.send.details.create} />
disabled={!(this.state.description.length > 0 && this.state.amount > 0)}
onPress={() => this.createInvoice()}
title={loc.send.details.create}
/>
)} )}
</View> </View>
); );

36
screen/lnd/lndViewAdditionalInvoiceInformation.js

@ -1,7 +1,15 @@
/* global alert */ /* global alert */
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Animated, StyleSheet, View, TouchableOpacity, Clipboard, Share } from 'react-native'; import { View, Share } from 'react-native';
import { BlueLoading, SafeBlueArea, BlueButton, BlueNavigationStyle, BlueText, BlueSpacing20 } from '../../BlueComponents'; import {
BlueLoading,
BlueCopyTextToClipboard,
SafeBlueArea,
BlueButton,
BlueNavigationStyle,
BlueText,
BlueSpacing20,
} from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { QRCode } from 'react-native-custom-qr-codes'; import { QRCode } from 'react-native-custom-qr-codes';
/** @type {AppStorage} */ /** @type {AppStorage} */
@ -16,13 +24,6 @@ export default class LNDViewAdditionalInvoiceInformation extends Component {
state = { walletInfo: undefined }; 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() { async componentDidMount() {
const fromWallet = this.props.navigation.getParam('fromWallet'); const fromWallet = this.props.navigation.getParam('fromWallet');
try { try {
@ -56,13 +57,9 @@ export default class LNDViewAdditionalInvoiceInformation extends Component {
/> />
<BlueSpacing20 /> <BlueSpacing20 />
<BlueText>Open direct channel with this node:</BlueText> <BlueText>Open direct channel with this node:</BlueText>
<TouchableOpacity onPress={this.copyToClipboard}> <BlueCopyTextToClipboard text={this.state.walletInfo.uris[0]} />
<Animated.Text style={styles.address} numberOfLines={0}>
{this.state.addressText}
</Animated.Text>
</TouchableOpacity>
</View> </View>
<View style={{ marginBottom: 24 }}> <View style={{ marginBottom: 25 }}>
<BlueButton <BlueButton
icon={{ icon={{
name: 'share-alternative', name: 'share-alternative',
@ -83,15 +80,6 @@ export default class LNDViewAdditionalInvoiceInformation extends Component {
} }
} }
const styles = StyleSheet.create({
address: {
marginVertical: 32,
fontSize: 15,
color: '#9aa0aa',
textAlign: 'center',
},
});
LNDViewAdditionalInvoiceInformation.propTypes = { LNDViewAdditionalInvoiceInformation.propTypes = {
navigation: PropTypes.shape({ navigation: PropTypes.shape({
goBack: PropTypes.function, goBack: PropTypes.function,

48
screen/lnd/lndViewInvoice.js

@ -1,6 +1,14 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Animated, StyleSheet, View, TouchableOpacity, Clipboard, Dimensions, Share, ScrollView, BackHandler } from 'react-native'; import { View, Dimensions, Share, ScrollView, BackHandler } from 'react-native';
import { BlueLoading, BlueText, SafeBlueArea, BlueButton, BlueNavigationStyle, BlueSpacing20 } from '../../BlueComponents'; import {
BlueLoading,
BlueText,
SafeBlueArea,
BlueButton,
BlueCopyTextToClipboard,
BlueNavigationStyle,
BlueSpacing20,
} from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { Icon } from 'react-native-elements'; import { Icon } from 'react-native-elements';
@ -59,7 +67,7 @@ export default class LNDViewInvoice extends Component {
ReactNativeHapticFeedback.trigger('notificationSuccess', false); ReactNativeHapticFeedback.trigger('notificationSuccess', false);
clearInterval(this.fetchInvoiceInterval); clearInterval(this.fetchInvoiceInterval);
this.fetchInvoiceInterval = undefined; this.fetchInvoiceInterval = undefined;
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED); EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // remote because we want to refetch from server tx list and balance
} else { } else {
const currentDate = new Date(); const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0; const now = (currentDate.getTime() / 1000) | 0;
@ -88,16 +96,10 @@ export default class LNDViewInvoice extends Component {
} }
handleBackButton() { handleBackButton() {
this.props.navigation.dismiss();
return true; return true;
} }
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 = () => { onLayout = () => {
const { height } = Dimensions.get('window'); const { height } = Dimensions.get('window');
this.setState({ qrCodeHeight: height > width ? width - 20 : width / 2 }); this.setState({ qrCodeHeight: height > width ? width - 20 : width / 2 });
@ -171,7 +173,6 @@ export default class LNDViewInvoice extends Component {
} }
} }
} }
// Invoice has not expired, nor has it been paid for. // Invoice has not expired, nor has it been paid for.
return ( return (
<SafeBlueArea> <SafeBlueArea>
@ -181,12 +182,11 @@ export default class LNDViewInvoice extends Component {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
marginTop: 8, marginTop: 8,
paddingHorizontal: 16,
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
onLayout={this.onLayout} onLayout={this.onLayout}
> >
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 16 }}>
<QRFast <QRFast
value={typeof this.state.invoice === 'object' ? invoice.payment_request : invoice} value={typeof this.state.invoice === 'object' ? invoice.payment_request : invoice}
fgColor={BlueApp.settings.brandingColor} fgColor={BlueApp.settings.brandingColor}
@ -197,12 +197,10 @@ export default class LNDViewInvoice extends Component {
<BlueSpacing20 /> <BlueSpacing20 />
{invoice && invoice.amt && <BlueText>Please pay {invoice.amt} sats</BlueText>} {invoice && invoice.amt && <BlueText>Please pay {invoice.amt} sats</BlueText>}
{invoice && invoice.description && <BlueText>For: {invoice.description}</BlueText>} {invoice && invoice.hasOwnProperty('description') && invoice.description.length > 0 && (
<TouchableOpacity onPress={this.copyToClipboard}> <BlueText>For: {invoice.description}</BlueText>
<Animated.Text style={styles.address} numberOfLines={0}> )}
{this.state.addressText} <BlueCopyTextToClipboard text={this.state.invoice.payment_request} />
</Animated.Text>
</TouchableOpacity>
<BlueButton <BlueButton
icon={{ icon={{
@ -217,8 +215,9 @@ export default class LNDViewInvoice extends Component {
}} }}
title={loc.receive.details.share} title={loc.receive.details.share}
/> />
<BlueSpacing20 />
<BlueButton <BlueButton
buttonStyle={{ backgroundColor: 'white' }} backgroundColor="#FFFFFF"
icon={{ icon={{
name: 'info', name: 'info',
type: 'entypo', type: 'entypo',
@ -235,15 +234,6 @@ export default class LNDViewInvoice extends Component {
} }
} }
const styles = StyleSheet.create({
address: {
marginVertical: 32,
fontSize: 15,
color: '#9aa0aa',
textAlign: 'center',
},
});
LNDViewInvoice.propTypes = { LNDViewInvoice.propTypes = {
navigation: PropTypes.shape({ navigation: PropTypes.shape({
goBack: PropTypes.function, goBack: PropTypes.function,

126
screen/lnd/scanLndInvoice.js

@ -1,16 +1,22 @@
/* global alert */ /* global alert */
import React from 'react'; import React from 'react';
import { Text, Dimensions, ActivityIndicator, View, TouchableOpacity, TouchableWithoutFeedback, TextInput, Keyboard } from 'react-native'; import { Text, ActivityIndicator, View, TouchableWithoutFeedback, Keyboard } from 'react-native';
import { Icon } from 'react-native-elements';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueNavigationStyle, BlueBitcoinAmount } from '../../BlueComponents'; import {
BlueSpacing20,
BlueButton,
SafeBlueArea,
BlueCard,
BlueNavigationStyle,
BlueAddressInput,
BlueBitcoinAmount,
} from '../../BlueComponents';
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
import { BitcoinUnit } from '../../models/bitcoinUnits'; import { BitcoinUnit } from '../../models/bitcoinUnits';
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
let EV = require('../../events'); let EV = require('../../events');
let loc = require('../../loc'); let loc = require('../../loc');
const { width } = Dimensions.get('window');
export default class ScanLndInvoice extends React.Component { export default class ScanLndInvoice extends React.Component {
static navigationOptions = ({ navigation }) => ({ static navigationOptions = ({ navigation }) => ({
@ -59,20 +65,12 @@ export default class ScanLndInvoice extends React.Component {
} }
async componentDidMount() { async componentDidMount() {
EV(
EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS,
data => {
this.processInvoice(data);
},
true,
);
if (this.props.navigation.state.params.uri) { if (this.props.navigation.state.params.uri) {
this.processTextForInvoice(this.props.navigation.getParam('uri')); this.processTextForInvoice(this.props.navigation.getParam('uri'));
} }
} }
processInvoice(data) { processInvoice = data => {
this.setState({ isLoading: true }, async () => { this.setState({ isLoading: true }, async () => {
if (this.ignoreRead) return; if (this.ignoreRead) return;
this.ignoreRead = true; this.ignoreRead = true;
@ -85,6 +83,12 @@ export default class ScanLndInvoice extends React.Component {
return this.props.navigation.goBack(); return this.props.navigation.goBack();
} }
// handling BIP21 w/BOLT11 support
let ind = data.indexOf('lightning=');
if (ind !== -1) {
data = data.substring(ind + 10).split('&')[0];
}
data = data.replace('LIGHTNING:', '').replace('lightning:', ''); data = data.replace('LIGHTNING:', '').replace('lightning:', '');
console.log(data); console.log(data);
@ -116,7 +120,7 @@ export default class ScanLndInvoice extends React.Component {
alert(Err.message); alert(Err.message);
} }
}); });
} };
async pay() { async pay() {
if (!this.state.hasOwnProperty('decoded')) { if (!this.state.hasOwnProperty('decoded')) {
@ -206,53 +210,15 @@ export default class ScanLndInvoice extends React.Component {
/> />
<BlueSpacing20 /> <BlueSpacing20 />
<BlueCard> <BlueCard>
<View <BlueAddressInput
style={{ onChangeText={text => {
flexDirection: 'row', this.setState({ destination: text });
borderColor: '#d2d2d2', this.processTextForInvoice(text);
borderBottomColor: '#d2d2d2',
borderWidth: 1.0,
borderBottomWidth: 0.5,
backgroundColor: '#f5f5f5',
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
marginVertical: 8,
borderRadius: 4,
}} }}
> onBarScanned={this.processInvoice}
<TextInput address={this.state.destination}
onChangeText={text => { isLoading={this.state.isLoading}
this.setState({ destination: text }); />
this.processTextForInvoice(text);
}}
placeholder={loc.wallets.details.destination}
numberOfLines={1}
value={this.state.destination}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33, height: 33 }}
editable={!this.state.isLoading}
/>
<TouchableOpacity
disabled={this.state.isLoading}
onPress={() => this.props.navigation.navigate('ScanQrAddress')}
style={{
width: 75,
height: 36,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: '#bebebe',
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
marginHorizontal: 4,
}}
>
<Icon name="qrcode" size={22} type="font-awesome" color="#FFFFFF" />
<Text style={{ color: '#FFFFFF' }}>{loc.send.details.scan}</Text>
</TouchableOpacity>
</View>
<View <View
style={{ style={{
flexDirection: 'row', flexDirection: 'row',
@ -269,27 +235,27 @@ export default class ScanLndInvoice extends React.Component {
{this.state.expiresIn !== undefined && ( {this.state.expiresIn !== undefined && (
<Text style={{ color: '#81868e', fontSize: 12, left: 20, top: 10 }}>Expires in: {this.state.expiresIn}</Text> <Text style={{ color: '#81868e', fontSize: 12, left: 20, top: 10 }}>Expires in: {this.state.expiresIn}</Text>
)} )}
<BlueSpacing20 />
<BlueSpacing20 />
{this.state.isLoading ? (
<View>
<ActivityIndicator />
</View>
) : (
<BlueButton
icon={{
name: 'bolt',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
title={'Pay'}
onPress={() => {
this.pay();
}}
disabled={this.shouldDisablePayButton()}
/>
)}
</BlueCard> </BlueCard>
<BlueSpacing20 />
{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> </SafeBlueArea>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
); );

36
screen/receive/details.js

@ -1,8 +1,16 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Animated, StyleSheet, View, TouchableOpacity, Clipboard, Share } from 'react-native'; import { View, Share } from 'react-native';
import { QRCode } from 'react-native-custom-qr-codes'; import { QRCode } from 'react-native-custom-qr-codes';
import bip21 from 'bip21'; import bip21 from 'bip21';
import { BlueLoading, SafeBlueArea, BlueButton, BlueButtonLink, BlueNavigationStyle, is } from '../../BlueComponents'; import {
BlueLoading,
SafeBlueArea,
BlueCopyTextToClipboard,
BlueButton,
BlueButtonLink,
BlueNavigationStyle,
is,
} from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
@ -70,13 +78,6 @@ export default class ReceiveDetails extends Component {
} }
} }
copyToClipboard = () => {
this.setState({ addressText: loc.receive.details.copiedToClipboard }, () => {
Clipboard.setString(this.state.address);
setTimeout(() => this.setState({ addressText: this.state.address }), 1000);
});
};
render() { render() {
console.log('render() receive/details, address,secret=', this.state.address, ',', this.state.secret); console.log('render() receive/details, address,secret=', this.state.address, ',', this.state.secret);
if (this.state.isLoading) { if (this.state.isLoading) {
@ -94,13 +95,9 @@ export default class ReceiveDetails extends Component {
backgroundColor={BlueApp.settings.brandingColor} backgroundColor={BlueApp.settings.brandingColor}
logo={require('../../img/qr-code.png')} logo={require('../../img/qr-code.png')}
/> />
<TouchableOpacity onPress={this.copyToClipboard}> <BlueCopyTextToClipboard text={this.state.addressText} />
<Animated.Text style={styles.address} numberOfLines={0}>
{this.state.addressText}
</Animated.Text>
</TouchableOpacity>
</View> </View>
<View style={{ marginBottom: 24, alignItems: 'center' }}> <View style={{ flex: 0.2, marginBottom: 24, alignItems: 'center' }}>
<BlueButtonLink <BlueButtonLink
title={loc.receive.details.setAmount} title={loc.receive.details.setAmount}
onPress={() => { onPress={() => {
@ -129,15 +126,6 @@ export default class ReceiveDetails extends Component {
} }
} }
const styles = StyleSheet.create({
address: {
marginVertical: 32,
fontSize: 15,
color: '#9aa0aa',
textAlign: 'center',
},
});
ReceiveDetails.propTypes = { ReceiveDetails.propTypes = {
navigation: PropTypes.shape({ navigation: PropTypes.shape({
goBack: PropTypes.function, goBack: PropTypes.function,

100
screen/receive/receiveAmount.js

@ -1,21 +1,17 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { import { View, Share, TextInput, KeyboardAvoidingView, Platform, Dimensions, ScrollView } from 'react-native';
StyleSheet,
View,
Share,
TextInput,
KeyboardAvoidingView,
Clipboard,
Animated,
TouchableOpacity,
Platform,
Dimensions,
ScrollView,
} from 'react-native';
import { QRCode as QRSlow } from 'react-native-custom-qr-codes'; import { QRCode as QRSlow } from 'react-native-custom-qr-codes';
import QRFast from 'react-native-qrcode'; import QRFast from 'react-native-qrcode';
import bip21 from 'bip21'; import bip21 from 'bip21';
import { SafeBlueArea, BlueButton, BlueNavigationStyle, BlueBitcoinAmount, BlueText } from '../../BlueComponents'; import {
SafeBlueArea,
BlueCard,
BlueButton,
BlueNavigationStyle,
BlueBitcoinAmount,
BlueText,
BlueCopyTextToClipboard,
} from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
@ -52,13 +48,6 @@ export default class ReceiveAmount extends Component {
}; };
} }
copyToClipboard = () => {
this.setState({ addressText: loc.receive.details.copiedToClipboard }, () => {
Clipboard.setString(this.state.bip21);
setTimeout(() => this.setState({ addressText: this.state.address }), 1000);
});
};
determineSize = () => { determineSize = () => {
if (width > 312) { if (width > 312) {
return width - 48; return width - 48;
@ -94,15 +83,17 @@ export default class ReceiveAmount extends Component {
editable={!this.state.isLoading} editable={!this.state.isLoading}
/> />
</View> </View>
<BlueButton <BlueCard>
title={loc.receive.create} <BlueButton
onPress={() => { title={loc.receive.details.create}
this.setState({ onPress={() => {
amountSet: true, this.setState({
bip21: bip21.encode(this.state.address, { amount: this.state.amount, label: this.state.label }), amountSet: true,
}); bip21: bip21.encode(this.state.address, { amount: this.state.amount, label: this.state.label }),
}} });
/> }}
/>
</BlueCard>
</View> </View>
); );
} }
@ -132,12 +123,8 @@ export default class ReceiveAmount extends Component {
/> />
)} )}
</View> </View>
<View style={{ marginBottom: 24, alignItems: 'center', justifyContent: 'space-between' }}> <View style={{ alignItems: 'center', justifyContent: 'space-between' }}>
<TouchableOpacity onPress={this.copyToClipboard}> <BlueCopyTextToClipboard text={this.state.bip21} />
<Animated.Text style={styles.address} numberOfLines={0}>
{this.state.bip21}
</Animated.Text>
</TouchableOpacity>
</View> </View>
</View> </View>
); );
@ -157,23 +144,21 @@ export default class ReceiveAmount extends Component {
{this.state.amountSet ? this.renderWithSetAmount() : this.renderDefault()} {this.state.amountSet ? this.renderWithSetAmount() : this.renderDefault()}
</KeyboardAvoidingView> </KeyboardAvoidingView>
{this.state.amountSet && ( {this.state.amountSet && (
<BlueButton <BlueCard>
buttonStyle={{ <BlueButton
alignSelf: 'center', icon={{
marginBottom: 24, name: 'share-alternative',
}} type: 'entypo',
icon={{ color: BlueApp.settings.buttonTextColor,
name: 'share-alternative', }}
type: 'entypo', onPress={async () => {
color: BlueApp.settings.buttonTextColor, Share.share({
}} message: this.state.bip21,
onPress={async () => { });
Share.share({ }}
message: this.state.bip21, title={loc.receive.details.share}
}); />
}} </BlueCard>
title={loc.receive.details.share}
/>
)} )}
</View> </View>
</ScrollView> </ScrollView>
@ -181,12 +166,3 @@ export default class ReceiveAmount extends Component {
); );
} }
} }
const styles = StyleSheet.create({
address: {
marginVertical: 32,
fontSize: 15,
color: '#9aa0aa',
textAlign: 'center',
},
});

150
screen/send/details.js

@ -14,7 +14,7 @@ import {
Text, Text,
} from 'react-native'; } from 'react-native';
import { Icon } from 'react-native-elements'; import { Icon } from 'react-native-elements';
import { BlueNavigationStyle, BlueButton, BlueBitcoinAmount } from '../../BlueComponents'; import { BlueNavigationStyle, BlueButton, BlueBitcoinAmount, BlueAddressInput } from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Modal from 'react-native-modal'; import Modal from 'react-native-modal';
import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees'; import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees';
@ -23,7 +23,6 @@ import { BitcoinUnit } from '../../models/bitcoinUnits';
import { HDLegacyP2PKHWallet, HDSegwitP2SHWallet } from '../../class'; import { HDLegacyP2PKHWallet, HDSegwitP2SHWallet } from '../../class';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
const bip21 = require('bip21'); const bip21 = require('bip21');
let EV = require('../../events');
let BigNumber = require('bignumber.js'); let BigNumber = require('bignumber.js');
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
@ -82,13 +81,15 @@ export default class SendDetails extends Component {
}; };
} }
async componentDidMount() { processAddressData = data => {
EV(EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, data => { this.setState(
this.setState( { isLoading: true },
{ isLoading: false }, () => {
() => { if (BitcoinBIP70TransactionDecode.matchesPaymentURL(data)) {
data = data.replace('bitcoin:', ''); this.processBIP70Invoice(data);
if (btcAddressRx.test(data) || data.indexOf('bc1') === 0) { } else {
const dataWithoutSchema = data.replace('bitcoin:', '');
if (btcAddressRx.test(dataWithoutSchema) || dataWithoutSchema.indexOf('bc1') === 0) {
this.setState({ this.setState({
address: data, address: data,
bip70TransactionExpiration: null, bip70TransactionExpiration: null,
@ -97,11 +98,15 @@ export default class SendDetails extends Component {
} else { } else {
let address, options; let address, options;
try { try {
if (!data.toLowerCase().startsWith('bitcoin:')) {
data = `bitcoin:${data}`;
}
const decoded = bip21.decode(data); const decoded = bip21.decode(data);
address = decoded.address; address = decoded.address;
options = decoded.options; options = decoded.options;
} catch (Err) { } catch (error) {
console.log(Err); console.log(error);
this.setState({ isLoading: false });
} }
console.log(options); console.log(options);
if (btcAddressRx.test(address)) { if (btcAddressRx.test(address)) {
@ -112,14 +117,15 @@ export default class SendDetails extends Component {
bip70TransactionExpiration: null, bip70TransactionExpiration: null,
isLoading: false, isLoading: false,
}); });
} else if (BitcoinBIP70TransactionDecode.matchesPaymentURL(data)) {
this.processBIP70Invoice(data);
} }
} }
}, }
true, },
); true,
}); );
};
async componentDidMount() {
let recommendedFees = await NetworkTransactionFees.recommendedFees().catch(response => { let recommendedFees = await NetworkTransactionFees.recommendedFees().catch(response => {
this.setState({ this.setState({
fee: response.halfHourFee, fee: response.halfHourFee,
@ -141,21 +147,7 @@ export default class SendDetails extends Component {
this.processBIP70Invoice(this.props.navigation.state.params.uri); this.processBIP70Invoice(this.props.navigation.state.params.uri);
} else { } else {
try { try {
let amount = ''; const { address, amount, memo } = this.decodeBitcoinUri(this.props.navigation.getParam('uri'));
let parsedBitcoinUri = null;
let address = '';
let memo = '';
parsedBitcoinUri = bip21.decode(this.props.navigation.state.params.uri);
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 }); this.setState({ address, amount, memo });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@ -166,6 +158,28 @@ export default class SendDetails extends Component {
} }
} }
decodeBitcoinUri(uri) {
try {
let amount = '';
let parsedBitcoinUri = null;
let address = '';
let memo = '';
parsedBitcoinUri = bip21.decode(uri);
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;
}
}
return { address, amount, memo };
} catch (_) {
return undefined;
}
}
recalculateAvailableBalance(balance, amount, fee) { recalculateAvailableBalance(balance, amount, fee) {
if (!amount) amount = 0; if (!amount) amount = 0;
if (!fee) fee = 0; if (!fee) fee = 0;
@ -497,56 +511,27 @@ export default class SendDetails extends Component {
amount={this.state.amount} amount={this.state.amount}
onChangeText={text => this.setState({ amount: text })} onChangeText={text => this.setState({ amount: text })}
/> />
<View <BlueAddressInput
style={{ onChangeText={text => {
flexDirection: 'row', if (!this.processBIP70Invoice(text)) {
borderColor: '#d2d2d2', this.setState({
borderBottomColor: '#d2d2d2', address: text.trim().replace('bitcoin:', ''),
borderWidth: 1.0, isLoading: false,
borderBottomWidth: 0.5, bip70TransactionExpiration: null,
backgroundColor: '#f5f5f5', });
minHeight: 44, } else {
height: 44, try {
marginHorizontal: 20, const { address, amount, memo } = this.decodeBitcoinUri(text);
alignItems: 'center', this.setState({ address, amount, memo, isLoading: false, bip70TransactionExpiration: null });
marginVertical: 8, } catch (_) {
borderRadius: 4, this.setState({ address: text.trim(), isLoading: false, bip70TransactionExpiration: null });
}}
>
<TextInput
onChangeText={text => {
if (!this.processBIP70Invoice(text)) {
this.setState({ address: text.replace(' ', ''), isLoading: false, bip70TransactionExpiration: null });
} else {
this.setState({ address: text.replace(' ', ''), isLoading: false, bip70TransactionExpiration: null });
} }
}} }
placeholder={loc.send.details.address} }}
numberOfLines={1} onBarScanned={this.processAddressData}
value={this.state.address} address={this.state.address}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} isLoading={this.state.isLoading}
editable={!this.state.isLoading} />
/>
<TouchableOpacity
disabled={this.state.isLoading}
onPress={() => this.props.navigation.navigate('ScanQrAddress')}
style={{
width: 75,
height: 36,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: '#bebebe',
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
marginHorizontal: 4,
}}
>
<Icon name="qrcode" size={22} type="font-awesome" color="#FFFFFF" />
<Text style={{ color: '#FFFFFF' }}>{loc.send.details.scan}</Text>
</TouchableOpacity>
</View>
<View <View
hide={!this.state.showMemoRow} hide={!this.state.showMemoRow}
style={{ style={{
@ -635,8 +620,9 @@ const styles = StyleSheet.create({
SendDetails.propTypes = { SendDetails.propTypes = {
navigation: PropTypes.shape({ navigation: PropTypes.shape({
goBack: PropTypes.function, goBack: PropTypes.func,
navigate: PropTypes.func, navigate: PropTypes.func,
getParam: PropTypes.func,
state: PropTypes.shape({ state: PropTypes.shape({
params: PropTypes.shape({ params: PropTypes.shape({
address: PropTypes.string, address: PropTypes.string,

27
screen/send/scanQrAddress.js

@ -1,11 +1,9 @@
/* global alert */
import React from 'react'; import React from 'react';
import { ActivityIndicator, Image, View, TouchableOpacity } from 'react-native'; import { ActivityIndicator, Image, View, TouchableOpacity } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Camera from 'react-native-camera'; import Camera from 'react-native-camera';
import Permissions from 'react-native-permissions'; import Permissions from 'react-native-permissions';
import { SafeBlueArea } from '../../BlueComponents'; import { SafeBlueArea } from '../../BlueComponents';
let EV = require('../../events');
export default class CameraExample extends React.Component { export default class CameraExample extends React.Component {
static navigationOptions = { static navigationOptions = {
@ -17,18 +15,14 @@ export default class CameraExample extends React.Component {
hasCameraPermission: null, hasCameraPermission: null,
}; };
async onBarCodeScanned(ret) { onBarCodeScanned(ret) {
if (this.ignoreRead) return; console.warn(ret);
this.ignoreRead = true; const onBarScanned = this.props.navigation.getParam('onBarScanned');
setTimeout(() => { onBarScanned(ret.data);
this.ignoreRead = false; this.props.navigation.goBack(null);
}, 2000);
this.props.navigation.goBack();
EV(EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, ret.data);
} // end } // end
async componentDidMount() { componentDidMount() {
Permissions.request('camera').then(response => { Permissions.request('camera').then(response => {
// Response is one of: 'authorized', 'denied', 'restricted', or 'undetermined' // Response is one of: 'authorized', 'denied', 'restricted', or 'undetermined'
this.setState({ hasCameraPermission: response === 'authorized' }); this.setState({ hasCameraPermission: response === 'authorized' });
@ -48,13 +42,11 @@ export default class CameraExample extends React.Component {
if (hasCameraPermission === null) { if (hasCameraPermission === null) {
return <View />; return <View />;
} else if (hasCameraPermission === false) { } else if (hasCameraPermission === false) {
alert('BlueWallet does not have permission to use your camera.');
this.props.navigation.goBack(null);
return <View />; return <View />;
} else { } else {
return ( return (
<SafeBlueArea style={{ flex: 1 }}> <SafeBlueArea style={{ flex: 1 }}>
<Camera style={{ flex: 1 }} onBarCodeRead={ret => this.onBarCodeScanned(ret)}> <Camera style={{ flex: 1, justifyContent: 'space-between' }} onBarCodeRead={ret => this.onBarCodeScanned(ret)}>
<TouchableOpacity <TouchableOpacity
style={{ width: 40, height: 80, padding: 14, marginTop: 32 }} style={{ width: 40, height: 80, padding: 14, marginTop: 32 }}
onPress={() => this.props.navigation.goBack(null)} onPress={() => this.props.navigation.goBack(null)}
@ -70,7 +62,8 @@ export default class CameraExample extends React.Component {
CameraExample.propTypes = { CameraExample.propTypes = {
navigation: PropTypes.shape({ navigation: PropTypes.shape({
goBack: PropTypes.function, goBack: PropTypes.func,
dismiss: PropTypes.function, dismiss: PropTypes.func,
getParam: PropTypes.func,
}), }),
}; };

122
screen/settings/about.js

@ -51,73 +51,71 @@ export default class About extends Component {
<BlueTextCentered h4>Always backup your keys</BlueTextCentered> <BlueTextCentered h4>Always backup your keys</BlueTextCentered>
<BlueSpacing20 /> <BlueSpacing20 />
</BlueCard>
<BlueButton <BlueButton
icon={{ icon={{
name: 'mark-github', name: 'mark-github',
type: 'octicon', type: 'octicon',
color: BlueApp.settings.buttonTextColor, color: BlueApp.settings.buttonTextColor,
}} }}
onPress={() => { onPress={() => {
Linking.openURL('https://github.com/BlueWallet/BlueWallet'); Linking.openURL('https://github.com/BlueWallet/BlueWallet');
}} }}
title="github.com/BlueWallet/BlueWallet" title="github.com/BlueWallet/BlueWallet"
/> />
<BlueSpacing20 /> <BlueSpacing20 />
<BlueButton <BlueButton
icon={{ icon={{
name: 'twitter', name: 'twitter',
type: 'font-awesome', type: 'font-awesome',
color: BlueApp.settings.buttonTextColor, color: BlueApp.settings.buttonTextColor,
}} }}
onPress={() => { onPress={() => {
Linking.openURL('https://twitter.com/bluewalletio'); Linking.openURL('https://twitter.com/bluewalletio');
}} }}
title="Follow us on Twitter" title="Follow us on Twitter"
/> />
<BlueSpacing20 /> <BlueSpacing20 />
<BlueButton <BlueButton
icon={{ icon={{
name: 'telegram', name: 'telegram',
type: 'font-awesome', type: 'font-awesome',
color: BlueApp.settings.buttonTextColor, color: BlueApp.settings.buttonTextColor,
}} }}
onPress={() => { onPress={() => {
Linking.openURL('https://t.me/bluewallet'); Linking.openURL('https://t.me/bluewallet');
}} }}
title="Join Telegram chat" title="Join Telegram chat"
/> />
<BlueSpacing20 /> <BlueSpacing20 />
<BlueButton <BlueButton
icon={{ icon={{
name: 'thumbsup', name: 'thumbsup',
type: 'octicon', type: 'octicon',
color: BlueApp.settings.buttonTextColor, color: BlueApp.settings.buttonTextColor,
}} }}
onPress={() => { onPress={() => {
let options = { let options = {
AppleAppID: '1376878040', AppleAppID: '1376878040',
GooglePackageName: 'io.bluewallet.bluewallet', GooglePackageName: 'io.bluewallet.bluewallet',
preferredAndroidMarket: AndroidMarket.Google, preferredAndroidMarket: AndroidMarket.Google,
preferInApp: true, preferInApp: true,
openAppStoreIfInAppFails: true, openAppStoreIfInAppFails: true,
fallbackPlatformURL: 'https://bluewallet.io', fallbackPlatformURL: 'https://bluewallet.io',
}; };
Rate.rate(options, success => { Rate.rate(options, success => {
if (success) { if (success) {
console.warn('User Rated.'); console.log('User Rated.');
} }
}); });
}} }}
title="Rate Blue Wallet" title="Rate BlueWallet"
/> />
<BlueSpacing20 /> <BlueSpacing20 />
<BlueCard>
<BlueText h3>Built with awesome:</BlueText> <BlueText h3>Built with awesome:</BlueText>
<BlueSpacing20 /> <BlueSpacing20 />
<BlueText h4>* React Native</BlueText> <BlueText h4>* React Native</BlueText>

2
screen/settings/language.js

@ -78,7 +78,7 @@ export default class Language extends Component {
renderItem={this.renderItem} renderItem={this.renderItem}
/> />
<BlueCard> <BlueCard>
<BlueText>When selecting a new language, restarting Blue Wallet may be required for the change to take effect.</BlueText> <BlueText>When selecting a new language, restarting BlueWallet may be required for the change to take effect.</BlueText>
</BlueCard> </BlueCard>
</SafeBlueArea> </SafeBlueArea>
); );

6
screen/settings/lightningSettings.js

@ -3,6 +3,7 @@ import { AsyncStorage, View, TextInput, Linking } from 'react-native';
import { AppStorage } from '../../class'; import { AppStorage } from '../../class';
import { BlueLoading, BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueNavigationStyle, BlueText } from '../../BlueComponents'; import { BlueLoading, BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueNavigationStyle, BlueText } from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Button } from 'react-native-elements';
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
@ -56,7 +57,7 @@ export default class LightningSettings extends Component {
<BlueText>{loc.settings.lightning_settings_explain}</BlueText> <BlueText>{loc.settings.lightning_settings_explain}</BlueText>
</BlueCard> </BlueCard>
<BlueButton <Button
icon={{ icon={{
name: 'mark-github', name: 'mark-github',
type: 'octicon', type: 'octicon',
@ -67,6 +68,7 @@ export default class LightningSettings extends Component {
Linking.openURL('https://github.com/BlueWallet/LndHub'); Linking.openURL('https://github.com/BlueWallet/LndHub');
}} }}
title="github.com/BlueWallet/LndHub" title="github.com/BlueWallet/LndHub"
color={BlueApp.settings.buttonTextColor}
buttonStyle={{ buttonStyle={{
backgroundColor: '#FFFFFF', backgroundColor: '#FFFFFF',
}} }}
@ -92,7 +94,7 @@ export default class LightningSettings extends Component {
value={this.state.URI} value={this.state.URI}
onChangeText={text => this.setState({ URI: text })} onChangeText={text => this.setState({ URI: text })}
numberOfLines={1} numberOfLines={1}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33, height: 33 }} style={{ flex: 1, marginHorizontal: 8, minHeight: 36, height: 36 }}
editable={!this.state.isLoading} editable={!this.state.isLoading}
underlineColorAndroid="transparent" underlineColorAndroid="transparent"
/> />

2
screen/transactions/details.js

@ -176,7 +176,7 @@ export default class TransactionsDetails extends Component {
</React.Fragment> </React.Fragment>
)} )}
{this.state.tx.hasOwnProperty('block_height') && ( {this.state.tx.hasOwnProperty('block_height') && this.state.block_height > 0 && (
<React.Fragment> <React.Fragment>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Block Height</BlueText> <BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Block Height</BlueText>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.block_height}</BlueText> <BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.block_height}</BlueText>

3
screen/wallets/add.js

@ -182,9 +182,6 @@ export default class WalletsAdd extends Component {
{!this.state.isLoading ? ( {!this.state.isLoading ? (
<BlueButton <BlueButton
title={loc.wallets.add.create} title={loc.wallets.add.create}
buttonStyle={{
width: width / 1.5,
}}
onPress={() => { onPress={() => {
this.setState( this.setState(
{ isLoading: true }, { isLoading: true },

34
screen/wallets/buyBitcoin.js

@ -1,6 +1,14 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Animated, Linking, StyleSheet, View, TouchableOpacity, Clipboard } from 'react-native'; import { Linking, View } from 'react-native';
import { BlueNavigationStyle, BlueLoading, SafeBlueArea, BlueButton, BlueText, BlueSpacing40 } from '../../BlueComponents'; import {
BlueNavigationStyle,
BlueCopyTextToClipboard,
BlueLoading,
SafeBlueArea,
BlueButton,
BlueText,
BlueSpacing40,
} from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
@ -58,13 +66,6 @@ export default class BuyBitcoin extends Component {
} }
} }
copyToClipboard = () => {
this.setState({ addressText: loc.buyBitcoin.copied }, () => {
Clipboard.setString(this.state.address);
setTimeout(() => this.setState({ addressText: this.state.address }), 1000);
});
};
render() { render() {
console.log('render() receive/details, address,secret=', this.state.address, ',', this.state.secret); console.log('render() receive/details, address,secret=', this.state.address, ',', this.state.secret);
if (this.state.isLoading) { if (this.state.isLoading) {
@ -77,11 +78,7 @@ export default class BuyBitcoin extends Component {
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}> <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}>
<BlueText>{loc.buyBitcoin.tap_your_address}</BlueText> <BlueText>{loc.buyBitcoin.tap_your_address}</BlueText>
<TouchableOpacity onPress={this.copyToClipboard}> <BlueCopyTextToClipboard text={this.state.addressText} />
<Animated.Text style={styles.address} numberOfLines={0}>
{this.state.addressText}
</Animated.Text>
</TouchableOpacity>
<BlueButton <BlueButton
icon={{ icon={{
@ -108,15 +105,6 @@ export default class BuyBitcoin extends Component {
} }
} }
const styles = StyleSheet.create({
address: {
marginVertical: 32,
fontSize: 15,
color: '#9aa0aa',
textAlign: 'center',
},
});
BuyBitcoin.propTypes = { BuyBitcoin.propTypes = {
navigation: PropTypes.shape({ navigation: PropTypes.shape({
goBack: PropTypes.function, goBack: PropTypes.function,

144
screen/wallets/details.js

@ -120,88 +120,88 @@ export default class WalletDetails extends Component {
{loc.wallets.details.type.toLowerCase()} {loc.wallets.details.type.toLowerCase()}
</Text> </Text>
<Text style={{ color: '#81868e', fontWeight: '500', fontSize: 14 }}>{this.state.wallet.typeReadable}</Text> <Text style={{ color: '#81868e', fontWeight: '500', fontSize: 14 }}>{this.state.wallet.typeReadable}</Text>
</BlueCard> <View>
<View> <BlueSpacing20 />
<BlueSpacing20 /> <BlueButton
<BlueButton onPress={() =>
onPress={() => this.props.navigation.navigate('WalletExport', {
this.props.navigation.navigate('WalletExport', { address: this.state.wallet.getAddress(),
address: this.state.wallet.getAddress(), secret: this.state.wallet.getSecret(),
secret: this.state.wallet.getSecret(), })
}) }
} title={loc.wallets.details.export_backup}
title={loc.wallets.details.export_backup} />
/>
<BlueSpacing20 />
{(this.state.wallet.type === HDLegacyBreadwalletWallet.type ||
this.state.wallet.type === HDLegacyP2PKHWallet.type ||
this.state.wallet.type === HDSegwitP2SHWallet.type) && (
<React.Fragment>
<BlueButton
onPress={() =>
this.props.navigation.navigate('WalletXpub', {
secret: this.state.wallet.getSecret(),
})
}
title={loc.wallets.details.show_xpub}
/>
<BlueSpacing20 /> <BlueSpacing20 />
</React.Fragment>
)}
{(this.state.wallet.type === HDLegacyBreadwalletWallet.type || {this.state.wallet.type !== LightningCustodianWallet.type && (
this.state.wallet.type === HDLegacyP2PKHWallet.type ||
this.state.wallet.type === HDSegwitP2SHWallet.type) && (
<React.Fragment>
<BlueButton <BlueButton
icon={{
name: 'shopping-cart',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => onPress={() =>
this.props.navigation.navigate('WalletXpub', { this.props.navigation.navigate('BuyBitcoin', {
address: this.state.wallet.getAddress(),
secret: this.state.wallet.getSecret(), secret: this.state.wallet.getSecret(),
}) })
} }
title={loc.wallets.details.show_xpub} title={loc.wallets.details.buy_bitcoin}
/> />
)}
<BlueSpacing20 />
<BlueSpacing20 /> <TouchableOpacity
</React.Fragment> style={{ alignItems: 'center' }}
)} onPress={() => {
ReactNativeHapticFeedback.trigger('notificationWarning', false);
{this.state.wallet.type !== LightningCustodianWallet.type && ( Alert.alert(
<BlueButton loc.wallets.details.delete + ' ' + loc.wallets.details.title,
icon={{ loc.wallets.details.are_you_sure,
name: 'shopping-cart', [
type: 'font-awesome', {
color: BlueApp.settings.buttonTextColor, text: loc.wallets.details.yes_delete,
}} onPress: async () => {
onPress={() => this.props.navigation.setParams({ isLoading: true });
this.props.navigation.navigate('BuyBitcoin', { this.setState({ isLoading: true }, async () => {
address: this.state.wallet.getAddress(), BlueApp.deleteWallet(this.state.wallet);
secret: this.state.wallet.getSecret(), ReactNativeHapticFeedback.trigger('notificationSuccess', false);
}) await BlueApp.saveToDisk();
} EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
title={loc.wallets.details.buy_bitcoin} EV(EV.enum.WALLETS_COUNT_CHANGED);
/> this.props.navigation.navigate('Wallets');
)} });
<BlueSpacing20 /> },
style: 'destructive',
<TouchableOpacity
style={{ alignItems: 'center' }}
onPress={() => {
ReactNativeHapticFeedback.trigger('notificationWarning', false);
Alert.alert(
loc.wallets.details.delete + ' ' + loc.wallets.details.title,
loc.wallets.details.are_you_sure,
[
{
text: loc.wallets.details.yes_delete,
onPress: async () => {
this.props.navigation.setParams({ isLoading: true });
this.setState({ isLoading: true }, async () => {
BlueApp.deleteWallet(this.state.wallet);
ReactNativeHapticFeedback.trigger('notificationSuccess', false);
await BlueApp.saveToDisk();
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
EV(EV.enum.WALLETS_COUNT_CHANGED);
this.props.navigation.navigate('Wallets');
});
}, },
style: 'destructive', { text: loc.wallets.details.no_cancel, onPress: () => {}, style: 'cancel' },
}, ],
{ text: loc.wallets.details.no_cancel, onPress: () => {}, style: 'cancel' }, { cancelable: false },
], );
{ cancelable: false }, }}
); >
}} <Text style={{ color: '#d0021b', fontSize: 15, fontWeight: '500' }}>{loc.wallets.details.delete}</Text>
> </TouchableOpacity>
<Text style={{ color: '#d0021b', fontSize: 15, fontWeight: '500' }}>{loc.wallets.details.delete}</Text> </View>
</TouchableOpacity> </BlueCard>
</View>
</View> </View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
</SafeBlueArea> </SafeBlueArea>

9
screen/wallets/list.js

@ -19,6 +19,7 @@ import { Icon } from 'react-native-elements';
import { NavigationEvents } from 'react-navigation'; import { NavigationEvents } from 'react-navigation';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import WalletGradient from '../../class/walletGradient';
let EV = require('../../events'); let EV = require('../../events');
let A = require('../../analytics'); let A = require('../../analytics');
/** @type {AppStorage} */ /** @type {AppStorage} */
@ -121,13 +122,13 @@ export default class WalletsList extends Component {
return ''; return '';
} }
handleClick(index, gradients) { handleClick(index) {
console.log('click', index); console.log('click', index);
let wallet = BlueApp.wallets[index]; let wallet = BlueApp.wallets[index];
if (wallet) { if (wallet) {
this.props.navigation.navigate('WalletTransactions', { this.props.navigation.navigate('WalletTransactions', {
wallet: wallet, wallet: wallet,
gradients: gradients, headerColor: WalletGradient.headerColorFor(wallet.type),
}); });
} else { } else {
// if its out of index - this must be last card with incentive to create wallet // if its out of index - this must be last card with incentive to create wallet
@ -298,8 +299,8 @@ export default class WalletsList extends Component {
<BlueHeaderDefaultMain leftText={loc.wallets.list.title} onNewWalletPress={() => this.props.navigation.navigate('AddWallet')} /> <BlueHeaderDefaultMain leftText={loc.wallets.list.title} onNewWalletPress={() => this.props.navigation.navigate('AddWallet')} />
<WalletsCarousel <WalletsCarousel
data={this.state.wallets} data={this.state.wallets}
handleClick={(index, headerColor) => { handleClick={index => {
this.handleClick(index, headerColor); this.handleClick(index);
}} }}
handleLongPress={this.handleLongPress} handleLongPress={this.handleLongPress}
onSnapToItem={index => { onSnapToItem={index => {

40
screen/wallets/reorderWallets.js

@ -4,12 +4,9 @@ import { SafeBlueArea, BlueNavigationStyle } from '../../BlueComponents';
import SortableList from 'react-native-sortable-list'; import SortableList from 'react-native-sortable-list';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { WatchOnlyWallet, LegacyWallet } from '../../class';
import { HDLegacyP2PKHWallet } from '../../class/hd-legacy-p2pkh-wallet';
import { HDLegacyBreadwalletWallet } from '../../class/hd-legacy-breadwallet-wallet';
import { HDSegwitP2SHWallet } from '../../class/hd-segwit-p2sh-wallet';
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import WalletGradient from '../../class/walletGradient';
let EV = require('../../events'); let EV = require('../../events');
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
@ -67,39 +64,6 @@ export default class ReorderWallets extends Component {
} }
item = item.data; item = item.data;
let gradient1 = '#65ceef';
let gradient2 = '#68bbe1';
if (WatchOnlyWallet.type === item.type) {
gradient1 = '#7d7d7d';
gradient2 = '#4a4a4a';
}
if (LegacyWallet.type === item.type) {
gradient1 = '#40fad1';
gradient2 = '#15be98';
}
if (HDLegacyP2PKHWallet.type === item.type) {
gradient1 = '#e36dfa';
gradient2 = '#bd10e0';
}
if (HDLegacyBreadwalletWallet.type === item.type) {
gradient1 = '#fe6381';
gradient2 = '#f99c42';
}
if (HDSegwitP2SHWallet.type === item.type) {
gradient1 = '#c65afb';
gradient2 = '#9053fe';
}
if (LightningCustodianWallet.type === item.type) {
gradient1 = '#f1be07';
gradient2 = '#f79056';
}
return ( return (
<View <View
shadowOpacity={40 / 100} shadowOpacity={40 / 100}
@ -109,7 +73,7 @@ export default class ReorderWallets extends Component {
> >
<LinearGradient <LinearGradient
shadowColor="#000000" shadowColor="#000000"
colors={[gradient1, gradient2]} colors={WalletGradient.gradientsFor(item.type)}
style={{ style={{
padding: 15, padding: 15,
borderRadius: 10, borderRadius: 10,

40
screen/wallets/selectWallet.js

@ -3,12 +3,9 @@ import { View, ActivityIndicator, Image, Text, TouchableOpacity, FlatList } from
import { SafeBlueArea, BlueNavigationStyle, BlueText, BlueSpacing20 } from '../../BlueComponents'; import { SafeBlueArea, BlueNavigationStyle, BlueText, BlueSpacing20 } from '../../BlueComponents';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { WatchOnlyWallet, LegacyWallet } from '../../class';
import { HDLegacyP2PKHWallet } from '../../class/hd-legacy-p2pkh-wallet';
import { HDLegacyBreadwalletWallet } from '../../class/hd-legacy-breadwallet-wallet';
import { HDSegwitP2SHWallet } from '../../class/hd-segwit-p2sh-wallet';
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import WalletGradient from '../../class/walletGradient';
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
let loc = require('../../loc'); let loc = require('../../loc');
@ -36,39 +33,6 @@ export default class SelectWallet extends Component {
} }
_renderItem = ({ item }) => { _renderItem = ({ item }) => {
let gradient1 = '#65ceef';
let gradient2 = '#68bbe1';
if (WatchOnlyWallet.type === item.type) {
gradient1 = '#7d7d7d';
gradient2 = '#4a4a4a';
}
if (LegacyWallet.type === item.type) {
gradient1 = '#40fad1';
gradient2 = '#15be98';
}
if (HDLegacyP2PKHWallet.type === item.type) {
gradient1 = '#e36dfa';
gradient2 = '#bd10e0';
}
if (HDLegacyBreadwalletWallet.type === item.type) {
gradient1 = '#fe6381';
gradient2 = '#f99c42';
}
if (HDSegwitP2SHWallet.type === item.type) {
gradient1 = '#c65afb';
gradient2 = '#9053fe';
}
if (LightningCustodianWallet.type === item.type) {
gradient1 = '#f1be07';
gradient2 = '#f79056';
}
return ( return (
<TouchableOpacity <TouchableOpacity
onPress={() => { onPress={() => {
@ -84,7 +48,7 @@ export default class SelectWallet extends Component {
> >
<LinearGradient <LinearGradient
shadowColor="#000000" shadowColor="#000000"
colors={[gradient1, gradient2]} colors={WalletGradient.gradientsFor(item.type)}
style={{ style={{
padding: 15, padding: 15,
borderRadius: 10, borderRadius: 10,

30
screen/wallets/transactions.js

@ -20,6 +20,7 @@ import {
import { Icon } from 'react-native-elements'; import { Icon } from 'react-native-elements';
import { BitcoinUnit } from '../../models/bitcoinUnits'; import { BitcoinUnit } from '../../models/bitcoinUnits';
import { LightningCustodianWallet } from '../../class'; import { LightningCustodianWallet } from '../../class';
import WalletGradient from '../../class/walletGradient';
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
let loc = require('../../loc'); let loc = require('../../loc');
@ -41,7 +42,7 @@ export default class WalletTransactions extends Component {
</TouchableOpacity> </TouchableOpacity>
), ),
headerStyle: { headerStyle: {
backgroundColor: navigation.getParam('gradients')[0] || '#65ceef', backgroundColor: navigation.getParam('headerColor'),
borderBottomWidth: 0, borderBottomWidth: 0,
elevation: 0, elevation: 0,
shadowRadius: 0, shadowRadius: 0,
@ -145,12 +146,17 @@ export default class WalletTransactions extends Component {
setTimeout(async function() { setTimeout(async function() {
// more responsive // more responsive
let noErr = true; let noErr = true;
let smthChanged = false;
try { try {
/** @type {LegacyWallet} */ /** @type {LegacyWallet} */
let wallet = that.state.wallet; let wallet = that.state.wallet;
const oldBalance = wallet.getBalance();
await wallet.fetchBalance(); await wallet.fetchBalance();
if (oldBalance !== wallet.getBalance()) smthChanged = true;
let start = +new Date(); let start = +new Date();
const oldTxLen = wallet.getTransactions().length;
await wallet.fetchTransactions(); await wallet.fetchTransactions();
if (oldTxLen !== wallet.getTransactions().length) smthChanged = true;
if (wallet.fetchPendingTransactions) { if (wallet.fetchPendingTransactions) {
await wallet.fetchPendingTransactions(); await wallet.fetchPendingTransactions();
} }
@ -163,7 +169,8 @@ export default class WalletTransactions extends Component {
noErr = false; noErr = false;
console.warn(err); console.warn(err);
} }
if (noErr) { if (noErr && smthChanged) {
console.log('saving to disk');
await BlueApp.saveToDisk(); // caching await BlueApp.saveToDisk(); // caching
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED); // let other components know they should redraw EV(EV.enum.TRANSACTIONS_COUNT_CHANGED); // let other components know they should redraw
} }
@ -195,9 +202,8 @@ export default class WalletTransactions extends Component {
} }
renderWalletHeader = () => { renderWalletHeader = () => {
const gradients = this.props.navigation.getParam('gradients') || ['#65ceef', '#68bbe1'];
return ( return (
<LinearGradient colors={[gradients[0], gradients[1]]} style={{ padding: 15, minHeight: 164 }}> <LinearGradient colors={WalletGradient.gradientsFor(this.state.wallet.type)} style={{ padding: 15, minHeight: 164 }}>
<Image <Image
source={ source={
(LightningCustodianWallet.type === this.state.wallet.type && require('../../img/lnd-shape.png')) || (LightningCustodianWallet.type === this.state.wallet.type && require('../../img/lnd-shape.png')) ||
@ -366,27 +372,26 @@ export default class WalletTransactions extends Component {
{(() => { {(() => {
if (this.state.showManageFundsSmallButton) { if (this.state.showManageFundsSmallButton) {
return ( return (
<React.Fragment> <View style={{ justifyContent: 'space-between', alignContent: 'center', flexDirection: 'row', marginVertical: 8 }}>
<TouchableOpacity <TouchableOpacity
style={{ alignSelf: 'flex-start', left: 10, top: 15, flexDirection: 'row' }} style={{ left: 10, flexDirection: 'row', flex: 1, alignItems: 'center' }}
onPress={() => { onPress={() => {
console.log('navigating to LappBrowser'); console.log('navigating to LappBrowser');
navigate('LappBrowser', { fromSecret: this.state.wallet.getSecret(), fromWallet: this.state.wallet }); navigate('LappBrowser', { fromSecret: this.state.wallet.getSecret(), fromWallet: this.state.wallet });
}} }}
> >
<BlueText style={{ fontWeight: '600', fontSize: 16 }}>{'marketplace'}</BlueText> <BlueText style={{ fontWeight: '600', fontSize: 16 }}>marketplace</BlueText>
<Icon <Icon
style={{ position: 'relative' }}
name="shopping-cart" name="shopping-cart"
type="font-awesome" type="font-awesome"
size={14} size={14}
color={BlueApp.settings.foregroundColor} color={BlueApp.settings.foregroundColor}
iconStyle={{ left: 5 }} iconStyle={{ left: 5, top: 2 }}
/> />
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity
style={{ alignSelf: 'flex-end', right: 10, top: -5, flexDirection: 'row' }} style={{ marginRight: 10, flexDirection: 'row', alignItems: 'center' }}
onPress={() => { onPress={() => {
console.log('navigating to', this.state.wallet.getLabel()); console.log('navigating to', this.state.wallet.getLabel());
navigate('ManageFunds', { fromWallet: this.state.wallet }); navigate('ManageFunds', { fromWallet: this.state.wallet });
@ -394,15 +399,14 @@ export default class WalletTransactions extends Component {
> >
<BlueText style={{ fontWeight: '600', fontSize: 16 }}>{loc.lnd.title}</BlueText> <BlueText style={{ fontWeight: '600', fontSize: 16 }}>{loc.lnd.title}</BlueText>
<Icon <Icon
style={{ position: 'relative' }}
name="link" name="link"
type="font-awesome" type="font-awesome"
size={14} size={14}
color={BlueApp.settings.foregroundColor} color={BlueApp.settings.foregroundColor}
iconStyle={{ left: 5, transform: [{ rotate: '90deg' }] }} iconStyle={{ left: 5, top: 2, transform: [{ rotate: '90deg' }] }}
/> />
</TouchableOpacity> </TouchableOpacity>
</React.Fragment> </View>
); );
} }
})()} })()}

2
screen/wallets/walletMigrate.js

@ -82,7 +82,7 @@ export default class WalletMigrate extends Component {
render() { render() {
return ( return (
<View style={{ flex: 1, justifyContent: 'center', alignContent: 'center' }}> <View style={{ flex: 1, justifyContent: 'center', alignContent: 'center', backgroundColor: '#ffffff' }}>
<ActivityIndicator /> <ActivityIndicator />
</View> </View>
); );

17
screen/wallets/xpub.js

@ -1,7 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Dimensions, Platform, ActivityIndicator, View, Clipboard, Animated, TouchableOpacity } from 'react-native'; import { Dimensions, Platform, ActivityIndicator, View } from 'react-native';
import { QRCode as QRSlow } from 'react-native-custom-qr-codes'; import { QRCode as QRSlow } from 'react-native-custom-qr-codes';
import { BlueSpacing20, SafeBlueArea, BlueText, BlueNavigationStyle } from '../../BlueComponents'; import { BlueSpacing20, SafeBlueArea, BlueText, BlueNavigationStyle, BlueCopyTextToClipboard } from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const QRFast = require('react-native-qrcode'); const QRFast = require('react-native-qrcode');
/** @type {AppStorage} */ /** @type {AppStorage} */
@ -49,13 +49,6 @@ export default class WalletXpub extends Component {
}, 1000); }, 1000);
} }
copyToClipboard = () => {
this.setState({ xpubText: loc.wallets.xpub.copiedToClipboard }, () => {
Clipboard.setString(this.state.xpub);
setTimeout(() => this.setState({ xpubText: this.state.xpub }), 1000);
});
};
onLayout = () => { onLayout = () => {
const { height } = Dimensions.get('window'); const { height } = Dimensions.get('window');
this.setState({ qrCodeHeight: height > width ? width - 40 : width / 2 }); this.setState({ qrCodeHeight: height > width ? width - 40 : width / 2 });
@ -110,11 +103,7 @@ export default class WalletXpub extends Component {
} }
})()} })()}
<BlueSpacing20 /> <BlueSpacing20 />
<TouchableOpacity onPress={this.copyToClipboard}> <BlueCopyTextToClipboard text={this.state.xpubText} />
<Animated.Text style={{ paddingHorizontal: 8, textAlign: 'center' }} numberOfLines={0}>
{this.state.xpubText}
</Animated.Text>
</TouchableOpacity>
</View> </View>
</SafeBlueArea> </SafeBlueArea>
); );

Loading…
Cancel
Save