mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 09:50:15 +01:00
730 lines
34 KiB
JavaScript
730 lines
34 KiB
JavaScript
import assert from 'assert';
|
|
import * as bitcoin from 'bitcoinjs-lib';
|
|
|
|
import {
|
|
expectToBeVisible,
|
|
extractTextFromElementById,
|
|
hashIt,
|
|
helperCreateWallet,
|
|
helperDeleteWallet,
|
|
helperSwitchAdvancedMode,
|
|
sleep,
|
|
sup,
|
|
yo,
|
|
} from './helperz';
|
|
|
|
/**
|
|
* this testsuite is for test cases that require no wallets to be present
|
|
*/
|
|
|
|
beforeAll(async () => {
|
|
// reinstalling the app just for any case to clean up app's storage
|
|
await device.launchApp({ delete: true });
|
|
}, 300_000);
|
|
|
|
describe('BlueWallet UI Tests - no wallets', () => {
|
|
it('selftest passes', async () => {
|
|
const lockFile = '/tmp/travislock.' + hashIt('t1');
|
|
if (process.env.TRAVIS) {
|
|
if (require('fs').existsSync(lockFile)) return console.warn('skipping', JSON.stringify('t1'), 'as it previously passed on Travis');
|
|
}
|
|
await device.launchApp({ newInstance: true });
|
|
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);
|
|
await device.pressBack();
|
|
await device.pressBack();
|
|
await device.pressBack();
|
|
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
|
|
});
|
|
|
|
it('all settings screens work', async () => {
|
|
const lockFile = '/tmp/travislock.' + hashIt('t2');
|
|
if (process.env.TRAVIS) {
|
|
if (require('fs').existsSync(lockFile)) return console.warn('skipping', JSON.stringify('t2'), 'as it previously passed on Travis');
|
|
}
|
|
await device.launchApp({ newInstance: true });
|
|
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();
|
|
|
|
// privacy
|
|
// trigger switches
|
|
await element(by.id('SettingsPrivacy')).tap();
|
|
await element(by.id('ClipboardSwitch')).tap();
|
|
await element(by.id('ClipboardSwitch')).tap();
|
|
await element(by.id('QuickActionsSwitch')).tap();
|
|
await element(by.id('QuickActionsSwitch')).tap();
|
|
await device.pressBack();
|
|
|
|
// enable AdvancedMode
|
|
await element(by.id('AdvancedMode')).tap();
|
|
await device.pressBack();
|
|
// disable it:
|
|
await element(by.id('GeneralSettings')).tap();
|
|
await element(by.id('AdvancedMode')).tap();
|
|
await device.pressBack();
|
|
//
|
|
// 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('Price is 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('');
|
|
await expect(element(by.id('SSLPortInput'))).toHaveToggleValue(false);
|
|
await device.pressBack();
|
|
|
|
// network -> lightning
|
|
// change URI and revert it back
|
|
/* muted since https://lndhub.herokuapp.com is down
|
|
await element(by.id('LightningSettings')).tap();
|
|
await element(by.id('URIInput')).replaceText('invalid\n');
|
|
await element(by.id('Save')).tap();
|
|
await sup('OK');
|
|
await expect(element(by.text('Invalid LNDHub URI'))).toBeVisible();
|
|
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();
|
|
*/
|
|
|
|
// 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();
|
|
await device.pressBack();
|
|
} else {
|
|
await device.pressBack();
|
|
}
|
|
|
|
// 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();
|
|
|
|
// 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();
|
|
await device.pressBack();
|
|
|
|
// about
|
|
await element(by.id('AboutButton')).tap();
|
|
await device.pressBack();
|
|
await device.pressBack();
|
|
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 () => {
|
|
const lockFile = '/tmp/travislock.' + hashIt('t3');
|
|
if (process.env.TRAVIS) {
|
|
if (require('fs').existsSync(lockFile)) return console.warn('skipping', JSON.stringify('t3'), 'as it previously passed on Travis');
|
|
}
|
|
await device.launchApp({ newInstance: true });
|
|
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
|
|
await element(by.text(`No, and do not ask me again.`)).tap();
|
|
await element(by.text(`No, and do not ask me again.`)).tap(); // sometimes the first click doesnt work (detox issue, not app's)
|
|
} catch (_) {}
|
|
await yo('BitcoinAddressQRCodeContainer');
|
|
await yo('CopyTextToClipboard');
|
|
await element(by.id('SetCustomAmountButton')).tap();
|
|
await element(by.id('BitcoinAmountInput')).replaceText('1');
|
|
await element(by.id('CustomAmountDescription')).typeText('test');
|
|
await element(by.id('CustomAmountDescription')).tapReturnKey();
|
|
await element(by.id('CustomAmountSaveButton')).tap();
|
|
await sup('1 BTC');
|
|
await sup('test');
|
|
await yo('BitcoinAddressQRCodeContainer');
|
|
await yo('CopyTextToClipboard');
|
|
await device.pressBack();
|
|
await device.pressBack();
|
|
await helperDeleteWallet('cr34t3d');
|
|
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
|
|
});
|
|
|
|
it('can encrypt storage, with plausible deniabilityl decrypt fake storage', async () => {
|
|
const lockFile = '/tmp/travislock.' + hashIt('t4');
|
|
if (process.env.TRAVIS) {
|
|
if (require('fs').existsSync(lockFile)) return console.warn('skipping', JSON.stringify('t4'), 'as it previously passed on Travis');
|
|
}
|
|
await device.launchApp({ newInstance: true });
|
|
await yo('WalletsList');
|
|
|
|
// lets create a wallet
|
|
await helperCreateWallet();
|
|
|
|
// go to settings
|
|
await expect(element(by.id('SettingsButton'))).toBeVisible();
|
|
await element(by.id('SettingsButton')).tap();
|
|
await expect(element(by.id('SecurityButton'))).toBeVisible();
|
|
|
|
// 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/@rneui/themed/@rneui/themed/issues/2519
|
|
await expect(element(by.id('PlausibleDeniabilityButton'))).toBeNotVisible();
|
|
|
|
if (device.getPlatform() === 'ios') {
|
|
console.warn('Android only test skipped');
|
|
return;
|
|
}
|
|
|
|
// lets encrypt the storage.
|
|
// first, trying to mistype second password:
|
|
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();
|
|
await element(by.type('android.widget.EditText')).typeText('666');
|
|
await element(by.text('OK')).tap();
|
|
await expect(element(by.text('Passwords do not match.'))).toBeVisible();
|
|
await element(by.text('OK')).tap();
|
|
|
|
// 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();
|
|
|
|
// relaunch app
|
|
await device.launchApp({ newInstance: true });
|
|
await waitFor(element(by.text('OK')))
|
|
.toBeVisible()
|
|
.withTimeout(33000);
|
|
|
|
// trying to decrypt with incorrect password
|
|
await expect(element(by.text('Your storage is encrypted. Password is required to decrypt it.'))).toBeVisible();
|
|
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();
|
|
|
|
// correct password
|
|
await element(by.type('android.widget.EditText')).typeText('qqq');
|
|
await element(by.text('OK')).tap();
|
|
await yo('WalletsList');
|
|
|
|
// previously created wallet should be visible
|
|
await expect(element(by.id('cr34t3d'))).toBeVisible();
|
|
|
|
// 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/@rneui/themed/@rneui/themed/issues/2519
|
|
await expect(element(by.id('PlausibleDeniabilityButton'))).toBeVisible();
|
|
await element(by.id('PlausibleDeniabilityButton')).tap();
|
|
|
|
// trying to enable plausible denability
|
|
await element(by.id('CreateFakeStorageButton')).tap();
|
|
await expect(element(by.text('Password for the fake storage should not match the password for your main storage.'))).toBeVisible();
|
|
|
|
// 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
|
|
await element(by.text('OK')).tap();
|
|
if (process.env.TRAVIS) await sleep(3000); // hopefully helps prevent crash
|
|
|
|
// trying new password, but will mistype
|
|
await element(by.id('CreateFakeStorageButton')).tap();
|
|
if (process.env.TRAVIS) await sleep(3000); // hopefully helps prevent crash
|
|
await element(by.type('android.widget.EditText')).typeText('passwordForFakeStorage');
|
|
await element(by.text('OK')).tap();
|
|
await expect(element(by.text('Re-type password'))).toBeVisible();
|
|
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();
|
|
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();
|
|
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 });
|
|
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();
|
|
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();
|
|
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();
|
|
|
|
// now derypting it, to cleanup
|
|
await element(by.id('SettingsButton')).tap();
|
|
await element(by.id('SecurityButton')).tap();
|
|
|
|
// correct password
|
|
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol
|
|
await element(by.text('OK')).tap();
|
|
await element(by.type('android.widget.EditText')).typeText('passwordForFakeStorage');
|
|
await element(by.text('OK')).tap();
|
|
|
|
await helperDeleteWallet('fake_wallet');
|
|
|
|
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
|
|
});
|
|
|
|
it('can encrypt storage, and decrypt storage works', async () => {
|
|
const lockFile = '/tmp/travislock.' + hashIt('t5');
|
|
if (process.env.TRAVIS) {
|
|
if (require('fs').existsSync(lockFile)) return console.warn('skipping', JSON.stringify('t5'), 'as it previously passed on Travis');
|
|
}
|
|
await device.launchApp({ newInstance: true });
|
|
await yo('WalletsList');
|
|
await helperCreateWallet();
|
|
await element(by.id('SettingsButton')).tap();
|
|
await element(by.id('SecurityButton')).tap();
|
|
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();
|
|
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();
|
|
await element(by.type('android.widget.EditText')).typeText('pass');
|
|
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();
|
|
|
|
// now go to settings, and decrypt
|
|
await element(by.id('SettingsButton')).tap();
|
|
await element(by.id('SecurityButton')).tap();
|
|
|
|
// putting FAKE storage password. should not succeed
|
|
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol
|
|
await element(by.text('OK')).tap();
|
|
await element(by.type('android.widget.EditText')).typeText('fake');
|
|
await element(by.text('OK')).tap();
|
|
await expect(element(by.text('Incorrect password. Please try again.'))).toBeVisible();
|
|
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();
|
|
await element(by.type('android.widget.EditText')).typeText('pass');
|
|
await element(by.text('OK')).tap();
|
|
|
|
// relaunch app
|
|
await device.launchApp({ newInstance: true });
|
|
await yo('cr34t3d'); // success
|
|
await helperDeleteWallet('cr34t3d');
|
|
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
|
|
});
|
|
|
|
it('can import 2of2 multisig using individual cosigners (1 signer, 1 xpub)', async () => {
|
|
const lockFile = '/tmp/travislock.' + hashIt('can import 2of2 multisig using individual cosigners (1 signer, 1 xpub)');
|
|
if (process.env.TRAVIS) {
|
|
if (require('fs').existsSync(lockFile)) return console.warn('skipping as it previously passed on Travis');
|
|
}
|
|
await device.launchApp({ newInstance: true });
|
|
await helperSwitchAdvancedMode();
|
|
await yo('WalletsList');
|
|
await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit
|
|
await sleep(200); // Wait until bounce animation finishes.
|
|
// going to Import Wallet screen and importing Vault
|
|
await element(by.id('CreateAWallet')).tap();
|
|
await yo('ActivateVaultButton');
|
|
await element(by.id('ActivateVaultButton')).tap();
|
|
await element(by.id('Create')).tap();
|
|
// vault settings:
|
|
await element(by.id('VaultAdvancedCustomize')).tap();
|
|
await element(by.id('DecreaseN')).tap();
|
|
await element(by.id('ModalDoneButton')).tap();
|
|
|
|
//
|
|
|
|
await element(by.id('LetsStart')).tap();
|
|
|
|
// key1 - seed:
|
|
|
|
await element(by.id('VaultCosignerImport1')).tap();
|
|
await element(by.id('ScanOrOpenFile')).tap();
|
|
|
|
await sleep(5000); // wait for camera screen to initialize
|
|
for (let c = 0; c <= 5; c++) {
|
|
await element(by.id('ScanQrBackdoorButton')).tap();
|
|
}
|
|
await element(by.id('scanQrBackdoorInput')).replaceText(
|
|
'pipe goose bottom run seed curious thought kangaroo example family coral success',
|
|
);
|
|
await element(by.id('scanQrBackdoorOkButton')).tap();
|
|
await element(by.id('DoImportKeyButton')).tap(); // when seed - need to extra tap the button
|
|
|
|
// key2 - xpub:
|
|
|
|
await element(by.id('VaultCosignerImport2')).tap();
|
|
await element(by.id('ScanOrOpenFile')).tap();
|
|
|
|
await sleep(5000); // wait for camera screen to initialize
|
|
for (let c = 0; c <= 5; c++) {
|
|
await element(by.id('ScanQrBackdoorButton')).tap();
|
|
}
|
|
await element(by.id('scanQrBackdoorInput')).replaceText(
|
|
'ur:crypto-account/oeadcypdlouebgaolytaadmetaaddloxaxhdclaxfdyksnwkuypkfevlfzfroyiyecoeosbakbpdcldawzhtcarkwsndcphphsbsdsayaahdcxfgjyckryosmwtdptlbflonbkimlsmovolslbytonayisprvoieftgeflzcrtvesbamtaaddyotadlocsdyykaeykaeykaoykaocypdlouebgaxaaaycyttatrnolimvetsst',
|
|
);
|
|
await element(by.id('scanQrBackdoorOkButton')).tap();
|
|
// when xpub - it automatically closes the modal, so no need to tap the button
|
|
|
|
await element(by.id('CreateButton')).tap();
|
|
await yo('Multisig Vault');
|
|
await element(by.id('Multisig Vault')).tap(); // go inside the wallet
|
|
|
|
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
|
|
await element(by.text(`No, and do not ask me again.`)).tap();
|
|
await element(by.text(`No, and do not ask me again.`)).tap(); // sometimes the first click doesnt work (detox issue, not app's)
|
|
} catch (_) {}
|
|
|
|
await sup('bc1qmf06nt4jhvzz4387ak8fecs42k6jqygr2unumetfc7xkdup7ah9s8phlup');
|
|
|
|
await device.pressBack();
|
|
|
|
await element(by.id('WalletDetails')).tap();
|
|
await sup('2 / 2 (native segwit)');
|
|
|
|
await device.pressBack();
|
|
await helperDeleteWallet('Multisig Vault');
|
|
await helperSwitchAdvancedMode(); // turn off advanced mode
|
|
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
|
|
});
|
|
|
|
it('can import multisig setup from UR, and create tx, and sign on hw devices', async () => {
|
|
const lockFile = '/tmp/travislock.' + hashIt('t6');
|
|
if (process.env.TRAVIS) {
|
|
if (require('fs').existsSync(lockFile)) return console.warn('skipping', JSON.stringify('t6'), 'as it previously passed on Travis');
|
|
}
|
|
await device.launchApp({ newInstance: true });
|
|
await yo('WalletsList');
|
|
await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit
|
|
await sleep(200); // Wait until bounce animation finishes.
|
|
// 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:
|
|
await sleep(5000); // wait for camera screen to initialize
|
|
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();
|
|
|
|
// 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 = 3;
|
|
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 ursSignedByPassport = [
|
|
'UR:CRYPTO-PSBT/22-4/LPCMAACFAXPLCYZTVYVOPKHDWPHKAXPYJOIHIDHNJSATRTSWEYGUHDURWYDECAGLAAHTTBHTFZFPWDRTLROXLUEHCXAHJTIHTEHDHKTEVTOTIOWFSKGEOSCFFLDRGLFTCYKELSRDNSHYGLLEVYIDGYZOEEDAAOENHGASFDHFVWNSATVYCFETATZSFROXFPMHGUJNWDSPNYMHHGPAIMGYURAYCXLEZEZSCLKBJZLFSRAOOYMSYNCEHDOSPYGTTDSODRSKLALBCAVYBNOLOEGSOYVOVLMWFDPFHGBAVDAEAEAEADADWMDTGDPTADAEADADSTENFYASFDTBCLDINBAOHFHYTPPKWYMSSNDKHKKNUOIELPDRKTOYHPCFCSWNFXPKFZNEPKVOIOCNAOAXMNPSKPLTGYFLRHLOHGUYKISWBWVEGUGMLAAYDLLDLSAAVDTDSADLIDFXYLKKFYURMTOXLKMDRSTYTERSJNHSBDPSGOGWJKJESTWLZCTKGE',
|
|
'UR:CRYPTO-PSBT/52-4/LPCSEEAACFAXPLCYZTVYVOPKHDWPPECPWPHNLBGMLDGOMWJYMDASCFYTOEGRTDGMZOCXFGFEGOSPBDSBISPKCNNYNBIORDRLRTRTHGAAHLGDFWJTVWCEYKGLVEIODSYKKNMSBGNYSAZEZEADGHSAAEAEAECPAMAXCSPLEHGDWFSAGETNWLPRECKOSKDWURMKMYSOASBEBDBNLBFMESCHZEVSJTJKDNADCEENTDROWFVYWEPDRNMDSNYKPMRYZMRLMNRYPAAYBKTDCLGDJKIYBNTYFXECMKWFLSWFWPCPGYBKEHSETTOECANTRHKGFGCLSROLMYLKNSOLHGGDHPOXWPMWCKLYETCLMWAMIDISHKKPJTWDHFPTECMOBALNDABSPTAXAEAELAAEADAEAELSAOGDPTAEAHFLGMCLAXDWPLAHBSMTLSHFPKRLMTIYLRYATNLGPLWFLYHFTOTLRDWZHKBWLAHNFNCPWTIMRFLUVYHLBWKBCXGYNLFYDPOL',
|
|
'UR:CRYPTO-PSBT/76-4/LPCSGSAACFAXPLCYZTVYVOPKHDWPBKEHRTTTFDCTNTRHKGFGCXSAHSRHWDMDTONBRLROWMSFCPBTHNTIAAGMPLAEAECPAOAXFXPSGMTABNWEIAJTHSCLAOSERKVLJKADWEBKTBBTRKJPTPYKMKLTMSRYRKDYPLLKCECMLGTBAXDYAEAELAAEAEAELAAEAEAELAAOAEAELAADAEAEAEAEAEAEAECPAOAXCSMYGWCMSGFWBTIENEOTRHHDFTVTLYTASNUYWZAMFSNLZSYLHGDKDWAYKBFYZTECCETEKBPMLODYAEAELAAEAEAELAAEAEAELAAOAEAELAADAEAEAEAEAEAEAEADADFLGMCLAXCSMYGWCMSGFWBTIENEOTRHHDFTVTLYTASNUYWZAMFSNLZSYLHGDKDWAYKBFYZTECCLAXFXPSGMTABNWEIAJTHSCLAOSERKVLJKADWEBKTBBTRKJPTPYKMKLTMSRYRKDYPLLKGMPLAEAEAEWLCMNTPF',
|
|
'UR:CRYPTO-PSBT/416-4/LPCFADNBAACFAXPLCYZTVYVOPKHDWPONBWDWPACLGTAEIDWLWZBAZODNCSMSEHZELBIYDLMWCSHDIADKNYWNZSSGPKVEFLMKLRKNCELEDAMYEMBYKIJKFNDEDMIAHPLBRDPMLTROWMFNWSLTROCMIOYKWPFZDSLGLGDKWSOYPFAHMODAMYENSAIOSEGAZEEHTSLBKGOEDMWZUTRFNYJEKIPEEMIYJSOTUEZERORFPSGRPABSSKLOGOONAECAGRSFBDLRWFEMLNSALRCWZOWNLPHNPTNSLPJTKBMTEYNSISTAFTEHSEGDOTENSNMHKNFGCLFXOXMYPLCELTKOMTNEGRTOJYGURHKGPMTAFHHKWPKIOTJPGWVSKNSKSSFTOYPTKKSGSRFGIORHMDDAFHNYKTHPCPOTKEGELANNLEWEGMJEKPIYGYSFECJNCWFNRYVLTBTBWPHTKBISTLRLDEMWADCWMNKTTARKDRJSZCJPLRCNFSHGNEGAMTYLVLGOWS',
|
|
];
|
|
|
|
for (const ur of ursSignedByPassport) {
|
|
// tapping 5 times invisible button is a backdoor:
|
|
await sleep(5000); // wait for camera screen to initialize
|
|
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();
|
|
}
|
|
|
|
await waitFor(element(by.id('ItemSigned'))).toBeVisible(); // one green checkmark visible
|
|
|
|
await element(by.id('ProvideSignature')).tap();
|
|
await element(by.id('CosignedScanOrImportFile')).tap();
|
|
|
|
const urSignedByPassportAndKeystone = [
|
|
'UR:CRYPTO-PSBT/105-2/LPCSINAOCFAXOLCYSBLUFDHSHKADTEHKAXOTJOJKIDJYZMADAEKIAOAEAEAEADSGMHIEQDIAFLKPVABAJEHLLNVLRKKPCPDAHYNSOTTSOYBTIMMUCYAASSMDAMDAMKAEAEAEAEAEZMZMZMZMAOGDSRAEAEAEAEAEAECMAEBBKBOTLPWFGMRNINIMPFYNWLGEBAVTVLSWTYPAGEGUWFVLAEAEAEAEAEAECPAECXHEJTWTTTWEPDFMMDSNYKDPRYZMRLBARSPAAYLETDCLGDJKIHBNTYFXCHNNWTRHFEAEAEAEAEAEADAEWDAOAEAEAEAEADADSTENIYASISYLVDVLGWCXRPBWVYVSDALOTLCESKTTFEJTWDTBPTECMOMNLNDABSDTADAEAEAEAEADAEAELAAOGDPTADAEAEAEAEAECPAECXCLSWSSWSCPVTGTESFWSBCTCSETNSPYNLBKJLZTUEMWSOMSNNTYGSLSFPNEPKVOIOONMOAOAEAEAEAEAECMAEBBMNAMRTRKDYGUHDURWSVOLGDRRLESMEDLOLGWLYNTAOFLDYFYAOCXDYYTJOMYYAUELEDYKIYLADUROYFNURDRGLFTCYKEKEFEIAOYGSTNCPIDGYZOEEDAAOCXHGCAENYKHNJLGOHEJOGMRLBNTDWYGWJOPFPYFMKKTISROXGMIMGYURAYCXLEUOZSADCLAOJPBGWSASPTIATTPMLECMPRIHSTMDJYLOYKTKRTHHTLSTFZKPOYWKBKROASBGBAVDAEAEAEAEADADDNGDPTADAEAEAEAEAECPAECXCLSWSSWSCPVTGTESFWSBCTCSETNSPYNLBKJLZTUEMWSOMSNNTYGSLSFPNEPKVOIOCPAOAXFTRPWPCPGYBKEHRTTTFDCTNTRHKGFGCXSAHSRHWDMDTONBRLROWMSFCPBTHNTIAAFLDYFYAOCXISRERKHDRDGAPMATEHJLFL',
|
|
'UR:CRYPTO-PSBT/158-2/LPCSNNAOCFAXOLCYSBLUFDHSHKADTEZECFFTHDETNBADMOCLINLNOSOXZEYKGDPYTPKTRETNURTIZOPDAOCXIAAOWETTJKMDUOSBONAASWNLMERLZSGLCYCTGAKBDAFHGHWKMTRSNLAAYKFWWPRSADADAHFLGMCLAXBAPLDADMGDFLRHLOHGUYHESWEOSKMDMTJLDRTKSSRDFGDWSNTNCHZEVSJTJKDNCNCLAXFTRPWPCPGYBKEHRTTTFDCTNTRHKGFGCXSAHSRHWDMDTONBRLROWMSFCPBTHNTIAAGMPLCPAMAXBAPLDADMGDFLRHLOHGUYHESWEOSKMDMTJLDRTKSSRDFGDWSNTNCHZEVSJTJKDNCNCECMLGTBAXDYAEAELAAEAEAELAAEAEAELAAOAEAELAAEAEAEAEAXAEAEAECPAMAXFTRPWPCPGYBKEHRTTTFDCTNTRHKGFGCXSAHSRHWDMDTONBRLROWMSFCPBTHNTIAACETEKBPMLODYAEAELAAEAEAELAAEAEAELAAOAEAELAAEAEAEAEAXAEAEAEAEAEADADFLGMCLAXCSMYGWCMSGFWBTIENEOTRHHDFTVTLYTASNUYWZAMFSNLZSYLHGDKDWAYKBFYZTECCLAXFXPSGMTABNWEIAJTHSCLAOSERKVLJKADWEBKTBBTRKJPTPYKMKLTMSRYRKDYPLLKGMPLCPAOAXCSMYGWCMSGFWBTIENEOTRHHDFTVTLYTASNUYWZAMFSNLZSYLHGDKDWAYKBFYZTECCETEKBPMLODYAEAELAAEAEAELAAEAEAELAAOAEAELAADAEAEAEAEAEAEAECPAOAXFXPSGMTABNWEIAJTHSCLAOSERKVLJKADWEBKTBBTRKJPTPYKMKLTMSRYRKDYPLLKCECMLGTBAXDYAEAELAAEAEAELAAEAEAELAAOAEAELAADAEAEAEAEAEAEAEAENNHKLKUO',
|
|
];
|
|
|
|
for (const ur of urSignedByPassportAndKeystone) {
|
|
// tapping 5 times invisible button is a backdoor:
|
|
await sleep(5000); // wait for camera screen to initialize
|
|
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();
|
|
}
|
|
|
|
await waitFor(element(by.id('ExportSignedPsbt'))).toBeVisible();
|
|
|
|
await element(by.id('PsbtMultisigConfirmButton')).tap();
|
|
|
|
// created. verifying:
|
|
await yo('TransactionValue');
|
|
await 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);
|
|
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);
|
|
|
|
await device.pressBack();
|
|
await device.pressBack();
|
|
await device.pressBack();
|
|
await device.pressBack();
|
|
await helperDeleteWallet(expectedWalletLabel, '108880');
|
|
|
|
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
|
|
});
|
|
|
|
it('can discover wallet account and import it', async () => {
|
|
const lockFile = '/tmp/travislock.' + hashIt('t7');
|
|
if (process.env.TRAVIS) {
|
|
if (require('fs').existsSync(lockFile)) return console.warn('skipping', JSON.stringify('t6'), 'as it previously passed on Travis');
|
|
}
|
|
await device.launchApp({ newInstance: true });
|
|
await yo('WalletsList');
|
|
|
|
// enable AdvancedMode to see derivation path in wallet details
|
|
await helperSwitchAdvancedMode();
|
|
|
|
await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit
|
|
await sleep(200); // Wait until bounce animation finishes.
|
|
// going to Import Wallet screen and importing mnemonic
|
|
await element(by.id('CreateAWallet')).tap();
|
|
await element(by.id('ScrollView')).swipe('up', 'fast', 0.9); // in case emu screen is small and it doesnt fit
|
|
await element(by.id('ImportWallet')).tap();
|
|
await element(by.id('MnemonicInput')).replaceText(
|
|
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
|
|
);
|
|
await element(by.id('AskPassphrase')).tap();
|
|
await element(by.id('SearchAccounts')).tap();
|
|
await element(by.id('DoImport')).tap();
|
|
await sleep(1000);
|
|
|
|
// cancel import and start over
|
|
await element(by.text('Cancel')).tap();
|
|
await element(by.id('DoImport')).tap();
|
|
await sleep(1000);
|
|
await element(by.text('OK')).tap();
|
|
await waitFor(element(by.id('Loading'))) // wait for discovery to be completed
|
|
.not.toExist()
|
|
.withTimeout(300 * 1000);
|
|
|
|
await expect(element(by.text("m/44'/0'/1'"))).toBeVisible();
|
|
await expect(element(by.text("m/49'/0'/0'"))).toBeVisible();
|
|
await expect(element(by.text("m/84'/0'/0'"))).toBeVisible();
|
|
|
|
// open custom derivation path screen and import the wallet
|
|
await element(by.id('CustomDerivationPathButton')).tap();
|
|
await element(by.id('DerivationPathInput')).replaceText("m/44'/0'/1'");
|
|
await waitFor(element(by.text('Found'))) // wait for discovery to be completed
|
|
.toExist()
|
|
.withTimeout(300 * 1000);
|
|
await element(by.text('Found')).tap();
|
|
await element(by.id('ImportButton')).tap();
|
|
await element(by.text('OK')).tap();
|
|
|
|
// go to wallet and check derivation path
|
|
await element(by.id('Imported HD Legacy (BIP44 P2PKH)')).tap();
|
|
await element(by.id('WalletDetails')).tap();
|
|
await expect(element(by.id('DerivationPath'))).toHaveText("m/44'/0'/1'");
|
|
|
|
await device.pressBack();
|
|
await device.pressBack();
|
|
await helperDeleteWallet('Imported HD Legacy (BIP44 P2PKH)');
|
|
await helperSwitchAdvancedMode();
|
|
|
|
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
|
|
});
|
|
});
|