BlueWallet/tests/e2e/bluewallet.spec.js

1286 lines
58 KiB
JavaScript
Raw Normal View History

const bitcoin = require('bitcoinjs-lib');
const assert = require('assert');
2020-05-23 08:50:08 +02:00
const createHash = require('create-hash');
jasmine.getEnv().addReporter({
specStarted: result => (jasmine.currentTest = result),
specDone: result => (jasmine.currentTest = result),
});
describe('BlueWallet UI Tests', () => {
2020-04-20 14:29:37 +02:00
it('selftest passes', async () => {
2020-05-23 08:50:08 +02:00
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
2020-10-06 13:23:13 +02:00
return console.warn('skipping', JSON.stringify(jasmine.currentTest.fullName), 'as it previously passed on Travis');
2020-05-23 08:50:08 +02:00
}
2020-04-20 14:29:37 +02:00
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);
2021-09-16 20:56:09 +02:00
await device.pressBack();
await device.pressBack();
await device.pressBack();
2020-05-23 08:50:08 +02:00
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
2020-04-20 14:29:37 +02:00
});
2021-08-19 18:30:19 +02:00
it('all settings screens work', async () => {
2021-03-01 11:20:01 +01:00
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
return console.warn('skipping', JSON.stringify(jasmine.currentTest.fullName), 'as it previously passed on Travis');
}
await yo('WalletsList');
// go to settings, press SelfTest and wait for OK
await element(by.id('SettingsButton')).tap();
// general
await element(by.id('GeneralSettings')).tap();
2021-04-06 12:31:56 +02:00
// privacy
// trigger switches
await element(by.id('SettingsPrivacy')).tap();
await element(by.id('ClipboardSwith')).tap();
await element(by.id('ClipboardSwith')).tap();
await element(by.id('QuickActionsSwith')).tap();
await element(by.id('QuickActionsSwith')).tap();
await device.pressBack();
// enable AdvancedMode
2021-03-01 11:20:01 +01:00
await element(by.id('AdvancedMode')).tap();
await device.pressBack();
2021-09-16 20:56:09 +02:00
// disable it:
await element(by.id('GeneralSettings')).tap();
await element(by.id('AdvancedMode')).tap();
await device.pressBack();
2021-03-01 11:20:01 +01:00
//
// currency
// change currency to ARS ($) and switch it back to USD ($)
await element(by.id('Currency')).tap();
await element(by.text('ARS ($)')).tap();
await expect(element(by.text('Prices are obtained from Yadio'))).toBeVisible();
await element(by.text('USD ($)')).tap();
await device.pressBack();
// language
// change language to Chinese (ZH), test it and switch back to English
await element(by.id('Language')).tap();
await element(by.text('Chinese (ZH)')).tap();
await device.pressBack();
await expect(element(by.text('语言'))).toBeVisible();
await element(by.id('Language')).tap();
await element(by.text('English')).tap();
await device.pressBack();
// security
await element(by.id('SecurityButton')).tap();
await device.pressBack();
// network
await element(by.id('NetworkSettings')).tap();
// network -> electrum server
// change electrum server to electrum.blockstream.info and revert it back
await element(by.id('ElectrumSettings')).tap();
await element(by.id('HostInput')).replaceText('electrum.blockstream.info\n');
await element(by.id('PortInput')).replaceText('50001\n');
await element(by.id('Save')).tap();
await sup('OK');
await element(by.text('OK')).tap();
await element(by.id('ResetToDefault')).tap();
await sup('OK');
await element(by.text('OK')).tap();
await expect(element(by.id('HostInput'))).toHaveText('');
await expect(element(by.id('PortInput'))).toHaveText('');
2021-07-05 05:05:04 +02:00
await expect(element(by.id('SSLPortInput'))).toHaveToggleValue(false);
2021-03-01 11:20:01 +01:00
await device.pressBack();
// network -> lightning
// change URI and revert it back
await element(by.id('LightningSettings')).tap();
await element(by.id('URIInput')).replaceText('invalid\n');
await element(by.id('Save')).tap();
await sup('OK');
2021-08-30 05:51:24 +02:00
await expect(element(by.text('Invalid LNDHub URI'))).toBeVisible();
2021-03-01 11:20:01 +01:00
await element(by.text('OK')).tap();
await element(by.id('URIInput')).replaceText('https://lndhub.herokuapp.com\n');
await element(by.id('Save')).tap();
await sup('OK');
await expect(element(by.text('Your changes have been saved successfully.'))).toBeVisible();
await element(by.text('OK')).tap();
await element(by.id('URIInput')).replaceText('\n');
await element(by.id('Save')).tap();
await sup('OK');
await expect(element(by.text('Your changes have been saved successfully.'))).toBeVisible();
await element(by.text('OK')).tap();
await device.pressBack();
2021-04-11 22:53:05 +02:00
// notifications
// turn on notifications if available
// console.warn('yo');
// await sleep(300000);
if (await expectToBeVisible('NotificationSettings')) {
await element(by.id('NotificationSettings')).tap();
await element(by.id('NotificationsSwitch')).tap();
await sup('OK');
await element(by.text('OK')).tap();
await element(by.id('NotificationsSwitch')).tap();
await device.pressBack();
2021-03-31 17:21:39 +02:00
await device.pressBack();
} else {
await device.pressBack();
}
2021-03-01 11:20:01 +01:00
2021-03-31 17:21:39 +02:00
// tools
await element(by.id('Tools')).tap();
// tools -> broadcast
// try to broadcast wrong tx
await element(by.id('Broadcast')).tap();
await element(by.id('TxHex')).replaceText('invalid\n');
await element(by.id('BroadcastButton')).tap();
await sup('OK');
// await expect(element(by.text('the transaction was rejected by network rules....'))).toBeVisible();
await element(by.text('OK')).tap();
await device.pressBack();
2021-04-01 12:11:53 +02:00
// IsItMyAddress
await element(by.id('IsItMyAddress')).tap();
await element(by.id('AddressInput')).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
await element(by.id('CheckAddress')).tap();
await expect(element(by.id('Result'))).toHaveText('None of the available wallets own the provided address.');
await device.pressBack();
2021-03-31 18:38:43 +02:00
await device.pressBack();
2021-03-01 11:20:01 +01:00
// about
await element(by.id('AboutButton')).tap();
await device.pressBack();
2021-09-16 20:56:09 +02:00
await device.pressBack();
2021-03-01 11:20:01 +01:00
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
it('can create wallet, reload app and it persists. then go to receive screen, set custom amount and label. Dismiss modal and go to WalletsList.', async () => {
2020-05-23 08:50:08 +02:00
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
2020-10-06 13:23:13 +02:00
return console.warn('skipping', JSON.stringify(jasmine.currentTest.fullName), 'as it previously passed on Travis');
2020-05-23 08:50:08 +02:00
}
await yo('WalletsList');
await helperCreateWallet();
await device.launchApp({ newInstance: true });
await yo('WalletsList');
await expect(element(by.id('cr34t3d'))).toBeVisible();
await element(by.id('cr34t3d')).tap();
await element(by.id('ReceiveButton')).tap();
await element(by.text('Yes, I have')).tap();
try {
// in case emulator has no google services and doesnt support pushes
// we just dont show this popup
2021-02-09 14:35:04 +01:00
await element(by.text(`No, and don’t ask me again`)).tap();
} catch (_) {}
await yo('BitcoinAddressQRCodeContainer');
await yo('BlueCopyTextToClipboard');
await element(by.id('SetCustomAmountButton')).tap();
await element(by.id('BitcoinAmountInput')).replaceText('1');
await element(by.id('CustomAmountDescription')).typeText('test');
await element(by.id('CustomAmountSaveButton')).tap();
await sup('1 BTC');
await sup('test');
await yo('BitcoinAddressQRCodeContainer');
await yo('BlueCopyTextToClipboard');
await device.pressBack();
await device.pressBack();
2021-09-16 20:56:09 +02:00
await helperDeleteWallet('cr34t3d');
2020-05-23 08:50:08 +02:00
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
2021-09-16 20:56:09 +02:00
it('can encrypt storage, with plausible deniabilityl decrypt fake storage', async () => {
2020-05-23 08:50:08 +02:00
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
2020-10-06 13:23:13 +02:00
return console.warn('skipping', JSON.stringify(jasmine.currentTest.fullName), 'as it previously passed on Travis');
2020-05-23 08:50:08 +02:00
}
2020-03-19 16:51:35 +01:00
await yo('WalletsList');
// lets create a wallet
await helperCreateWallet();
2020-03-19 16:51:35 +01:00
// go to settings
await expect(element(by.id('SettingsButton'))).toBeVisible();
await element(by.id('SettingsButton')).tap();
await expect(element(by.id('SecurityButton'))).toBeVisible();
2020-03-19 16:51:35 +01:00
// go to Security page where we will enable encryption
await element(by.id('SecurityButton')).tap();
// await expect(element(by.id('EncyptedAndPasswordProtected'))).toBeVisible(); // @see https://github.com/react-native-elements/react-native-elements/issues/2519
2020-03-19 16:51:35 +01:00
await expect(element(by.id('PlausibleDeniabilityButton'))).toBeNotVisible();
if (device.getPlatform() === 'ios') {
console.warn('Android only test skipped');
return;
}
2020-03-19 16:51:35 +01:00
// lets encrypt the storage.
// first, trying to mistype second password:
2020-03-19 16:51:35 +01:00
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol. lets tap it
await element(by.type('android.widget.EditText')).typeText('08902');
await element(by.text('OK')).tap();
2020-03-19 16:51:35 +01:00
await element(by.type('android.widget.EditText')).typeText('666');
await element(by.text('OK')).tap();
2020-12-12 22:14:57 +01:00
await expect(element(by.text('Passwords do not match.'))).toBeVisible();
await element(by.text('OK')).tap();
2020-03-19 16:51:35 +01:00
// now, lets put correct passwords and encrypt the storage
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol
await element(by.type('android.widget.EditText')).typeText('qqq');
await element(by.text('OK')).tap();
await element(by.type('android.widget.EditText')).typeText('qqq');
await element(by.text('OK')).tap();
2020-03-16 14:51:08 +01:00
2020-03-19 16:51:35 +01:00
// relaunch app
await device.launchApp({ newInstance: true });
await waitFor(element(by.text('OK')))
.toBeVisible()
2020-03-19 17:55:35 +01:00
.withTimeout(33000);
2020-11-22 10:18:05 +01:00
// trying to decrypt with incorrect password
await expect(element(by.text('Your storage is encrypted. Password is required to decrypt it.'))).toBeVisible();
2020-03-19 16:51:35 +01:00
await element(by.type('android.widget.EditText')).typeText('wrong');
await element(by.text('OK')).tap();
await expect(element(by.text('Incorrect password. Please try again.'))).toBeVisible();
2020-03-19 16:51:35 +01:00
// correct password
await element(by.type('android.widget.EditText')).typeText('qqq');
await element(by.text('OK')).tap();
await yo('WalletsList');
2020-03-19 16:51:35 +01:00
// previously created wallet should be visible
await expect(element(by.id('cr34t3d'))).toBeVisible();
2020-03-19 16:51:35 +01:00
// now lets enable plausible deniability feature
// go to settings -> security screen -> plausible deniability screen
await element(by.id('SettingsButton')).tap();
await expect(element(by.id('SecurityButton'))).toBeVisible();
await element(by.id('SecurityButton')).tap();
// await expect(element(by.id('EncyptedAndPasswordProtected'))).toBeVisible(); // @see https://github.com/react-native-elements/react-native-elements/issues/2519
2020-03-19 16:51:35 +01:00
await expect(element(by.id('PlausibleDeniabilityButton'))).toBeVisible();
await element(by.id('PlausibleDeniabilityButton')).tap();
// trying to enable plausible denability
await element(by.id('CreateFakeStorageButton')).tap();
2020-12-12 22:14:57 +01:00
await expect(element(by.text('Password for the fake storage should not match the password for your main storage.'))).toBeVisible();
2020-03-19 16:51:35 +01:00
// trying MAIN password: should fail, obviously
await element(by.type('android.widget.EditText')).typeText('qqq');
await element(by.text('OK')).tap();
await expect(element(by.text('Password is currently in use. Please try a different password.'))).toBeVisible();
if (process.env.TRAVIS) await sleep(3000); // hopefully helps prevent crash
2020-03-19 16:51:35 +01:00
await element(by.text('OK')).tap();
if (process.env.TRAVIS) await sleep(3000); // hopefully helps prevent crash
2020-03-19 16:51:35 +01:00
// trying new password, but will mistype
await element(by.id('CreateFakeStorageButton')).tap();
if (process.env.TRAVIS) await sleep(3000); // hopefully helps prevent crash
2020-03-19 16:51:35 +01:00
await element(by.type('android.widget.EditText')).typeText('passwordForFakeStorage');
await element(by.text('OK')).tap();
await expect(element(by.text('Re-type password'))).toBeVisible();
2020-03-19 16:51:35 +01:00
await element(by.type('android.widget.EditText')).typeText('passwordForFakeStorageWithTypo'); // retyping with typo
await element(by.text('OK')).tap();
await expect(element(by.text('Passwords do not match. Please try again.'))).toBeVisible();
2020-03-19 16:51:35 +01:00
await element(by.text('OK')).tap();
// trying new password
await element(by.id('CreateFakeStorageButton')).tap();
await element(by.type('android.widget.EditText')).typeText('passwordForFakeStorage');
await element(by.text('OK')).tap();
await expect(element(by.text('Re-type password'))).toBeVisible();
2020-03-19 16:51:35 +01:00
await element(by.type('android.widget.EditText')).typeText('passwordForFakeStorage'); // retyping
await element(by.text('OK')).tap();
await expect(element(by.text('Success'))).toBeVisible();
await element(by.text('OK')).tap();
// created fake storage.
// creating a wallet inside this fake storage
await helperCreateWallet('fake_wallet');
// relaunch the app, unlock with fake password, expect to see fake wallet
// relaunch app
await device.launchApp({ newInstance: true });
2020-03-19 16:51:35 +01:00
await waitFor(element(by.text('OK')))
.toBeVisible()
2020-03-19 17:55:35 +01:00
.withTimeout(33000);
2020-03-19 16:51:35 +01:00
//
await expect(element(by.text('Your storage is encrypted. Password is required to decrypt it.'))).toBeVisible();
2020-03-19 16:51:35 +01:00
await element(by.type('android.widget.EditText')).typeText('qqq');
await element(by.text('OK')).tap();
await yo('WalletsList');
// previously created wallet IN MAIN STORAGE should be visible
await expect(element(by.id('cr34t3d'))).toBeVisible();
// relaunch app
await device.launchApp({ newInstance: true });
await sleep(3000);
//
await expect(element(by.text('Your storage is encrypted. Password is required to decrypt it.'))).toBeVisible();
2020-03-19 16:51:35 +01:00
await element(by.type('android.widget.EditText')).typeText('passwordForFakeStorage');
await element(by.text('OK')).tap();
await yo('WalletsList');
// previously created wallet in FAKE storage should be visible
await expect(element(by.id('fake_wallet'))).toBeVisible();
2020-03-20 12:46:45 +01:00
2021-09-16 20:56:09 +02:00
// now derypting it, to cleanup
2020-03-20 12:46:45 +01:00
await element(by.id('SettingsButton')).tap();
await element(by.id('SecurityButton')).tap();
2020-03-20 12:46:45 +01:00
// correct password
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol
await element(by.text('OK')).tap();
2021-09-16 20:56:09 +02:00
await element(by.type('android.widget.EditText')).typeText('passwordForFakeStorage');
2020-03-20 12:46:45 +01:00
await element(by.text('OK')).tap();
2021-09-16 20:56:09 +02:00
await helperDeleteWallet('fake_wallet');
2020-05-23 08:50:08 +02:00
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
2020-03-20 12:46:45 +01:00
});
2021-09-16 20:56:09 +02:00
it('can encrypt storage, and decrypt storage works', async () => {
2020-05-23 08:50:08 +02:00
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
2020-10-06 13:23:13 +02:00
return console.warn('skipping', JSON.stringify(jasmine.currentTest.fullName), 'as it previously passed on Travis');
2020-05-23 08:50:08 +02:00
}
2020-03-20 12:46:45 +01:00
await yo('WalletsList');
await helperCreateWallet();
await element(by.id('SettingsButton')).tap();
await element(by.id('SecurityButton')).tap();
2020-03-20 12:46:45 +01:00
if (device.getPlatform() === 'ios') {
console.warn('Android only test skipped');
return;
}
// lets encrypt the storage.
// lets put correct passwords and encrypt the storage
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol
await element(by.type('android.widget.EditText')).typeText('pass');
await element(by.text('OK')).tap();
await element(by.type('android.widget.EditText')).typeText('pass');
await element(by.text('OK')).tap();
await element(by.id('PlausibleDeniabilityButton')).tap();
// trying to enable plausible denability
await element(by.id('CreateFakeStorageButton')).tap();
await element(by.type('android.widget.EditText')).typeText('fake');
await element(by.text('OK')).tap();
await expect(element(by.text('Re-type password'))).toBeVisible();
2020-03-20 12:46:45 +01:00
await element(by.type('android.widget.EditText')).typeText('fake'); // retyping
await element(by.text('OK')).tap();
await expect(element(by.text('Success'))).toBeVisible();
await element(by.text('OK')).tap();
// created fake storage.
// creating a wallet inside this fake storage
await helperCreateWallet('fake_wallet');
// relaunch app
await device.launchApp({ newInstance: true });
await waitFor(element(by.text('OK')))
.toBeVisible()
.withTimeout(33000);
//
await expect(element(by.text('Your storage is encrypted. Password is required to decrypt it.'))).toBeVisible();
2021-09-16 20:56:09 +02:00
await element(by.type('android.widget.EditText')).typeText('pass');
2020-03-20 12:46:45 +01:00
await element(by.text('OK')).tap();
await yo('WalletsList');
2021-09-16 20:56:09 +02:00
// previously created wallet IN MAIN STORAGE should be visible
await expect(element(by.id('cr34t3d'))).toBeVisible();
2020-03-20 12:46:45 +01:00
// now go to settings, and decrypt
await element(by.id('SettingsButton')).tap();
await element(by.id('SecurityButton')).tap();
2020-03-20 12:46:45 +01:00
2021-09-16 20:56:09 +02:00
// putting FAKE storage password. should not succeed
2020-03-20 12:46:45 +01:00
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol
await element(by.text('OK')).tap();
2021-09-16 20:56:09 +02:00
await element(by.type('android.widget.EditText')).typeText('fake');
2020-03-20 12:46:45 +01:00
await element(by.text('OK')).tap();
2020-12-10 16:28:56 +01:00
await expect(element(by.text('Incorrect password. Please try again.'))).toBeVisible();
2020-03-20 12:46:45 +01:00
await element(by.text('OK')).tap();
// correct password
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol
await element(by.text('OK')).tap();
2021-09-16 20:56:09 +02:00
await element(by.type('android.widget.EditText')).typeText('pass');
2020-03-20 12:46:45 +01:00
await element(by.text('OK')).tap();
// relaunch app
await device.launchApp({ newInstance: true });
2021-09-16 20:56:09 +02:00
await yo('cr34t3d'); // success
await helperDeleteWallet('cr34t3d');
2020-05-23 08:50:08 +02:00
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
2020-03-20 12:46:45 +01:00
});
2021-02-18 14:37:43 +01:00
it('can import BIP84 mnemonic, fetch balance & transactions, then create a transaction; then cosign', async () => {
2020-05-23 08:50:08 +02:00
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
2020-10-06 13:23:13 +02:00
return console.warn('skipping', JSON.stringify(jasmine.currentTest.fullName), 'as it previously passed on Travis');
2020-05-23 08:50:08 +02:00
}
if (!process.env.HD_MNEMONIC_BIP84) {
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
return;
}
await helperImportWallet(process.env.HD_MNEMONIC_BIP84, 'Imported HD SegWit (BIP84 Bech32 Native)', '0.00105526 BTC');
// lets create real transaction:
await element(by.id('SendButton')).tap();
await element(by.id('AddressInput')).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
await element(by.id('BitcoinAmountInput')).typeText('0.0001\n');
// setting fee rate:
const feeRate = 2;
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText(feeRate + '');
await element(by.text('OK')).tap();
if (process.env.TRAVIS) await sleep(5000);
try {
await element(by.id('CreateTransactionButton')).tap();
} catch (_) {}
// created. verifying:
await yo('TransactionValue');
expect(element(by.id('TransactionValue'))).toHaveText('0.0001');
const transactionFee = await extractTextFromElementById('TransactionFee');
assert.ok(transactionFee.startsWith('Fee: 0.00000292 BTC'), 'Unexpected tx fee: ' + transactionFee);
await element(by.id('TransactionDetailsButton')).tap();
let txhex = await extractTextFromElementById('TxhexInput');
let transaction = bitcoin.Transaction.fromHex(txhex);
assert.ok(transaction.ins.length === 1 || transaction.ins.length === 2); // depending on current fees gona use either 1 or 2 inputs
assert.strictEqual(transaction.outs.length, 2);
assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[0].script), 'bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl'); // to address
assert.strictEqual(transaction.outs[0].value, 10000);
// checking fee rate:
2021-05-11 12:18:24 +02:00
const totalIns = 100000; // we hardcode it since we know it in advance
const totalOuts = transaction.outs.map(el => el.value).reduce((a, b) => a + b, 0);
const tx = bitcoin.Transaction.fromHex(txhex);
assert.strictEqual(Math.round((totalIns - totalOuts) / tx.virtualSize()), feeRate);
assert.strictEqual(transactionFee.split(' ')[1] * 100000000, totalIns - totalOuts);
if (device.getPlatform() === 'ios') {
console.warn('rest of the test is Android only, skipped');
return;
}
// now, testing scanQR with bip21:
await device.pressBack();
await device.pressBack();
await element(by.id('changeAmountUnitButton')).tap(); // switched to SATS
await element(by.id('BlueAddressInputScanQrButton')).tap();
// tapping 5 times invisible button is a backdoor:
for (let c = 0; c <= 5; c++) {
await element(by.id('ScanQrBackdoorButton')).tap();
await sleep(1000);
}
const bip21 = 'bitcoin:bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7?amount=0.00015&pj=https://btc.donate.kukks.org/BTC/pj';
2020-09-28 15:37:41 +02:00
await element(by.id('scanQrBackdoorInput')).replaceText(bip21);
await element(by.id('scanQrBackdoorOkButton')).tap();
if (process.env.TRAVIS) await sleep(5000);
try {
await element(by.id('CreateTransactionButton')).tap();
} catch (_) {}
// created. verifying:
await yo('TransactionValue');
await yo('PayjoinSwitch');
await element(by.id('TransactionDetailsButton')).tap();
txhex = await extractTextFromElementById('TxhexInput');
transaction = bitcoin.Transaction.fromHex(txhex);
assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[0].script), 'bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7');
assert.strictEqual(transaction.outs[0].value, 15000);
// now, testing scanQR with just address after amount set to 1.1 USD. Denomination should not change after qrcode scan
await device.pressBack();
await device.pressBack();
await element(by.id('changeAmountUnitButton')).tap(); // switched to SATS
await element(by.id('changeAmountUnitButton')).tap(); // switched to FIAT
await element(by.id('BitcoinAmountInput')).replaceText('1.1');
await element(by.id('BlueAddressInputScanQrButton')).tap();
// tapping 5 times invisible button is a backdoor:
for (let c = 0; c <= 5; c++) {
await element(by.id('ScanQrBackdoorButton')).tap();
await sleep(1000);
}
await element(by.id('scanQrBackdoorInput')).replaceText('bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7');
await element(by.id('scanQrBackdoorOkButton')).tap();
if (process.env.TRAVIS) await sleep(5000);
try {
await element(by.id('CreateTransactionButton')).tap();
} catch (_) {}
// created. verifying:
await yo('TransactionValue');
await yo('PayjoinSwitch');
await element(by.id('TransactionDetailsButton')).tap();
txhex = await extractTextFromElementById('TxhexInput');
transaction = bitcoin.Transaction.fromHex(txhex);
assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[0].script), 'bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7');
assert.notEqual(transaction.outs[0].value, 110000000); // check that it is 1.1 USD, not 1 BTC
assert.ok(transaction.outs[0].value < 10000); // 1.1 USD ~ 0,00001964 sats in march 2021
// now, testing units switching, and then creating tx with SATS:
await device.pressBack();
await device.pressBack();
await element(by.id('changeAmountUnitButton')).tap(); // switched to BTC
await element(by.id('BitcoinAmountInput')).replaceText('0.00015');
await element(by.id('changeAmountUnitButton')).tap(); // switched to sats
assert.strictEqual(await extractTextFromElementById('BitcoinAmountInput'), '15000');
await element(by.id('changeAmountUnitButton')).tap(); // switched to FIAT
await element(by.id('changeAmountUnitButton')).tap(); // switched to BTC
assert.strictEqual(await extractTextFromElementById('BitcoinAmountInput'), '0.00015');
await element(by.id('changeAmountUnitButton')).tap(); // switched to sats
await element(by.id('BitcoinAmountInput')).replaceText('50000');
if (process.env.TRAVIS) await sleep(5000);
try {
await element(by.id('CreateTransactionButton')).tap();
} catch (_) {}
// created. verifying:
await yo('TransactionValue');
await element(by.id('TransactionDetailsButton')).tap();
txhex = await extractTextFromElementById('TxhexInput');
transaction = bitcoin.Transaction.fromHex(txhex);
assert.strictEqual(transaction.outs.length, 2);
assert.strictEqual(transaction.outs[0].value, 50000);
2021-02-25 15:32:11 +01:00
// now, testing send many feature
await device.pressBack();
await device.pressBack();
// we already have one output, lest add another two
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('AddRecipient')).tap();
2021-02-27 14:17:07 +01:00
await yo('Transaction1'); // adding a recipient autoscrolls it to the last one
2021-02-25 15:32:11 +01:00
await element(by.id('AddressInput').withAncestor(by.id('Transaction1'))).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
await element(by.id('BitcoinAmountInput').withAncestor(by.id('Transaction1'))).typeText('0.0002\n');
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('AddRecipient')).tap();
await yo('Transaction2'); // adding a recipient autoscrolls it to the last one
// remove last output, check if second output is shown
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('RemoveRecipient')).tap();
await yo('Transaction1');
// adding it again
2021-02-25 15:32:11 +01:00
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('AddRecipient')).tap();
2021-02-27 17:46:14 +01:00
await yo('Transaction2'); // adding a recipient autoscrolls it to the last one
2021-02-25 15:32:11 +01:00
await element(by.id('AddressInput').withAncestor(by.id('Transaction2'))).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
await element(by.id('BitcoinAmountInput').withAncestor(by.id('Transaction2'))).typeText('0.0003\n');
// remove second output
await element(by.id('Transaction2')).swipe('right', 'fast', NaN, 0.2);
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('RemoveRecipient')).tap();
// creating and verifying. tx should have 3 outputs
2021-09-13 19:43:26 +02:00
if (process.env.TRAVIS) await sleep(5000);
2021-02-25 15:32:11 +01:00
try {
await element(by.id('CreateTransactionButton')).tap();
} catch (_) {}
await element(by.id('TransactionDetailsButton')).tap();
txhex = await extractTextFromElementById('TxhexInput');
transaction = bitcoin.Transaction.fromHex(txhex);
assert.strictEqual(transaction.outs.length, 3);
assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[0].script), 'bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7');
assert.strictEqual(transaction.outs[0].value, 50000);
assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[1].script), 'bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
assert.strictEqual(transaction.outs[1].value, 30000);
2021-02-25 15:32:11 +01:00
// now, testing sendMAX feature:
await device.pressBack();
await device.pressBack();
await device.pressBack(); // go back to wallet tx list to reset the form
await element(by.id('SendButton')).tap();
// set fee rate
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText(feeRate + '');
await element(by.text('OK')).tap();
// first send MAX output
await element(by.id('AddressInput')).replaceText('bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7');
await element(by.id('BitcoinAmountInput')).typeText('0.0001\n');
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('sendMaxButton')).tap();
await element(by.text('OK')).tap();
if (process.env.TRAVIS) await sleep(5000);
try {
await element(by.id('CreateTransactionButton')).tap();
} catch (_) {}
// created. verifying:
await yo('TransactionDetailsButton');
await element(by.id('TransactionDetailsButton')).tap();
txhex = await extractTextFromElementById('TxhexInput');
transaction = bitcoin.Transaction.fromHex(txhex);
assert.strictEqual(transaction.outs.length, 1, 'should be single output, no change');
assert.ok(transaction.outs[0].value > 100000);
// add second output with amount
await device.pressBack();
await device.pressBack();
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('AddRecipient')).tap();
await yo('Transaction1');
await element(by.id('AddressInput').withAncestor(by.id('Transaction1'))).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
await element(by.id('BitcoinAmountInput').withAncestor(by.id('Transaction1'))).typeText('0.0001\n');
if (process.env.TRAVIS) await sleep(5000);
try {
await element(by.id('CreateTransactionButton')).tap();
} catch (_) {}
// created. verifying:
await yo('TransactionDetailsButton');
await element(by.id('TransactionDetailsButton')).tap();
txhex = await extractTextFromElementById('TxhexInput');
transaction = bitcoin.Transaction.fromHex(txhex);
assert.strictEqual(transaction.outs.length, 2, 'should be single output, no change');
assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[0].script), 'bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7');
assert.ok(transaction.outs[0].value > 50000);
assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[1].script), 'bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
assert.strictEqual(transaction.outs[1].value, 10000);
2021-02-18 14:37:43 +01:00
// now, testing cosign psbt:
2021-09-13 19:43:26 +02:00
await device.pressBack();
2021-02-18 14:37:43 +01:00
await device.pressBack();
await device.pressBack();
await element(by.id('SendButton')).tap();
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('PsbtSign')).tap();
// tapping 5 times invisible button is a backdoor:
2021-02-18 14:37:43 +01:00
for (let c = 0; c <= 5; c++) {
await element(by.id('ScanQrBackdoorButton')).tap();
await sleep(1000);
}
// 1 input, 2 outputs. wallet can fully sign this tx
const psbt =
'cHNidP8BAFICAAAAAXYa7FEQBAQ2X0B48aHHKKgzkVuHfQ2yCOi3v9RR0IqlAQAAAAAAAACAAegDAAAAAAAAFgAUSnH40G+jiJfreeRb36cs641KFm8AAAAAAAEBH5YVAAAAAAAAFgAUTKHjDm4OJQSbvy9uzyLYi5i5XIoiBgMQcGrP5TIMrdvb73yB4WnZvkPzKr1EzJXJYBHWmlPJZRgAAAAAVAAAgAAAAIAAAACAAQAAAD4AAAAAAA==';
await element(by.id('scanQrBackdoorInput')).replaceText(psbt);
await element(by.id('scanQrBackdoorOkButton')).tap();
// this is fully-signed tx, "this is tx hex" help text should appear
await yo('DynamicCode');
2021-03-02 14:38:02 +01:00
await device.pressBack();
await device.pressBack();
// let's test wallet details screens
await element(by.id('WalletDetails')).tap();
// rename test
await element(by.id('WalletNameInput')).replaceText('testname\n');
await element(by.id('Save')).tap();
await sup('OK');
await element(by.text('OK')).tap();
await expect(element(by.id('WalletLabel'))).toHaveText('testname');
await element(by.id('WalletDetails')).tap();
// wallet export
await element(by.id('WalletDetailsScroll')).swipe('up', 'fast', 1);
await element(by.id('WalletExport')).tap();
await element(by.id('WalletExportScroll')).swipe('up', 'fast', 1);
await expect(element(by.id('Secret'))).toHaveText(process.env.HD_MNEMONIC_BIP84);
await device.pressBack();
// XPUB
await element(by.id('WalletDetailsScroll')).swipe('up', 'fast', 1);
await element(by.id('XPub')).tap();
await expect(element(by.id('BlueCopyTextToClipboard'))).toBeVisible();
await device.pressBack();
// Delete
2021-09-16 20:56:09 +02:00
await device.pressBack();
await device.pressBack();
await helperDeleteWallet('testname', '105526');
2021-02-18 14:37:43 +01:00
2020-05-23 08:50:08 +02:00
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
/**
* test plan:
* 1. import wallet
* 2. wallet -> send -> import transaction (scan QR)
* 3. provide unsigned psbt from coldcard (UR)
* 4. on psbtWithHardwareWallet, tap scanQr
* 5. provide fully signed psbt (UR)
* 6. verify that we can see broadcast button and camera backdorr button is NOT visible
*/
it('can import zpub as watch-only, import psbt, and then scan signed psbt', async () => {
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
return console.warn('skipping', JSON.stringify(jasmine.currentTest.fullName), 'as it previously passed on Travis');
}
await helperImportWallet(
'zpub6rDWXE4wbwefeCrHWehXJheXnti5F9PbpamDUeB5eFbqaY89x3jq86JADBuXpnJnSvRVwqkaTnyMaZERUg4BpxD9V4tSZfKeYh1ozPdL1xK',
'Imported Watch-only',
2021-04-24 23:24:10 +02:00
'0 BTC', // it used to be 0.00030666 till someone stole it from git history kek
);
2021-01-28 06:55:42 +01:00
await element(by.id('ReceiveButton')).tap();
2021-01-28 09:50:55 +01:00
try {
// in case emulator has no google services and doesnt support pushes
// we just dont show this popup
2021-02-09 14:35:04 +01:00
await element(by.text(`No, and don’t ask me again`)).tap();
2021-01-28 09:50:55 +01:00
} catch (_) {}
2021-01-28 20:39:19 +01:00
await expect(element(by.id('BitcoinAddressQRCodeContainer'))).toBeVisible();
2021-01-28 20:34:45 +01:00
await expect(element(by.text('bc1qtc9zquvq7lgq87kzsgltvv4etwm9uxphfkvkay'))).toBeVisible();
2021-01-28 06:55:42 +01:00
await element(by.id('SetCustomAmountButton')).tap();
await element(by.id('BitcoinAmountInput')).replaceText('1');
2021-01-28 20:34:45 +01:00
await element(by.id('CustomAmountDescription')).typeText('Test');
2021-01-28 06:55:42 +01:00
await element(by.id('CustomAmountSaveButton')).tap();
await sup('1 BTC');
await sup('Test');
2021-01-28 20:39:19 +01:00
await expect(element(by.id('BitcoinAddressQRCodeContainer'))).toBeVisible();
2021-01-28 20:34:45 +01:00
await expect(element(by.text('bitcoin:bc1qtc9zquvq7lgq87kzsgltvv4etwm9uxphfkvkay?amount=1&label=Test'))).toBeVisible();
2021-01-28 06:55:42 +01:00
await device.pressBack();
await element(by.id('SendButton')).tap();
await element(by.text('OK')).tap();
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('ImportQrTransactionButton')).tap(); // opens camera
const unsignedPsbt =
'ur:bytes/tzahqumzwnlszqzjqgqqqqqp6uu247pvcz6zld9p77ghlnl753q8fgygggzv9ugjxsmggyy5gqcqqqqqqqq0llllluqepssqqqqqqqqqzcqpfkxmzh6ud2yrvcl37uyy9yswr2z4mx276qqqqqqqqqgpragvxqqqqqqqqqqkqq2tgxjzwa0000egemyzygsv92j2zdwvg5ejypszwe3qctjvrwul6t2ts7yhk8e5takxwzey2z70kdnykwd43jsptrzps95d6cp4gqqqsqqqqqyqqqqqpqqqqqqqqpqqqqqqqqq0vr0lj';
const signedPsbt =
'ur:bytes/tyqjuurnvf607qgq2gpqqqqqq8tn32hc9nqtgta558mezl70l6jyqa9q3ppqfsh3zg6rdpqsj3qrqqqqqqqqpllllllsryxzqqqqqqqqqqtqq9xcmv2lt34gsdnr78msss5jpcdg2hvetmgqqqqqqqqpqy04pscqqqqqqqqqzcqpfdq6gfm4aaal9r8vsg3zps42fgf4e3znxgszqfmxyrpwfsdmnlfdfwrcj7clx30kcecty3gte7ekvjeekkx2q9vvgjpsg5pzzqxjc9xv3rlhu2n6u87pm94agwcmvcywwsx9k0jpvwyng8crytgrkcpzqae6amp5xy03x2lsklv5zgnmeht0grzns27tmsjtsg2j0ne2969kqyqsxpqpqqqqqgsxqfmxyrpwfsdmnlfdfwrcj7clx30kcecty3gte7ekvjeekkx2q9vvgxqk3htqx4qqqzqqqqqqsqqqqqyqqqqqqqqyqqqqqqqqear8ke';
// tapping 5 times invisible button is a backdoor:
for (let c = 0; c <= 5; c++) {
await element(by.id('ScanQrBackdoorButton')).tap();
await sleep(1000);
}
await element(by.id('scanQrBackdoorInput')).replaceText(unsignedPsbt);
await element(by.id('scanQrBackdoorOkButton')).tap();
// now lets test scanning back QR with UR PSBT. this should lead straight to broadcast dialog
await element(by.id('PsbtWithHardwareScrollView')).swipe('up', 'fast', 1); // in case emu screen is small and it doesnt fit
await element(by.id('PsbtTxScanButton')).tap(); // opening camera
// tapping 5 times invisible button is a backdoor:
for (let c = 0; c <= 5; c++) {
await element(by.id('ScanQrBackdoorButton')).tap();
await sleep(1000);
}
await element(by.id('scanQrBackdoorInput')).replaceText(signedPsbt);
await element(by.id('scanQrBackdoorOkButton')).tap();
await expect(element(by.id('ScanQrBackdoorButton'))).toBeNotVisible();
await yo('PsbtWithHardwareWalletBroadcastTransactionButton');
2021-09-16 20:56:09 +02:00
await device.pressBack();
await device.pressBack();
await device.pressBack();
await helperDeleteWallet('Imported Watch-only');
2020-05-23 08:50:08 +02:00
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
2020-08-11 20:16:24 +02:00
it('should handle URL successfully', async () => {
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
2020-10-06 13:23:13 +02:00
return console.warn('skipping', JSON.stringify(jasmine.currentTest.fullName), 'as it previously passed on Travis');
2020-08-11 20:16:24 +02:00
}
if (!process.env.HD_MNEMONIC_BIP84) {
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
return;
}
await helperImportWallet(process.env.HD_MNEMONIC_BIP84, 'Imported HD SegWit (BIP84 Bech32 Native)', '0.00105526 BTC');
await device.launchApp({
newInstance: true,
url: 'bitcoin:BC1QH6TF004TY7Z7UN2V5NTU4MKF630545GVHS45U7\\?amount=0.0001\\&label=Yo',
});
// setting fee rate:
const feeRate = 2;
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText(feeRate + '');
await element(by.text('OK')).tap();
2021-09-13 19:43:26 +02:00
if (process.env.TRAVIS) await sleep(5000);
2020-08-11 20:16:24 +02:00
try {
await element(by.id('CreateTransactionButton')).tap();
} catch (_) {}
// created. verifying:
await yo('TransactionValue');
expect(element(by.id('TransactionValue'))).toHaveText('0.0001');
expect(element(by.id('TransactionAddress'))).toHaveText('BC1QH6TF004TY7Z7UN2V5NTU4MKF630545GVHS45U7');
2020-11-05 18:17:27 +01:00
2021-09-16 20:56:09 +02:00
await device.pressBack();
await device.pressBack();
await helperDeleteWallet('Imported HD SegWit (BIP84 Bech32 Native)', '105526');
2020-11-05 18:17:27 +01:00
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
2020-08-11 20:16:24 +02:00
});
2020-11-05 18:17:27 +01:00
it('can import multisig setup from UR (ver1) QRs (2 frames), and create tx', async () => {
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
return console.warn('skipping', JSON.stringify(jasmine.currentTest.fullName), 'as it previously passed on Travis');
}
await yo('WalletsList');
await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit
// going to Import Wallet screen and importing mnemonic
await element(by.id('CreateAWallet')).tap();
await element(by.id('ImportWallet')).tap();
await element(by.id('ScanImport')).tap();
const urs = [
'UR:BYTES/1OF2/J8RX04F2WJ9SSY577U30R55ELM4LUCJCXJVJTD60SYV9A286Q0AQH7QXL6/TYQMJGEQGFK82E2HV9KXCET5YPXH2MR5D9EKJEEQWDJHGATSYPNXJMR9PG3JQARGD9EJQENFD3JJQCM0DE6XZ6TWWVSX7MNV0YS8QATZD35KXGRTV4UHXGRPDEJZQ6TNYPEKZEN9YP6X7Z3RYPJXJUM5WF5KYAT5V5SXZMT0DENJQCM0WD5KWMN9WFES5GC2FESK6EF6YPXH2MR5D9EKJEEQ2ESH2MR5PFGX7MRFVDUN5GPJYPHKVGPJPFZX2UNFWESHG6T0DCAZQMF0XSUZWTESYUHNQFE0XGNS53N0WFKKZAP6YPGRY46NFQ9Q53PNXAZ5Z3PC8QAZQKNSW43RWDRFDFCXV6Z92F9YU6NGGD94S5NNWP2XGNZ22C6K2M69D4F4YKNYFPC5GANS',
'UR:BYTES/2OF2/J8RX04F2WJ9SSY577U30R55ELM4LUCJCXJVJTD60SYV9A286Q0AQH7QXL6/8944VARY2EZHJ62CDVMHQKRC2F3XVKN629M8X3ZXWPNYGJZ9FPT8G4NS0Q6YG73EG3R42468DCE9S6E40FRN2AF5X4G4GNTNT9FNYAN2DA5YU5G2PGCNVWZYGSMRQVE6YPD8QATZXU6K6S298PZK57TC2DAX772SD4RKUEP4G5MY672YXAQ5C36WDEJ8YA2HWC6NY7RS0F5K6KJ3FD6KKAMKG4N9S4ZGW9K5SWRWVF3XXDNRVDGR2APJV9XNXMTHWVEHQJ6E2DHYKUZTF4XHJARYVF8Y2KJX24UYK7N6W3V5VNFC2PHQ5ZSJDYL5T',
];
await waitFor(element(by.id('UrProgressBar'))).toBeNotVisible();
for (const ur of urs) {
// tapping 5 times invisible button is a backdoor:
for (let c = 0; c <= 5; c++) {
await element(by.id('ScanQrBackdoorButton')).tap();
}
await element(by.id('scanQrBackdoorInput')).replaceText(ur);
await element(by.id('scanQrBackdoorOkButton')).tap();
await waitFor(element(by.id('UrProgressBar'))).toBeVisible();
}
if (process.env.TRAVIS) await sleep(60000);
await sup('OK', 3 * 61000); // waiting for wallet import
await element(by.text('OK')).tap();
// ok, wallet imported
// lets go inside wallet
const expectedWalletLabel = 'Multisig Vault';
await element(by.text(expectedWalletLabel)).tap();
2020-11-05 18:17:27 +01:00
// sending...
await element(by.id('SendButton')).tap();
await element(by.id('AddressInput')).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
await element(by.id('BitcoinAmountInput')).typeText('0.0005\n');
// setting fee rate:
const feeRate = 1;
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText(feeRate + '');
await element(by.text('OK')).tap();
if (process.env.TRAVIS) await sleep(5000);
try {
await element(by.id('CreateTransactionButton')).tap();
} catch (_) {}
await waitFor(element(by.id('ItemUnsigned'))).toBeVisible();
await waitFor(element(by.id('ItemSigned'))).toBeNotVisible(); // not a single green checkmark
await element(by.id('ProvideSignature')).tap();
await element(by.id('CosignedScanOrImportFile')).tap();
const ursSignedByColdcard = [
'UR:BYTES/1OF3/AZ7UYD5YRTJGJHEPJC045UPYV6SRFURJALR0968WCXHP0ZZ9HK0SV8YW98/TYP6XURNVF607QGQ05PQQQQQQ89FQE9NVDRHTESWDDWCDCAMW53Z2H5U50T6ZRT2JVDQF3Y4QCJESQQQQQQQPLLLLLLSY5XRQQQQQQQQQQTQQ9R75WZLX547D94TPAHFFG8WPC7X6JC555C7U5QQQQQQQQQZYQPQTAH0P50D4QLFTN049K7LLDCWH7CS3ZKJY9G8XEGV63P308HSH9ZSQQQQQQQQZQ82QGQQQQQQQYQUWDNXP9500ELRFUSTVYLPAQJC34GUCHG52MH2665NTY5WSCJS72GPQQQQQQQPQQQGQQJS4YQSQQQQQQQZYQPQY8RVFMEZUPXNJSKTRUVR389TNY9XLLX7JNYE08K5FJP5R8A2UFN6TYSZQQQQQQQQZCQPFRSXCZANQ56CMLH79RF2KUUEZTAXF7QE6QJ8XPZQYGPSL9CGL7X73GC8MACPM7SNEHE2FCAP5LRUG436ZNX6YF39R7E5Y5PZQ4CAXM6KQM64TAC99DCV6THY7U9S4VL8N5XR53FX55WLPQSG4H86QYSSYUSJAUY6JC734K9PDVN9C72HFZ84ELQ9E4W8GP66RAQ2HQY3YRH8QQQQQQQPQY44P2GPQQQQQQQQYGQZQGWXCNHJ9CZD89PVK8CC8ZW2HXG2DL7DA9XFJ70DGNYRGX',
'UR:BYTES/2OF3/AZ7UYD5YRTJGJHEPJC045UPYV6SRFURJALR0968WCXHP0ZZ9HK0SV8YW98/064CN8YGPQXR4WY5H9Q3AE3PTAKH7XX0ZET9N09T8UFWJX9NXA59L7APH8X2ERGUCYGQ3QFQ4CKRKRQP9ZC7T85KSRA46NT9FYEKCLPZQ9X7RNA3QL9MS4Y0XSYGQSNZX89397SCJFJ4AP9UAS7PX5R0RMDTQN6YLGKPT53D05LN7QGCQSZP282GSSXR4WY5H9Q3AE3PTAKH7XX0ZET9N09T8UFWJX9NXA59L7APH8X2ERYYPN4DHVYFGS5VWQ69YPL8DE0DRZPSNPH84FTN4QK7UWHNPZP4SDQPZJ4C3QVQCW4CJJU5Z8HXY90K6LCCEUT9VKDU4VL396GCKVMKSHLM5XUUETYVWPDRWKQVCQQQYQQQQQPQQQQQQGQQSQQZQQQQQQQQPSQQQQYGRQXW4KAS39ZZ33CRG5S8UAH9A5VGXZVXU749WW5ZMM367VYGXKP5QYRNFHATVGXQQQPQQQQQQGQQQQQZQQYQQQSQQQQQQQQVQQQQQQQQQSZ36JYYP33R60ZM9YYRTYN73MJKP6UZQANNWM7GRRMX067ATJGTQG0EZ0CDFPQDP6C5KEPNKKXMNPYYPVRWLRWVQ76ZKKPKAH9K84NZRE00DMXZHGC54WYGPQXXY0FUTV5SSDVJ068W2C8TSGRKWDM0EQV0VELTM4',
'UR:BYTES/3OF3/AZ7UYD5YRTJGJHEPJC045UPYV6SRFURJALR0968WCXHP0ZZ9HK0SV8YW98/WFPVPPLYFLP4RNFHATVGXQQQPQQQQQQGQQQQQZQQYQQQSQQSQQQQQQQQQQPZQGP58TZJMYXW6CMWVYSS9SDMUDESRMG26CXMKUKC7KVG09AAHVC2ARQUZ6XAVQESQQQGQQQQQZQQQQQQSQPQQQYQQYQQQQQQQQQQQQQH8NSKY',
];
for (const ur of ursSignedByColdcard) {
// tapping 5 times invisible button is a backdoor:
for (let c = 0; c <= 5; c++) {
2020-11-05 18:17:27 +01:00
await element(by.id('ScanQrBackdoorButton')).tap();
}
await element(by.id('scanQrBackdoorInput')).replaceText(ur);
await element(by.id('scanQrBackdoorOkButton')).tap();
await waitFor(element(by.id('UrProgressBar'))).toBeVisible();
}
await waitFor(element(by.id('ItemSigned'))).toBeVisible(); // one green checkmark visible
await element(by.id('ProvideSignature')).tap();
await element(by.id('CosignedScanOrImportFile')).tap();
const urSignedByColdcardAndCobo = [
'UR:BYTES/1OF3/CL7WUCY4FVHCWAA0TRRKJ0A5CA3JZTYP2L9PS2ZMPUG59ARUW2US09Z73L/TYP5CURNVF607QGQ05PQQQQQQ89FQE9NVDRHTESWDDWCDCAMW53Z2H5U50T6ZRT2JVDQF3Y4QCJESQQQQQQQPLLLLLLSY5XRQQQQQQQQQQTQQ9R75WZLX547D94TPAHFFG8WPC7X6JC555C7U5QQQQQQQQQZYQPQTAH0P50D4QLFTN049K7LLDCWH7CS3ZKJY9G8XEGV63P308HSH9ZSQQQQQQQQZQ82QGQQQQQQQYQUWDNXP9500ELRFUSTVYLPAQJC34GUCHG52MH2665NTY5WSCJS72GPQQQQQQQPQQQGQQJS4YQSQQQQQQQZYQPQY8RVFMEZUPXNJSKTRUVR389TNY9XLLX7JNYE08K5FJP5R8A2UFN6TYSZQQQQQQQQZCQPFRSXCZANQ56CMLH79RF2KUUEZTAXF7QE6QJ8XPZQYGPSL9CGL7X73GC8MACPM7SNEHE2FCAP5LRUG436ZNX6YF39R7E5Y5PZQ4CAXM6KQM64TAC99DCV6THY7U9S4VL8N5XR53FX55WLPQSG4H86QYSSYUSJAUY6JC734K9PDVN9C72HFZ84ELQ9E4W8GP66RAQ2HQY3YRH8QQQQQQQPQY44P2GPQQQQQQQQYGQZQGWXCNHJ9CZD89PVK8CC8ZW2HXG2DL7DA9XFJ70DGNYRGX',
'UR:BYTES/2OF3/CL7WUCY4FVHCWAA0TRRKJ0A5CA3JZTYP2L9PS2ZMPUG59ARUW2US09Z73L/064CN8QYYDKPQQGUCYGQ3QFQ4CKRKRQP9ZC7T85KSRA46NT9FYEKCLPZQ9X7RNA3QL9MS4Y0XSYGQSNZX89397SCJFJ4AP9UAS7PX5R0RMDTQN6YLGKPT53D05LN7QGCQ5SVZ9QGSSP5QHQ4KYKXRNW60UDHHKQVXK0M9XNETCY9P5EA2JUFVU0YTYNJZAQGSZQAYTRQWNMLYTLYL9UN4V4TZ6LPU4YYJNKRJLSZDNPAQRXR90LEQPGAFZZQCW4CJJU5Z8HXY90K6LCCEUT9VKDU4VL396GCKVMKSHLM5XUUETYVSSXW4KAS39ZZ33CRG5S8UAH9A5VGXZVXU749WW5ZMM367VYGXKP5QY22HQQQQPQ9R4YGGRRZ8579K2GGXKF8ARH9VR4CYPM8XAHUSX8KVL4A6HYSKQSLJYLS6JZQ6R43FDJR8DVDHXZGGZCXA7XUCPA59DVRDMWTV0TXY8J77MKV9W33F2UGSZQVVG7NCKEFPQ6EYL5WU4SWHQS8VUMKLJQC7EN7HH2UJZCZR7GN7R28XN06KCSVQQQZQQQQQQSQQQQQYQQGQQPQQPQQQQQQQQQQQZYQSRGWK99KGVA43KUCFPQTQMHCMNQ8KS44SDHDED3AVCS7TMMWES46XPC95D6CPNQQQQSQQQQQYQQQQQ',
'UR:BYTES/3OF3/CL7WUCY4FVHCWAA0TRRKJ0A5CA3JZTYP2L9PS2ZMPUG59ARUW2US09Z73L/PQQZQQQGQQGQQQQQQQQQQQQQGND7FE',
];
for (const ur of urSignedByColdcardAndCobo) {
// tapping 5 times invisible button is a backdoor:
for (let c = 0; c <= 5; c++) {
2020-11-05 18:17:27 +01:00
await element(by.id('ScanQrBackdoorButton')).tap();
}
await element(by.id('scanQrBackdoorInput')).replaceText(ur);
await element(by.id('scanQrBackdoorOkButton')).tap();
await waitFor(element(by.id('UrProgressBar'))).toBeVisible();
}
await waitFor(element(by.id('ExportSignedPsbt'))).toBeVisible();
await element(by.id('PsbtMultisigConfirmButton')).tap();
// created. verifying:
await yo('TransactionValue');
expect(element(by.id('TransactionValue'))).toHaveText('0.0005');
await element(by.id('TransactionDetailsButton')).tap();
const txhex = await extractTextFromElementById('TxhexInput');
const transaction = bitcoin.Transaction.fromHex(txhex);
assert.ok(transaction.ins.length === 1 || transaction.ins.length === 2); // depending on current fees gona use either 1 or 2 inputs
assert.strictEqual(transaction.outs.length, 2);
assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[0].script), 'bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl'); // to address
assert.strictEqual(transaction.outs[0].value, 50000);
2021-09-16 20:56:09 +02:00
await device.pressBack();
await device.pressBack();
await device.pressBack();
await device.pressBack();
await helperDeleteWallet(expectedWalletLabel, '108880');
2020-11-05 18:17:27 +01:00
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
2020-08-11 20:16:24 +02:00
});
it('can manage UTXO', async () => {
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
return console.warn('skipping', JSON.stringify(jasmine.currentTest.fullName), 'as it previously passed on Travis');
}
await helperImportWallet(process.env.HD_MNEMONIC_BIP84, 'Imported HD SegWit (BIP84 Bech32 Native)', '0.00105526 BTC');
// refresh transactions
await element(by.id('refreshTransactions')).tap();
await waitFor(element(by.id('NoTxBuyBitcoin')))
.not.toExist()
.withTimeout(300 * 1000);
// change note of 0.001 tx output
await element(by.text('0.001')).atIndex(0).tap();
2021-09-06 17:10:09 +02:00
await element(by.text('Details')).tap();
await expect(element(by.text('49944e90fe917952e36b1967cdbc1139e60c89b4800b91258bf2345a77a8b888'))).toBeVisible();
await element(by.type('android.widget.EditText')).typeText('test1');
await element(by.text('Save')).tap();
await element(by.text('OK')).tap();
// back to wallet screen
await device.pressBack();
await device.pressBack();
// open CoinControl
await element(by.id('SendButton')).tap();
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('CoinControl')).tap();
await waitFor(element(by.id('Loading'))) // wait for outputs to be loaded
.not.toExist()
.withTimeout(300 * 1000);
await expect(element(by.text('test1')).atIndex(0)).toBeVisible();
// change output note and freeze it
await element(by.text('test1')).atIndex(0).tap();
await element(by.id('OutputMemo')).replaceText('test2');
await element(by.type('android.widget.CompoundButton')).tap(); // freeze switch
await device.pressBack(); // closing modal
await expect(element(by.text('test2')).atIndex(0)).toBeVisible();
2020-12-10 17:44:14 +01:00
await expect(element(by.text('Freeze')).atIndex(0)).toBeVisible();
// use frozen output to create tx using "Use coin" feature
await element(by.text('test2')).atIndex(0).tap();
await element(by.id('UseCoin')).tap();
await element(by.id('AddressInput')).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('sendMaxButton')).tap();
await element(by.text('OK')).tap();
// setting fee rate:
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText('1');
await element(by.text('OK')).tap();
2021-09-13 19:43:26 +02:00
if (process.env.TRAVIS) await sleep(5000);
await element(by.id('CreateTransactionButton')).tap();
await element(by.id('TransactionDetailsButton')).tap();
const txhex1 = await extractTextFromElementById('TxhexInput');
const tx1 = bitcoin.Transaction.fromHex(txhex1);
assert.strictEqual(tx1.outs.length, 1);
assert.strictEqual(tx1.outs[0].script.toString('hex'), '00147ea385f352be696ab0f6e94a0ee0e3c6d4b14a53');
assert.strictEqual(tx1.outs[0].value, 99888);
assert.strictEqual(tx1.ins.length, 1);
assert.strictEqual(tx1.ins[0].hash.toString('hex'), '88b8a8775a34f28b25910b80b4890ce63911bccd67196be3527991fe904e9449');
assert.strictEqual(tx1.ins[0].index, 0);
// back to wallet screen
await device.pressBack();
await device.pressBack();
await device.pressBack();
// create tx with unfrozen input
await element(by.id('SendButton')).tap();
await element(by.id('AddressInput')).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('sendMaxButton')).tap();
await element(by.text('OK')).tap();
// setting fee rate:
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText('1');
await element(by.text('OK')).tap();
2021-09-13 19:43:26 +02:00
if (process.env.TRAVIS) await sleep(5000);
await element(by.id('CreateTransactionButton')).tap();
await element(by.id('TransactionDetailsButton')).tap();
const txhex2 = await extractTextFromElementById('TxhexInput');
const tx2 = bitcoin.Transaction.fromHex(txhex2);
assert.strictEqual(tx2.outs.length, 1);
assert.strictEqual(tx2.outs[0].script.toString('hex'), '00147ea385f352be696ab0f6e94a0ee0e3c6d4b14a53');
assert.strictEqual(tx2.outs[0].value, 5414);
assert.strictEqual(tx2.ins.length, 1);
assert.strictEqual(tx2.ins[0].hash.toString('hex'), '761aec51100404365f4078f1a1c728a833915b877d0db208e8b7bfd451d08aa5');
assert.strictEqual(tx2.ins[0].index, 1);
2021-09-16 20:56:09 +02:00
await device.pressBack();
await device.pressBack();
await device.pressBack();
await device.pressBack();
await helperDeleteWallet('Imported HD SegWit (BIP84 Bech32 Native)', '105526');
2021-09-16 20:56:09 +02:00
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
it('can import HD wallet with a passphrase', async () => {
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
return console.warn('skipping', JSON.stringify(jasmine.currentTest.fullName), 'as it previously passed on Travis');
}
await helperImportWallet(
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
'Imported HD SegWit (BIP84 Bech32 Native)',
'0 BTC',
'BlueWallet',
);
await element(by.id('ReceiveButton')).tap();
try {
// in case emulator has no google services and doesnt support pushes
// we just dont show this popup
await element(by.text(`No, and don’t ask me again`)).tap();
} catch (_) {}
await yo('BitcoinAddressQRCodeContainer');
// check if imported wallet has correct recive address
await expect(element(by.id('AddressValue'))).toHaveText('bc1qe8q660wfj6uvqg7zyn86jcsux36natklqnfdrc');
2021-09-16 20:56:09 +02:00
await device.pressBack();
await device.pressBack();
await helperDeleteWallet('Imported HD SegWit (BIP84 Bech32 Native)');
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
2020-03-16 14:51:08 +01:00
});
2020-03-19 16:51:35 +01:00
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
2020-03-19 17:55:35 +01:00
async function yo(id, timeout = 33000) {
2020-03-19 16:51:35 +01:00
return waitFor(element(by.id(id)))
.toBeVisible()
.withTimeout(timeout);
}
async function sup(text, timeout = 33000) {
return waitFor(element(by.text(text)))
.toBeVisible()
.withTimeout(timeout);
}
2020-03-19 16:51:35 +01:00
async function helperCreateWallet(walletName) {
await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit
2020-03-19 16:51:35 +01:00
await element(by.id('CreateAWallet')).tap();
await element(by.id('WalletNameInput')).replaceText(walletName || 'cr34t3d');
2020-03-19 17:55:35 +01:00
await yo('ActivateBitcoinButton');
2020-03-19 16:51:35 +01:00
await element(by.id('ActivateBitcoinButton')).tap();
await element(by.id('ActivateBitcoinButton')).tap();
// why tf we need 2 taps for it to work..? mystery
await element(by.id('Create')).tap();
2020-03-19 17:55:35 +01:00
await yo('PleaseBackupScrollView');
2020-03-19 16:51:35 +01:00
await element(by.id('PleaseBackupScrollView')).swipe('up', 'fast', 1); // in case emu screen is small and it doesnt fit
2020-03-19 17:55:35 +01:00
await yo('PleasebackupOk');
2020-03-19 16:51:35 +01:00
await element(by.id('PleasebackupOk')).tap();
await expect(element(by.id('WalletsList'))).toBeVisible();
await element(by.id('WalletsList')).swipe('right', 'fast', 1); // in case emu screen is small and it doesnt fit
2020-03-19 16:51:35 +01:00
await expect(element(by.id(walletName || 'cr34t3d'))).toBeVisible();
}
async function helperImportWallet(importText, expectedWalletLabel, expectedBalance, passphrase) {
await yo('WalletsList');
2021-02-26 11:27:42 +01:00
await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit
// going to Import Wallet screen and importing mnemonic
await element(by.id('CreateAWallet')).tap();
await element(by.id('ImportWallet')).tap();
await element(by.id('MnemonicInput')).replaceText(importText);
2021-02-26 11:27:42 +01:00
let retries = 0;
while (true) {
retries = retries + 1;
try {
await element(by.id('DoImport')).tap();
} catch (_) {}
let passphraseAsked = false;
try {
await sup('Passphrase', 3000);
passphraseAsked = true;
} catch (e) {} // passphrase not asked
if (passphraseAsked) {
// enter passphrase if needed
if (passphrase) {
await element(by.type('android.widget.EditText')).typeText(passphrase);
}
await element(by.text('OK')).tap();
}
2021-02-26 11:27:42 +01:00
if (process.env.TRAVIS) await sleep(60000);
// waiting for import result
await sup('OK', 3 * 61000);
await element(by.text('OK')).tap();
try {
await expect(element(by.id('ImportError'))).not.toBeVisible();
break; // import succeded
} catch (e) {
// exit after two failed attempts
if (retries === 2) break;
// restart import
await element(by.id('ImportError')).tap();
await element(by.text('Try again')).tap();
}
}
// lets go inside wallet
await element(by.text(expectedWalletLabel)).tap();
// label might change in the future
expect(element(by.id('WalletBalance'))).toHaveText(expectedBalance);
}
2020-05-23 08:50:08 +02:00
function hashIt(s) {
return createHash('sha256').update(s).digest().toString('hex');
2020-05-23 08:50:08 +02:00
}
/**
* a hack to extract element text. warning, this might break in future
* @see https://github.com/wix/detox/issues/445
*
* @returns {Promise<string>}
*/
async function extractTextFromElementById(id) {
try {
await expect(element(by.id(id))).toHaveText('_unfoundable_text');
} catch (error) {
if (device.getPlatform() === 'ios') {
const start = `accessibilityLabel was "`;
const end = '" on ';
const errorMessage = error.message.toString();
const [, restMessage] = errorMessage.split(start);
const [label] = restMessage.split(end);
return label;
} else {
const start = 'Got:';
const end = '}"';
const errorMessage = error.message.toString();
const [, restMessage] = errorMessage.split(start);
const [label] = restMessage.split(end);
const value = label.split(',');
const combineText = value.find(i => i.includes('text=')).trim();
const [, elementText] = combineText.split('=');
return elementText;
}
}
}
const expectToBeVisible = async id => {
try {
await expect(element(by.id(id))).toBeVisible();
return true;
} catch (e) {
return false;
}
};
2021-09-16 20:56:09 +02:00
async function helperDeleteWallet(label, remainingBalanceSat = false) {
await element(by.text(label)).tap();
await element(by.id('WalletDetails')).tap();
await element(by.id('WalletDetailsScroll')).swipe('up', 'fast', 1);
await element(by.id('DeleteButton')).tap();
await sup('Yes, delete');
await element(by.text('Yes, delete')).tap();
if (remainingBalanceSat) {
await element(by.type('android.widget.EditText')).typeText(remainingBalanceSat);
await element(by.text('OK')).tap();
}
await expect(element(by.id('NoTransactionsMessage'))).toBeVisible();
}