Browse Source

Merge branch 'master' into localtrader-highlight

master
Nuno Coelho 5 years ago
committed by GitHub
parent
commit
4da74c5f26
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      android/app/build.gradle
  2. 6
      blue_modules/bip38/scryptsy/lib/scrypt.js
  3. 15
      class/abstract-hd-electrum-wallet.js
  4. 4
      class/abstract-hd-wallet.js
  5. 4
      class/abstract-wallet.js
  6. 65
      class/hd-legacy-electrum-seed-p2pkh-wallet.js
  7. 1
      class/index.js
  8. 3
      class/lightning-custodian-wallet.js
  9. 9
      class/walletImport.js
  10. 18
      class/watch-only-wallet.js
  11. 2
      ios/BlueWallet/Info.plist
  12. 2
      ios/BlueWalletWatch Extension/Info.plist
  13. 2
      ios/BlueWalletWatch/Info.plist
  14. 14
      ios/Podfile.lock
  15. 2
      ios/TodayExtension/Info.plist
  16. 25
      ios/fastlane/metadata/en-US/release_notes.txt
  17. 146
      package-lock.json
  18. 11
      package.json
  19. 345
      screen/receive/details.js
  20. 66
      screen/wallets/buyBitcoin.js
  21. 2
      screen/wallets/details.js
  22. 16
      screen/wallets/transactions.js
  23. 15
      tests/e2e/bluewallet.spec.js
  24. 18
      tests/e2e/selftest.spec.js
  25. 18
      tests/integration/hd-segwit-bech32-wallet.test.js
  26. 4
      tests/integration/hd-segwit-p2sh-wallet.test.js
  27. 30
      tests/unit/hd-legacy-electrum-seed-p2pkh-wallet.test.js

2
android/app/build.gradle

@ -140,7 +140,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "5.3.1"
versionName "5.3.3"
multiDexEnabled true
missingDimensionStrategy 'react-native-camera', 'general'
testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type

6
blue_modules/bip38/scryptsy/lib/scrypt.js

@ -10,13 +10,13 @@ async function scrypt (key, salt, N, r, p, dkLen, progressCallback) {
if (N > MAX_VALUE / 128 / r) throw Error('Parameter N is too large')
if (r > MAX_VALUE / 128 / p) throw Error('Parameter r is too large')
var XY = new Buffer(256 * r)
var V = new Buffer(128 * r * N)
var XY = Buffer.alloc(256 * r)
var V = Buffer.alloc(128 * r * N)
// pseudo global
var B32 = new Int32Array(16) // salsa20_8
var x = new Int32Array(16) // salsa20_8
var _X = new Buffer(64) // blockmix_salsa8
var _X = Buffer.alloc(64) // blockmix_salsa8
// pseudo global
var B = pbkdf2.pbkdf2Sync(key, salt, 1, p * 128 * r, 'sha256')

15
class/abstract-hd-electrum-wallet.js

@ -12,6 +12,9 @@ const reverse = require('buffer-reverse');
const { RNRandomBytes } = NativeModules;
/**
* Electrum - means that it utilizes Electrum protocol for blockchain data
*/
export class AbstractHDElectrumWallet extends AbstractHDWallet {
static type = 'abstract';
static typeReadable = 'abstract';
@ -986,4 +989,16 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
if (broadcast.indexOf('successfully') !== -1) return true;
return broadcast.length === 64; // this means return string is txid (precise length), so it was broadcasted ok
}
/**
* Probes zero address in external hierarchy for transactions, if there are any returns TRUE.
* Zero address is a pretty good indicator, since its a first one to fund the wallet. How can you use the wallet and
* not fund it first?
*
* @returns {Promise<boolean>}
*/
async wasEverUsed() {
let txs = await BlueElectrum.getTransactionsByAddress(this._getExternalAddressByIndex(0));
return txs.length > 0;
}
}

4
class/abstract-hd-wallet.js

@ -23,6 +23,10 @@ export class AbstractHDWallet extends LegacyWallet {
this.gap_limit = 20;
}
getNextFreeAddressIndex() {
return this.next_free_address_index;
}
prepareForSerialization() {
// deleting structures that cant be serialized
delete this._node0;

4
class/abstract-wallet.js

@ -171,4 +171,8 @@ export class AbstractWallet {
useWithHardwareWalletEnabled() {
return false;
}
async wasEverUsed() {
throw new Error('Not implemented');
}
}

65
class/hd-legacy-electrum-seed-p2pkh-wallet.js

@ -0,0 +1,65 @@
import { HDLegacyP2PKHWallet } from './';
const bitcoin = require('bitcoinjs-lib');
const mn = require('electrum-mnemonic');
/**
* ElectrumSeed means that instead of BIP39 seed format it works with the format invented by Electrum wallet. Otherwise
* its a regular HD wallet that has all the properties of parent class.
*
* @see https://electrum.readthedocs.io/en/latest/seedphrase.html
*/
export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet {
static type = 'HDlegacyElectrumSeedP2PKH';
static typeReadable = 'HD Legacy Electrum (BIP32 P2PKH)';
validateMnemonic() {
try {
mn.mnemonicToSeedSync(this.secret);
return true;
} catch (_) {
return false;
}
}
getXpub() {
if (this._xpub) {
return this._xpub; // cache hit
}
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret));
this._xpub = root.toBase58();
return this._xpub;
}
_getInternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
const node = bitcoin.bip32.fromBase58(this.getXpub());
const address = bitcoin.payments.p2pkh({
pubkey: node.derive(1).derive(index).publicKey,
}).address;
return (this.internal_addresses_cache[index] = address);
}
_getExternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
const node = bitcoin.bip32.fromBase58(this.getXpub());
const address = bitcoin.payments.p2pkh({
pubkey: node.derive(0).derive(index).publicKey,
}).address;
return (this.external_addresses_cache[index] = address);
}
_getWIFByIndex(internal, index) {
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret));
const path = `m/${internal ? 1 : 0}/${index}`;
const child = root.derivePath(path);
return child.toWIF();
}
}

1
class/index.js

@ -13,3 +13,4 @@ export * from './abstract-hd-wallet';
export * from './hd-segwit-bech32-wallet';
export * from './hd-segwit-bech32-transaction';
export * from './placeholder-wallet';
export * from './hd-legacy-electrum-seed-p2pkh-wallet';

3
class/lightning-custodian-wallet.js

@ -368,7 +368,8 @@ export class LightningCustodianWallet extends LegacyWallet {
}
async getAddressAsync() {
return this.fetchBtcAddress();
await this.fetchBtcAddress();
return this.getAddress();
}
async allowOnchainAddress() {

9
class/walletImport.js

@ -10,6 +10,7 @@ import {
LightningCustodianWallet,
PlaceholderWallet,
SegwitBech32Wallet,
HDLegacyElectrumSeedP2PKHWallet,
} from '../class';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
const EV = require('../events');
@ -97,6 +98,7 @@ export default class WalletImport {
// 1. check if its HDSegwitP2SHWallet (BIP49)
// 2. check if its HDLegacyP2PKHWallet (BIP44)
// 3. check if its HDLegacyBreadwalletWallet (no BIP, just "m/0")
// 3.1 check HD Electrum legacy
// 4. check if its Segwit WIF (P2SH)
// 5. check if its Legacy WIF
// 6. check if its address (watch-only wallet)
@ -202,6 +204,13 @@ export default class WalletImport {
}
}
let hdElectrumSeedLegacy = new HDLegacyElectrumSeedP2PKHWallet();
hdElectrumSeedLegacy.setSecret(importText);
if (await hdElectrumSeedLegacy.wasEverUsed()) {
// not fetching txs or balances, fuck it, yolo, life is too short
return WalletImport._saveWallet(hdElectrumSeedLegacy);
}
let hd2 = new HDSegwitP2SHWallet();
hd2.setSecret(importText);
if (hd2.validateMnemonic()) {

18
class/watch-only-wallet.js

@ -35,7 +35,9 @@ export class WatchOnlyWallet extends LegacyWallet {
}
getAddress() {
return this.secret;
if (this.isAddressValid(this.secret)) return this.secret; // handling case when there is an XPUB there
if (this._hdWalletInstance) throw new Error('Should not be used in watch-only HD wallets');
throw new Error('Not initialized');
}
createTx(utxos, amount, fee, toAddress, memo) {
@ -119,6 +121,11 @@ export class WatchOnlyWallet extends LegacyWallet {
throw new Error('Not initialized');
}
getNextFreeAddressIndex() {
if (this._hdWalletInstance) return this._hdWalletInstance.next_free_address_index;
throw new Error('Not initialized');
}
async getChangeAddressAsync() {
if (this._hdWalletInstance) return this._hdWalletInstance.getChangeAddressAsync();
throw new Error('Not initialized');
@ -183,6 +190,15 @@ export class WatchOnlyWallet extends LegacyWallet {
return this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub');
}
weOwnAddress(address) {
if (this.isHd()) {
if (this._hdWalletInstance) return this._hdWalletInstance.weOwnAddress(address);
throw new Error('Not initialized');
}
return this.getAddress() === address;
}
allowHodlHodlTrading() {
return this.isHd();
}

2
ios/BlueWallet/Info.plist

@ -48,7 +48,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>5.3.1</string>
<string>5.3.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>

2
ios/BlueWalletWatch Extension/Info.plist

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>5.3.1</string>
<string>5.3.3</string>
<key>CFBundleVersion</key>
<string>239</string>
<key>CLKComplicationPrincipalClass</key>

2
ios/BlueWalletWatch/Info.plist

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>5.3.1</string>
<string>5.3.3</string>
<key>CFBundleVersion</key>
<string>239</string>
<key>UISupportedInterfaceOrientations</key>

14
ios/Podfile.lock

@ -192,6 +192,8 @@ PODS:
- React-jsinspector (0.61.5)
- react-native-biometrics (2.0.0):
- React
- react-native-blue-crypto (1.0.0):
- React
- react-native-blur (0.8.0):
- React
- react-native-camera (3.21.0):
@ -255,7 +257,7 @@ PODS:
- React
- RemobileReactNativeQrcodeLocalImage (1.0.4):
- React
- RNCAsyncStorage (1.7.1):
- RNCAsyncStorage (1.8.1):
- React
- RNDefaultPreference (1.4.1):
- React
@ -263,7 +265,7 @@ PODS:
- React
- RNFS (2.16.6):
- React
- RNGestureHandler (1.5.6):
- RNGestureHandler (1.6.1):
- React
- RNHandoff (0.0.3):
- React
@ -315,6 +317,7 @@ DEPENDENCIES:
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- react-native-biometrics (from `../node_modules/react-native-biometrics`)
- react-native-blue-crypto (from `../node_modules/react-native-blue-crypto`)
- "react-native-blur (from `../node_modules/@react-native-community/blur`)"
- react-native-camera (from `../node_modules/react-native-camera`)
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
@ -398,6 +401,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
react-native-biometrics:
:path: "../node_modules/react-native-biometrics"
react-native-blue-crypto:
:path: "../node_modules/react-native-blue-crypto"
react-native-blur:
:path: "../node_modules/@react-native-community/blur"
react-native-camera:
@ -496,6 +501,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386
React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0
react-native-biometrics: c892904948a32295b128f633bcc11eda020645c5
react-native-blue-crypto: 23f1558ad3d38d7a2edb7e2f6ed1bc520ed93e56
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-camera: a9749fa0c1162cdb2f56b93a5d97a72ebd56694f
react-native-document-picker: e3516aff0dcf65ee0785d9bcf190eb10e2261154
@ -517,11 +523,11 @@ SPEC CHECKSUMS:
ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
ReactNativePrivacySnapshot: cc295e45dc22810e9ff2c93380d643de20a77015
RemobileReactNativeQrcodeLocalImage: 57aadc12896b148fb5e04bc7c6805f3565f5c3fa
RNCAsyncStorage: 8539fc80a0075fcc9c8e2dff84cd22dc5bf1dacf
RNCAsyncStorage: 00bdf63f7f1e0f11d3323533dba4f222e58bf092
RNDefaultPreference: 12d246dd2222e66dadcd76cc1250560663befc3a
RNDeviceInfo: 12faae605ba42a1a5041c3c41a77834bc23f049d
RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df
RNGestureHandler: 911d3b110a7a233a34c4f800e7188a84b75319c6
RNGestureHandler: 8f09cd560f8d533eb36da5a6c5a843af9f056b38
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
RNRate: d44a8bca6ee08f5d890ecccddaec2810955ffbb3

2
ios/TodayExtension/Info.plist

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>5.3.1</string>
<string>5.3.3</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>

25
ios/fastlane/metadata/en-US/release_notes.txt

@ -1,3 +1,13 @@
v5.3.2
======
* ADD: Handoff Receive Address
* FIX: BIP38 import support (password-protected private key from paper wallet)
* FIX: iOS widget not working
* FIX: negative confirmation num for electrum personal server
* FIX: Keyboard avoiding
* FIX: allow LocalTrader for watch-only HD wallets
v5.3.0
======
@ -50,18 +60,3 @@ v5.0.0
* FIX: lnurl scan to receive is not returning the correct view (closes #828)
* FIX: watch-only delete wallet doesnt have confirmation now
* FIX: typo in spanish
v4.9.4
======
* REL: ver bump 4.9.4
* FIX: Lint
* FIX: Listen to lnd invoice changes
* FIX: There's two refresh icons on the main view, when you pull-to-refresh
* FIX: Crash on scan in wallets import when closing the view
* FIX: Allow walet change on invoice creation
* FIX: Allow wallet text input to be empty for new wallet naming
* FIX: LN Invoice amount renders 0
* FIX: Handle both chains
* FIX: deeplinking (safello etc)
* DEL: Remove alerts from main list

146
package-lock.json

@ -1,6 +1,6 @@
{
"name": "bluewallet",
"version": "5.3.1",
"version": "5.3.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -1341,9 +1341,9 @@
}
},
"@react-native-community/async-storage": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@react-native-community/async-storage/-/async-storage-1.7.1.tgz",
"integrity": "sha512-/oX/x+EU4xNaqIaC/epVKzO8XghzImPA7l8cLz3USEFmtFiXFjBbTeeIFjjEm/u4/cv38Wi1xMEa10PHIWygRg=="
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@react-native-community/async-storage/-/async-storage-1.8.1.tgz",
"integrity": "sha512-MA1fTp4SB7OOtDmNAwds6jIpiwwty1NIoFboWjEWkoyWW35zIuxlhHxD4joSy21aWEzUVwvv6JJ2hSsP/HTb7A=="
},
"@react-native-community/blur": {
"version": "3.4.1",
@ -1556,102 +1556,112 @@
"from": "git+https://github.com/BlueWallet/react-native-qrcode-local-image.git"
},
"@sentry/browser": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.15.0.tgz",
"integrity": "sha512-9sgqWGaoT5jb3vk8sgQ1bz1LzhUf3oKoDMp/c6vX0reuA6Vz+/jwOC7a/FPWtQir2PwRJfbak2QOxw8W6Mwa3g==",
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.15.4.tgz",
"integrity": "sha512-l/auT1HtZM3KxjCGQHYO/K51ygnlcuOrM+7Ga8gUUbU9ZXDYw6jRi0+Af9aqXKmdDw1naNxr7OCSy6NBrLWVZw==",
"requires": {
"@sentry/core": "5.15.0",
"@sentry/types": "5.15.0",
"@sentry/utils": "5.15.0",
"@sentry/core": "5.15.4",
"@sentry/types": "5.15.4",
"@sentry/utils": "5.15.4",
"tslib": "^1.9.3"
}
},
"@sentry/cli": {
"version": "1.51.1",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.51.1.tgz",
"integrity": "sha512-JKYdoE5Pz8AQaupVQW3XOFTuff1UyLtxhcFzQPwQXiZEyXabo00dosX3YkqPKUwMv7LgxTOH23SJU8HwStAmFw==",
"version": "1.52.1",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.52.1.tgz",
"integrity": "sha512-XocAy3opa7bxWEbYQ9R/whbIb4BAX2YHXvfMoCwZRzLRy9cf85FYGQCMi8JA7wQd5PBmcxUh31AxcX7jAfMPCQ==",
"requires": {
"fs-copy-file-sync": "^1.1.1",
"https-proxy-agent": "^4.0.0",
"mkdirp": "^0.5.1",
"mkdirp": "^0.5.4",
"node-fetch": "^2.1.2",
"progress": "2.0.0",
"proxy-from-env": "^1.0.0"
},
"dependencies": {
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
}
}
},
"@sentry/core": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.15.0.tgz",
"integrity": "sha512-ujwHMwinPwuADoIBFjh1BiC6Li7RpEG3Mmo0MxOqKm7xKngkRUk5uH5e36roORnx+ngr/3NCe80QuvSqK7gQsw==",
"requires": {
"@sentry/hub": "5.15.0",
"@sentry/minimal": "5.15.0",
"@sentry/types": "5.15.0",
"@sentry/utils": "5.15.0",
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.15.4.tgz",
"integrity": "sha512-9KP4NM4SqfV5NixpvAymC7Nvp36Zj4dU2fowmxiq7OIbzTxGXDhwuN/t0Uh8xiqlkpkQqSECZ1OjSFXrBldetQ==",
"requires": {
"@sentry/hub": "5.15.4",
"@sentry/minimal": "5.15.4",
"@sentry/types": "5.15.4",
"@sentry/utils": "5.15.4",
"tslib": "^1.9.3"
}
},
"@sentry/hub": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.15.0.tgz",
"integrity": "sha512-wIDcaIuaYpg+Ma01NfFQTOnZLDCKSx2D06TTBqlo93WfMFNgyEgdMbU5Fk1PFZzjj2AMtzlc9DJzAfvt1hZx3w==",
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.15.4.tgz",
"integrity": "sha512-1XJ1SVqadkbUT4zLS0TVIVl99si7oHizLmghR8LMFl5wOkGEgehHSoOydQkIAX2C7sJmaF5TZ47ORBHgkqclUg==",
"requires": {
"@sentry/types": "5.15.0",
"@sentry/utils": "5.15.0",
"@sentry/types": "5.15.4",
"@sentry/utils": "5.15.4",
"tslib": "^1.9.3"
}
},
"@sentry/integrations": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-5.15.0.tgz",
"integrity": "sha512-50OLHL26EMOCeyVlwLk9P7YocaUF02RYuCD3BPofTswgXSDe61Qy3ntT/RuOTiUV1sjmpc4FjDBJdtp/miFfOg==",
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-5.15.4.tgz",
"integrity": "sha512-GaEVQf4R+WBJvTOGptOHIFSylnH1JAvBQZ7c45jGIDBp+upqzeI67KD+HoM4sSNT2Y2i8DLTJCWibe34knz5Kw==",
"requires": {
"@sentry/types": "5.15.0",
"@sentry/utils": "5.15.0",
"@sentry/types": "5.15.4",
"@sentry/utils": "5.15.4",
"tslib": "^1.9.3"
}
},
"@sentry/minimal": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.15.0.tgz",
"integrity": "sha512-VBkMfR6ahmuJrx4V51BNYd6XzGZ7GB8sfnBufMzqK6MsKe+g5oSyXeqHFd4oFC0co0YlFIw7IphF2JZLwVs0zA==",
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.15.4.tgz",
"integrity": "sha512-GL4GZ3drS9ge+wmxkHBAMEwulaE7DMvAEfKQPDAjg2p3MfcCMhAYfuY4jJByAC9rg9OwBGGehz7UmhWMFjE0tw==",
"requires": {
"@sentry/hub": "5.15.0",
"@sentry/types": "5.15.0",
"@sentry/hub": "5.15.4",
"@sentry/types": "5.15.4",
"tslib": "^1.9.3"
}
},
"@sentry/react-native": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-1.3.1.tgz",
"integrity": "sha512-EsR3UW9hQJ7VHsS7nL5mKFa7iMQAHoelXiDJ94k9Y/aFm7pMA6Knpj4Gqo6rMqd2opGXLfO40b65VL8mJ4JDXw==",
"requires": {
"@sentry/browser": "^5.12.1",
"@sentry/core": "^5.12.0",
"@sentry/integrations": "^5.12.0",
"@sentry/types": "^5.12.0",
"@sentry/utils": "^5.12.0",
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-1.3.5.tgz",
"integrity": "sha512-shgh3Nq9f43CVvrFgb8wBr5sdOWX0T2j0GQ2h1+JR4YvB7vGjIq1xCEZWql4GeAiImnzg15JNnsLAsgOWxF84A==",
"requires": {
"@sentry/browser": "^5.15.2",
"@sentry/core": "^5.15.2",
"@sentry/integrations": "^5.15.2",
"@sentry/types": "^5.15.2",
"@sentry/utils": "^5.15.2",
"@sentry/wizard": "^1.1.1"
}
},
"@sentry/types": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.15.0.tgz",
"integrity": "sha512-MC96wUAHhzRuH3xo4Qd+EXTOap8+d+SWbAdLBukScxuwhOSY/HNRh1TW17CuAu7s1oXa7xxO2ZCdyamSZinIiQ=="
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.15.4.tgz",
"integrity": "sha512-quPHPpeAuwID48HLPmqBiyXE3xEiZLZ5D3CEbU3c3YuvvAg8qmfOOTI6z4Z3Eedi7flvYpnx3n7N3dXIEz30Eg=="
},
"@sentry/utils": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.15.0.tgz",
"integrity": "sha512-td+wSBdVUPO3mEPcEHZwJiVEQ0+wplJCHBvM1PHqwQd+miB2mQAaiSkzdAAHzUpTeqPBI3rzjWPn59WkCcVF5Q==",
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.15.4.tgz",
"integrity": "sha512-lO8SLBjrUDGADl0LOkd55R5oL510d/1SaI08/IBHZCxCUwI4TiYo5EPECq8mrj3XGfgCyq9osw33bymRlIDuSQ==",
"requires": {
"@sentry/types": "5.15.0",
"@sentry/types": "5.15.4",
"tslib": "^1.9.3"
}
},
"@sentry/wizard": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@sentry/wizard/-/wizard-1.1.1.tgz",
"integrity": "sha512-P07iMkndcT865SS4WMEBlzda4MXg1tOzPe8kg9p8vQlppWZ4ZyCgs5cDyTZk3b7HLffk+YPL4S116mfwzVdP7w==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@sentry/wizard/-/wizard-1.1.2.tgz",
"integrity": "sha512-z7Ck5uli91omT+xSGzOXA3XNj0IUFritzZ5Qjf/KcuSUZuyqLCH2olAR6pXl262tC6kBbWw/xb+AOgPsAQ7u/Q==",
"requires": {
"@sentry/cli": "^1.51.0",
"chalk": "^2.4.1",
@ -4460,6 +4470,16 @@
"version": "git+https://github.com/BlueWallet/rn-electrum-client.git#2a5bb11dd9a8d89f328049d9ed59bce49d88a15d",
"from": "git+https://github.com/BlueWallet/rn-electrum-client.git#2a5bb11dd9a8d89f328049d9ed59bce49d88a15d"
},
"electrum-mnemonic": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/electrum-mnemonic/-/electrum-mnemonic-1.0.7.tgz",
"integrity": "sha512-qi53UYOr+yxCBDVYzKm6a6yzomhHiZR43EuH2I3+teqizj2fapMDh0AyixB3id7ZnFCsiGnOxFqN4TzuzQy5dQ==",
"requires": {
"create-hmac": "^1.1.7",
"pbkdf2": "^3.0.17",
"randombytes": "^2.1.0"
}
},
"elliptic": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
@ -6526,13 +6546,6 @@
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE="
},
"hammerjs": {
"version": "git+https://github.com/naver/hammer.js.git#54bc698b25edd6e1b76ca975ebaced5ce0467d51",
"from": "git+https://github.com/naver/hammer.js.git",
"requires": {
"@types/hammerjs": "^2.0.36"
}
},
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@ -11640,12 +11653,11 @@
}
},
"react-native-gesture-handler": {
"version": "1.5.6",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.5.6.tgz",
"integrity": "sha512-z2jLUkRiRc0PBAC9UcXYkqy3VUzBG0cYQAGMsDHsd90JgrzudHAFRJV9fvFm18wNauFTNnJievjZ0C3rI2ydhw==",
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.6.1.tgz",
"integrity": "sha512-gQgIKhDiYf754yzhhliagLuLupvGb6ZyBdzYzr7aus3Fyi87TLOw63ers+r4kGw0h26oAWTAdHd34JnF4NeL6Q==",
"requires": {
"@egjs/hammerjs": "^2.0.17",
"hammerjs": "git+https://github.com/naver/hammer.js.git",
"hoist-non-react-statics": "^2.3.1",
"invariant": "^2.2.4",
"prop-types": "^15.7.2"

11
package.json

@ -1,6 +1,6 @@
{
"name": "bluewallet",
"version": "5.3.1",
"version": "5.3.3",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.6.2",
@ -45,6 +45,7 @@
"e2e:debug": "(test -f android/app/build/outputs/apk/debug/app-debug.apk && test -f android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk) || detox build -c android.emu.debug; detox test -c android.emu.debug",
"lint": "./node_modules/.bin/eslint *.js screen/**/*.js screen/ class/ models/ loc/ tests/integration/ tests/e2e/ tests/unit/",
"lint:fix": "./node_modules/.bin/eslint *.js screen/**/*.js screen/ class/ models/ loc/ tests/integration/ tests/e2e/ tests/unit/ --fix",
"lint:quickfix": "git status --porcelain | grep -v '\\.json' | grep '\\.js' --color=never | awk '{print $2}' | xargs ./node_modules/.bin/eslint --fix; exit 0",
"unit": "node node_modules/jest/bin/jest.js tests/unit/*"
},
"jest": {
@ -58,12 +59,12 @@
},
"dependencies": {
"@babel/preset-env": "7.8.7",
"@react-native-community/async-storage": "1.7.1",
"@react-native-community/async-storage": "1.8.1",
"@react-native-community/blur": "3.4.1",
"@react-native-community/slider": "2.0.8",
"@react-native-community/viewpager": "3.3.0",
"@remobile/react-native-qrcode-local-image": "git+https://github.com/BlueWallet/react-native-qrcode-local-image.git",
"@sentry/react-native": "1.3.1",
"@sentry/react-native": "1.3.5",
"amplitude-js": "5.9.0",
"bech32": "1.1.3",
"bignumber.js": "9.0.0",
@ -79,6 +80,7 @@
"dayjs": "1.8.23",
"ecurve": "1.0.6",
"electrum-client": "git+https://github.com/BlueWallet/rn-electrum-client.git#2a5bb11dd9a8d89f328049d9ed59bce49d88a15d",
"electrum-mnemonic": "1.0.7",
"eslint-config-prettier": "6.10.0",
"eslint-config-standard": "12.0.0",
"eslint-config-standard-react": "7.0.2",
@ -90,6 +92,7 @@
"lottie-react-native": "3.1.1",
"node-libs-react-native": "1.0.3",
"path-browserify": "1.0.0",
"pbkdf2": "3.0.17",
"prettier": "1.19.1",
"process": "0.11.10",
"prop-types": "15.7.2",
@ -105,7 +108,7 @@
"react-native-elements": "0.19.0",
"react-native-flexi-radio-button": "0.2.2",
"react-native-fs": "2.16.6",
"react-native-gesture-handler": "1.5.6",
"react-native-gesture-handler": "1.6.1",
"react-native-handoff": "git+https://github.com/marcosrdz/react-native-handoff.git",
"react-native-haptic-feedback": "1.9.0",
"react-native-image-picker": "1.1.0",

345
screen/receive/details.js

@ -1,6 +1,7 @@
import React, { Component } from 'react';
import React, { useEffect, useState, useCallback } from 'react';
import { View, InteractionManager, Platform, TextInput, KeyboardAvoidingView, Keyboard, StyleSheet, ScrollView } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import { useNavigation, useNavigationParam } from 'react-navigation-hooks';
import bip21 from 'bip21';
import {
BlueLoading,
@ -15,7 +16,6 @@ import {
BlueSpacing20,
BlueAlertWalletExportReminder,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
import Privacy from '../../Privacy';
import Share from 'react-native-share';
import { Chain, BitcoinUnit } from '../../models/bitcoinUnits';
@ -26,120 +26,113 @@ import Handoff from 'react-native-handoff';
const BlueApp = require('../../BlueApp');
const loc = require('../../loc');
export default class ReceiveDetails extends Component {
static navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
title: loc.receive.header,
headerLeft: null,
});
const ReceiveDetails = () => {
const secret = useNavigationParam('secret');
const [wallet, setWallet] = useState();
const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false);
const [address, setAddress] = useState('');
const [customLabel, setCustomLabel] = useState();
const [customAmount, setCustomAmount] = useState(0);
const [bip21encoded, setBip21encoded] = useState();
const [qrCodeSVG, setQrCodeSVG] = useState();
const [isCustom, setIsCustom] = useState(false);
const [isCustomModalVisible, setIsCustomModalVisible] = useState(false);
const { navigate, goBack } = useNavigation();
constructor(props) {
super(props);
let secret = props.navigation.state.params.secret || '';
this.state = {
secret: secret,
isHandOffUseEnabled: false,
address: '',
customLabel: '',
customAmount: 0,
bip21encoded: undefined,
isCustom: false,
isCustomModalVisible: false,
};
}
renderReceiveDetails = async () => {
this.wallet.setUserHasSavedExport(true);
const renderReceiveDetails = useCallback(async () => {
console.log('receive/details - componentDidMount');
wallet.setUserHasSavedExport(true);
await BlueApp.saveToDisk();
let address;
if (this.wallet.getAddressAsync) {
if (this.wallet.chain === Chain.ONCHAIN) {
if (wallet.getAddressAsync) {
if (wallet.chain === Chain.ONCHAIN) {
try {
address = await Promise.race([this.wallet.getAddressAsync(), BlueApp.sleep(1000)]);
address = await Promise.race([wallet.getAddressAsync(), BlueApp.sleep(1000)]);
} catch (_) {}
if (!address) {
// either sleep expired or getAddressAsync threw an exception
console.warn('either sleep expired or getAddressAsync threw an exception');
address = this.wallet._getExternalAddressByIndex(this.wallet.next_free_address_index);
address = wallet._getExternalAddressByIndex(wallet.next_free_address_index);
} else {
BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally
}
this.setState({
address: address,
});
} else if (this.wallet.chain === Chain.OFFCHAIN) {
setAddress(address);
} else if (wallet.chain === Chain.OFFCHAIN) {
try {
await Promise.race([this.wallet.getAddressAsync(), BlueApp.sleep(1000)]);
address = this.wallet.getAddress();
await Promise.race([wallet.getAddressAsync(), BlueApp.sleep(1000)]);
address = wallet.getAddress();
} catch (_) {}
if (!address) {
// either sleep expired or getAddressAsync threw an exception
console.warn('either sleep expired or getAddressAsync threw an exception');
address = this.wallet.getAddress();
address = wallet.getAddress();
} else {
BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally
}
}
this.setState({
address: address,
});
} else if (this.wallet.getAddress) {
this.setState({
address: this.wallet.getAddress(),
});
setAddress(address);
} else if (wallet.getAddress) {
setAddress(wallet.getAddress());
}
InteractionManager.runAfterInteractions(async () => {
const bip21encoded = bip21.encode(this.state.address);
this.setState({ bip21encoded });
const bip21encoded = bip21.encode(address);
setBip21encoded(bip21encoded);
});
};
}, [wallet]);
componentDidMount() {
useEffect(() => {
Privacy.enableBlur();
console.log('receive/details - componentDidMount');
for (let w of BlueApp.getWallets()) {
if (w.getSecret() === this.state.secret) {
// found our wallet
this.wallet = w;
}
}
if (this.wallet) {
if (!this.wallet.getUserHasSavedExport()) {
setWallet(BlueApp.getWallets().find(w => w.getSecret() === secret));
if (wallet) {
if (!wallet.getUserHasSavedExport()) {
BlueAlertWalletExportReminder({
onSuccess: this.renderReceiveDetails,
onSuccess: renderReceiveDetails,
onFailure: () => {
this.props.navigation.goBack();
this.props.navigation.navigate('WalletExport', {
wallet: this.wallet,
goBack();
navigate('WalletExport', {
wallet: wallet,
});
},
});
} else {
this.renderReceiveDetails();
renderReceiveDetails();
}
}
HandoffSettings.isHandoffUseEnabled().then(value => this.setState({ isHandOffUseEnabled: value }));
}
HandoffSettings.isHandoffUseEnabled().then(setIsHandOffUseEnabled);
return () => Privacy.disableBlur();
}, [goBack, navigate, renderReceiveDetails, secret, wallet]);
componentWillUnmount() {
Privacy.disableBlur();
}
const dismissCustomAmountModal = () => {
Keyboard.dismiss();
setIsCustomModalVisible(false);
};
renderCustomAmountModal = () => {
const showCustomAmountModal = () => {
setIsCustomModalVisible(true);
};
const createCustomAmountAddress = () => {
setIsCustom(true);
setIsCustomModalVisible(false);
setBip21encoded(bip21.encode(address, { amount: customAmount, label: customLabel }));
};
const clearCustomAmount = () => {
setIsCustom(false);
setIsCustomModalVisible(false);
setCustomAmount('');
setCustomLabel('');
setBip21encoded(bip21.encode(address));
};
const renderCustomAmountModal = () => {
return (
<Modal
isVisible={this.state.isCustomModalVisible}
style={styles.bottomModal}
onBackdropPress={() => {
Keyboard.dismiss();
this.setState({ isCustomModalVisible: false });
}}
>
<Modal isVisible={isCustomModalVisible} style={styles.bottomModal} onBackdropPress={dismissCustomAmountModal}>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'position' : null}>
<View style={styles.modalContent}>
<BlueBitcoinAmount amount={this.state.customAmount || ''} onChangeText={text => this.setState({ customAmount: text })} />
<BlueBitcoinAmount amount={customAmount || ''} onChangeText={setCustomAmount} />
<View
style={{
flexDirection: 'row',
@ -157,38 +150,18 @@ export default class ReceiveDetails extends Component {
}}
>
<TextInput
onChangeText={text => this.setState({ customLabel: text })}
onChangeText={setCustomLabel}
placeholder={loc.receive.details.label}
value={this.state.customLabel || ''}
value={customLabel || ''}
numberOfLines={1}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
/>
</View>
<BlueSpacing20 />
<View>
<BlueButton
title={loc.receive.details.create}
onPress={() => {
this.setState({
isCustom: true,
isCustomModalVisible: false,
bip21encoded: bip21.encode(this.state.address, { amount: this.state.customAmount, label: this.state.customLabel }),
});
}}
/>
<BlueButton title={loc.receive.details.create} onPress={createCustomAmountAddress} />
<BlueSpacing20 />
<BlueButtonLink
title="Reset"
onPress={() => {
this.setState({
isCustom: false,
isCustomModalVisible: false,
customAmount: '',
customLabel: '',
bip21encoded: bip21.encode(this.state.addresss),
});
}}
/>
<BlueButtonLink title="Reset" onPress={clearCustomAmount} />
</View>
<BlueSpacing20 />
</View>
@ -197,87 +170,91 @@ export default class ReceiveDetails extends Component {
);
};
showCustomAmountModal = () => {
this.setState({ isCustomModalVisible: true });
const handleShareButtonPressed = () => {
if (qrCodeSVG === undefined) {
Share.open({ message: bip21encoded }).catch(error => console.log(error));
} else {
InteractionManager.runAfterInteractions(async () => {
qrCodeSVG.toDataURL(data => {
let shareImageBase64 = {
message: bip21encoded,
url: `data:image/png;base64,${data}`,
};
Share.open(shareImageBase64).catch(error => console.log(error));
});
});
}
};
render() {
return (
<SafeBlueArea style={{ flex: 1 }}>
{this.state.isHandOffUseEnabled && this.state.address !== undefined && (
<Handoff
title={`Bitcoin Transaction ${this.state.address}`}
type="io.bluewallet.bluewallet"
url={`https://blockstream.info/address/${this.state.address}`}
/>
)}
<ScrollView contentContainerStyle={{ justifyContent: 'space-between' }}>
<View style={{ marginTop: 32, alignItems: 'center', paddingHorizontal: 16 }}>
{this.state.isCustom && (
<>
<BlueText
style={{ color: '#0c2550', fontWeight: '600', fontSize: 36, textAlign: 'center', paddingBottom: 24 }}
numberOfLines={1}
>
{this.state.customAmount} {BitcoinUnit.BTC}
</BlueText>
<BlueText style={{ color: '#0c2550', fontWeight: '600', textAlign: 'center', paddingBottom: 24 }} numberOfLines={1}>
{this.state.customLabel}
</BlueText>
</>
)}
{this.state.bip21encoded === undefined ? (
<View style={{ alignItems: 'center', width: 300, height: 300 }}>
<BlueLoading />
</View>
) : (
<QRCode
value={this.state.bip21encoded}
logo={require('../../img/qr-code.png')}
size={(is.ipad() && 300) || 300}
logoSize={90}
color={BlueApp.settings.foregroundColor}
logoBackgroundColor={BlueApp.settings.brandingColor}
ecl={'H'}
getRef={c => (this.qrCodeSVG = c)}
/>
)}
<BlueCopyTextToClipboard text={this.state.isCustom ? this.state.bip21encoded : this.state.address} />
</View>
<View style={{ alignItems: 'center', alignContent: 'flex-end', marginBottom: 24 }}>
<BlueButtonLink title={loc.receive.details.setAmount} onPress={this.showCustomAmountModal} />
<View>
<BlueButton
icon={{
name: 'share-alternative',
type: 'entypo',
color: BlueApp.settings.buttonTextColor,
}}
onPress={async () => {
if (this.qrCodeSVG === undefined) {
Share.open({ message: this.state.bip21encoded }).catch(error => console.log(error));
} else {
InteractionManager.runAfterInteractions(async () => {
this.qrCodeSVG.toDataURL(data => {
let shareImageBase64 = {
message: this.state.bip21encoded,
url: `data:image/png;base64,${data}`,
};
Share.open(shareImageBase64).catch(error => console.log(error));
});
});
}
}}
title={loc.receive.details.share}
/>
return (
<SafeBlueArea style={{ flex: 1 }}>
{isHandOffUseEnabled && address !== undefined && (
<Handoff
title={`Bitcoin Transaction ${address}`}
type="io.bluewallet.bluewallet"
url={`https://blockstream.info/address/${address}`}
/>
)}
<ScrollView contentContainerStyle={{ justifyContent: 'space-between' }}>
<View style={{ marginTop: 32, alignItems: 'center', paddingHorizontal: 16 }}>
{isCustom && (
<>
<BlueText
style={{ color: '#0c2550', fontWeight: '600', fontSize: 36, textAlign: 'center', paddingBottom: 24 }}
numberOfLines={1}
>
{customAmount} {BitcoinUnit.BTC}
</BlueText>
<BlueText style={{ color: '#0c2550', fontWeight: '600', textAlign: 'center', paddingBottom: 24 }} numberOfLines={1}>
{customLabel}
</BlueText>
</>
)}
{bip21encoded === undefined ? (
<View style={{ alignItems: 'center', width: 300, height: 300 }}>
<BlueLoading />
</View>
) : (
<QRCode
value={bip21encoded}
logo={require('../../img/qr-code.png')}
size={(is.ipad() && 300) || 300}
logoSize={90}
color={BlueApp.settings.foregroundColor}
logoBackgroundColor={BlueApp.settings.brandingColor}
ecl={'H'}
getRef={setQrCodeSVG}
/>
)}
<BlueCopyTextToClipboard text={isCustom ? bip21encoded : address} />
</View>
<View style={{ alignItems: 'center', alignContent: 'flex-end', marginBottom: 24 }}>
<BlueButtonLink title={loc.receive.details.setAmount} onPress={showCustomAmountModal} />
<View>
<BlueButton
icon={{
name: 'share-alternative',
type: 'entypo',
color: BlueApp.settings.buttonTextColor,
}}
onPress={handleShareButtonPressed}
title={loc.receive.details.share}
/>
</View>
{this.renderCustomAmountModal()}
</ScrollView>
</SafeBlueArea>
);
}
}
</View>
{renderCustomAmountModal()}
</ScrollView>
</SafeBlueArea>
);
};
ReceiveDetails.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
title: loc.receive.header,
headerLeft: null,
});
export default ReceiveDetails;
const styles = StyleSheet.create({
modalContent: {
@ -296,15 +273,3 @@ const styles = StyleSheet.create({
margin: 0,
},
});
ReceiveDetails.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.func,
navigate: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
secret: PropTypes.string,
}),
}),
}),
};

66
screen/wallets/buyBitcoin.js

@ -2,8 +2,8 @@ import React, { Component } from 'react';
import { BlueNavigationStyle, BlueLoading, SafeBlueArea } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { WebView } from 'react-native-webview';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
import { AppStorage, LightningCustodianWallet, WatchOnlyWallet } from '../../class';
let BlueApp: AppStorage = require('../../BlueApp');
let loc = require('../../loc');
export default class BuyBitcoin extends Component {
@ -15,47 +15,54 @@ export default class BuyBitcoin extends Component {
constructor(props) {
super(props);
let address = props.navigation.state.params.address;
let secret = props.navigation.state.params.secret;
let wallet = props.navigation.state.params.wallet;
this.state = {
isLoading: true,
address: address,
secret: secret,
addressText: '',
wallet,
address: '',
};
}
async componentDidMount() {
console.log('buyBitcoin - componentDidMount');
/** @type {AbstractWallet} */
let wallet;
let address = this.state.address;
for (let w of BlueApp.getWallets()) {
if ((address && w.getAddress() === this.state.address) || w.getSecret() === this.state.secret) {
// found our wallet
wallet = w;
}
}
/** @type {AbstractHDWallet|WatchOnlyWallet|LightningCustodianWallet} */
let wallet = this.state.wallet;
let address = '';
if (wallet && wallet.getAddressAsync) {
setTimeout(async () => {
address = await wallet.getAddressAsync();
BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally
this.setState({
address: address,
addressText: address,
isLoading: false,
});
}, 1);
} else {
if (WatchOnlyWallet.type === wallet.type && !wallet.isHd()) {
// plain watchonly - just get the address
address = wallet.getAddress();
this.setState({
isLoading: false,
address,
addressText: address,
});
return;
}
// otherwise, lets call widely-used getAddressAsync()
try {
address = await Promise.race([wallet.getAddressAsync(), BlueApp.sleep(2000)]);
} catch (_) {}
if (!address) {
// either sleep expired or getAddressAsync threw an exception
if (LightningCustodianWallet.type === wallet.type) {
// not much we can do, lets hope refill address was cached previously
address = wallet.getAddress() || '';
} else {
// plain hd wallet (either HD or watchonly-wrapped). trying next free address
address = wallet._getExternalAddressByIndex(wallet.getNextFreeAddressIndex());
}
}
this.setState({
isLoading: false,
address,
});
}
render() {
@ -88,8 +95,7 @@ BuyBitcoin.propTypes = {
goBack: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
address: PropTypes.string,
secret: PropTypes.string,
wallet: PropTypes.object,
safelloStateToken: PropTypes.string,
}),
}),

2
screen/wallets/details.js

@ -270,9 +270,7 @@ export default class WalletDetails extends Component {
{this.renderMarketplaceButton()}
</React.Fragment>
)}
<BlueSpacing20 />
<TouchableOpacity
style={{ alignItems: 'center' }}
onPress={() => {

16
screen/wallets/transactions.js

@ -278,6 +278,19 @@ export default class WalletTransactions extends Component {
title={'Refill with External Wallet'}
/>
<BlueListItem
hideChevron
component={TouchableOpacity}
onPress={a => {
this.setState({ isManageFundsModalVisible: false }, async () => {
this.props.navigation.navigate('BuyBitcoin', {
wallet: this.state.wallet,
});
});
}}
title={'Refill with bank card'}
/>
<BlueListItem
title={loc.lnd.withdraw}
hideChevron
@ -638,8 +651,7 @@ export default class WalletTransactions extends Component {
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate('BuyBitcoin', {
address: this.state.wallet.getAddress(),
secret: this.state.wallet.getSecret(),
wallet: this.state.wallet,
})
}
style={{

15
tests/e2e/bluewallet.spec.js

@ -1,6 +1,21 @@
/* global it, describe, expect, element, by, waitFor, device */
describe('BlueWallet UI Tests', () => {
it('selftest passes', async () => {
await waitFor(element(by.id('WalletsList')))
.toBeVisible()
.withTimeout(300 * 1000);
// go to settings, press SelfTest and wait for OK
await element(by.id('SettingsButton')).tap();
await element(by.id('AboutButton')).tap();
await element(by.id('AboutScrollView')).swipe('up', 'fast', 1); // in case emu screen is small and it doesnt fit
await element(by.id('RunSelfTestButton')).tap();
await waitFor(element(by.id('SelfTestOk')))
.toBeVisible()
.withTimeout(300 * 1000);
});
it('can encrypt storage, with plausible deniability', async () => {
await yo('WalletsList');

18
tests/e2e/selftest.spec.js

@ -1,18 +0,0 @@
/* global it, describe, element, by, waitFor */
describe('BlueWallet Selftest', () => {
it('passes', async () => {
await waitFor(element(by.id('WalletsList')))
.toBeVisible()
.withTimeout(300 * 1000);
// go to settings, press SelfTest and wait for OK
await element(by.id('SettingsButton')).tap();
await element(by.id('AboutButton')).tap();
await element(by.id('AboutScrollView')).swipe('up', 'fast', 1); // in case emu screen is small and it doesnt fit
await element(by.id('RunSelfTestButton')).tap();
await waitFor(element(by.id('SelfTestOk')))
.toBeVisible()
.withTimeout(300 * 1000);
});
});

18
tests/integration/hd-segwit-bech32-wallet.test.js

@ -49,6 +49,7 @@ describe('Bech32 Segwit HD (BIP84)', () => {
assert.strictEqual(await hd.getAddressAsync(), hd._getExternalAddressByIndex(2));
assert.strictEqual(await hd.getChangeAddressAsync(), hd._getInternalAddressByIndex(2));
assert.strictEqual(hd.next_free_address_index, 2);
assert.strictEqual(hd.getNextFreeAddressIndex(), 2);
assert.strictEqual(hd.next_free_change_address_index, 2);
// now fetch txs
@ -81,6 +82,7 @@ describe('Bech32 Segwit HD (BIP84)', () => {
assert.strictEqual(await hd.getAddressAsync(), hd._getExternalAddressByIndex(2));
assert.strictEqual(await hd.getChangeAddressAsync(), hd._getInternalAddressByIndex(2));
assert.strictEqual(hd.next_free_address_index, 2);
assert.strictEqual(hd.getNextFreeAddressIndex(), 2);
assert.strictEqual(hd.next_free_change_address_index, 2);
});
@ -153,6 +155,7 @@ describe('Bech32 Segwit HD (BIP84)', () => {
assert.ok(hd.next_free_change_address_index > 0);
assert.ok(hd.next_free_address_index > 0);
assert.ok(hd.getNextFreeAddressIndex() > 0);
start = +new Date();
await hd.fetchTransactions();
@ -232,4 +235,19 @@ describe('Bech32 Segwit HD (BIP84)', () => {
assert.strictEqual(totalInput - totalOutput, fee);
assert.strictEqual(outputs[outputs.length - 1].address, changeAddress);
});
it('wasEverUsed() works', async () => {
if (!process.env.HD_MNEMONIC) {
console.error('process.env.HD_MNEMONIC not set, skipped');
return;
}
let hd = new HDSegwitBech32Wallet();
hd.setSecret(process.env.HD_MNEMONIC);
assert.ok(await hd.wasEverUsed());
hd = new HDSegwitBech32Wallet();
await hd.generate();
assert.ok(!(await hd.wasEverUsed()), hd.getSecret());
});
});

4
tests/integration/hd-segwit-p2sh-wallet.test.js

@ -187,6 +187,7 @@ it('can create a Legacy HD (BIP44)', async function() {
assert.ok(hd._lastTxFetch > 0);
assert.strictEqual(hd.getTransactions().length, 4);
assert.strictEqual(hd.next_free_address_index, 1);
assert.strictEqual(hd.getNextFreeAddressIndex(), 1);
assert.strictEqual(hd.next_free_change_address_index, 1);
for (let tx of hd.getTransactions()) {
@ -196,6 +197,7 @@ it('can create a Legacy HD (BIP44)', async function() {
// checking that internal pointer and async address getter return the same address
let freeAddress = await hd.getAddressAsync();
assert.strictEqual(hd._getExternalAddressByIndex(hd.next_free_address_index), freeAddress);
assert.strictEqual(hd._getExternalAddressByIndex(hd.getNextFreeAddressIndex()), freeAddress);
});
it('Legacy HD (BIP44) can create TX', async () => {
@ -257,9 +259,11 @@ it('HD breadwallet works', async function() {
assert.strictEqual(hdBread.getBalance(), 123456);
assert.strictEqual(hdBread.next_free_address_index, 11);
assert.strictEqual(hdBread.getNextFreeAddressIndex(), 11);
assert.strictEqual(hdBread.next_free_change_address_index, 118);
// checking that internal pointer and async address getter return the same address
let freeAddress = await hdBread.getAddressAsync();
assert.strictEqual(hdBread._getExternalAddressByIndex(hdBread.next_free_address_index), freeAddress);
assert.strictEqual(hdBread._getExternalAddressByIndex(hdBread.getNextFreeAddressIndex()), freeAddress);
});

30
tests/unit/hd-legacy-electrum-seed-p2pkh-wallet.test.js

@ -0,0 +1,30 @@
/* global describe, it */
import { HDLegacyElectrumSeedP2PKHWallet } from '../../class';
let assert = require('assert');
describe('HDLegacyElectrumSeedP2PKHWallet', () => {
it('can import mnemonics and generate addresses and WIFs', async function() {
if (!process.env.HD_ELECTRUM_SEED_LEGACY) {
console.error('process.env.HD_ELECTRUM_SEED_LEGACY not set, skipped');
return;
}
let hd = new HDLegacyElectrumSeedP2PKHWallet();
hd.setSecret(process.env.HD_ELECTRUM_SEED_LEGACY);
assert.ok(hd.validateMnemonic());
let address = hd._getExternalAddressByIndex(0);
assert.strictEqual(address, '1Ca9ZVshGdKiiMEMNTG1bYqbifYMZMwV8');
address = hd._getInternalAddressByIndex(0);
assert.strictEqual(address, '1JygAvTQS9haAYgRfPSdHgmXd3syjB8Fnp');
let wif = hd._getExternalWIFByIndex(0);
assert.strictEqual(wif, 'KxGPz9dyib26p6bL2vQPvBPHBMA8iHVqEetg3x5XA4Rk1trZ11Kz');
wif = hd._getInternalWIFByIndex(0);
assert.strictEqual(wif, 'L52d26QmYGW8ctHo1omM5fZeJMgaonSkEWCGpnEekNvkVUoqTsNF');
hd.setSecret('bs');
assert.ok(!hd.validateMnemonic());
});
});
Loading…
Cancel
Save