From 068f1e7ee74520d1e8e16dfe12da3b65d381773f Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Wed, 11 Feb 2026 12:23:33 +0100 Subject: [PATCH 1/2] feat: prepare for mainnet nightly --- ci_run_android.sh | 15 ++++++----- docs/mainnet-nightly.md | 31 +++++++++++++++++++++ test/helpers/actions.ts | 8 ++++-- test/helpers/constants.ts | 21 ++++++++++----- test/helpers/regtest.ts | 28 +++++++++++++++---- test/specs/mainnet.cjit.e2e.ts | 10 +++++++ test/specs/mainnet.strike.e2e.ts | 46 ++++++++++++++++++++++++++++++++ 7 files changed, 139 insertions(+), 20 deletions(-) create mode 100644 docs/mainnet-nightly.md create mode 100644 test/specs/mainnet.cjit.e2e.ts create mode 100644 test/specs/mainnet.strike.e2e.ts diff --git a/ci_run_android.sh b/ci_run_android.sh index 992dba2..e6329b4 100755 --- a/ci_run_android.sh +++ b/ci_run_android.sh @@ -27,12 +27,15 @@ cleanup() { } trap cleanup EXIT INT TERM -# regtest port -adb reverse tcp:60001 tcp:60001 -# lnd port -adb reverse tcp:9735 tcp:9735 -# lnurl port -adb reverse tcp:30001 tcp:30001 +# local/regtest helper ports +if [[ "${BACKEND:-local}" != "mainnet" ]]; then + # regtest electrum port + adb reverse tcp:60001 tcp:60001 + # lnd port + adb reverse tcp:9735 tcp:9735 + # lnurl server port + adb reverse tcp:30001 tcp:30001 +fi # show touches adb shell settings put system show_touches 1 diff --git a/docs/mainnet-nightly.md b/docs/mainnet-nightly.md new file mode 100644 index 0000000..df301e0 --- /dev/null +++ b/docs/mainnet-nightly.md @@ -0,0 +1,31 @@ +# Mainnet nightly consumption + +`bitkit-e2e-tests` is intentionally source-only for mainnet smoke coverage. + +Private orchestration (workflows, secrets, logs, artifacts) should live in `bitkit-nightly`. + +## Current smoke specs + +- `test/specs/mainnet.strike.e2e.ts` (active) +- `test/specs/mainnet.cjit.e2e.ts` (skipped placeholder for phase 2) + +## Tag-driven selection + +Private orchestration should prefer tag-based filtering over hardcoded single-spec execution. +In `bitkit-nightly`, grep patterns are hardcoded in a matrix so test targets can be parallelized. + +Recommended grep patterns: + +- strike only: `@strike_mainnet` +- cjit only: `@cjit_mainnet` +- wos only: `@wos_mainnet` + +## Mainnet execution contract + +- set `BACKEND=mainnet` +- provide `APP_ID_ANDROID=to.bitkit` +- provide test-specific seed variables: + - `STRIKE_SEED` + - `WOS_SEED` + - `CJIT_SEED` +- provide release APK at `aut/bitkit_e2e.apk` (or set `NATIVE_APK_PATH`) diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 5221cb7..5256a83 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -606,9 +606,13 @@ export async function restoreWallet( } if (expectBackGroundPaymentsSheet) { - try { await dismissBackgroundPaymentsTimedSheet();} catch { + try { + await dismissBackgroundPaymentsTimedSheet(); + } catch { console.info('→ Could not dismiss background payments timed sheet, trying again...'); - try { await dismissBackgroundPaymentsTimedSheet({ triggerTimedSheet: true });} catch { + try { + await dismissBackgroundPaymentsTimedSheet({ triggerTimedSheet: true }); + } catch { console.info('→ No background payments timed sheet to dismiss, continuing...'); } } diff --git a/test/helpers/constants.ts b/test/helpers/constants.ts index a9b2cef..5fa82c9 100644 --- a/test/helpers/constants.ts +++ b/test/helpers/constants.ts @@ -1,6 +1,6 @@ export const APP_ID = { - android: 'to.bitkit.dev', - ios: 'to.bitkit', + android: process.env.APP_ID_ANDROID ?? 'to.bitkit.dev', + ios: process.env.APP_ID_IOS ?? 'to.bitkit', }; export function getAppId(): string { @@ -21,19 +21,26 @@ export function getAppPath(): string { export const bitcoinURL = process.env.BITCOIN_RPC_URL ?? 'http://polaruser:polarpass@127.0.0.1:43782'; -export type Backend = 'local' | 'regtest'; +export type Backend = 'local' | 'regtest' | 'mainnet'; export function getBackend(): Backend { const backend = process.env.BACKEND || 'local'; - if (backend !== 'local' && backend !== 'regtest') { - throw new Error(`Invalid BACKEND: ${backend}. Expected 'local' or 'regtest'.`); + if (backend !== 'local' && backend !== 'regtest' && backend !== 'mainnet') { + throw new Error(`Invalid BACKEND: ${backend}. Expected 'local', 'regtest', or 'mainnet'.`); } return backend; } export const electrumHost = - getBackend() === 'regtest' ? 'electrs.bitkit.stag0.blocktank.to' : '127.0.0.1'; -export const electrumPort = getBackend() === 'regtest' ? 9999 : 60001; + getBackend() === 'regtest' + ? process.env.ELECTRUM_HOST ?? 'electrs.bitkit.stag0.blocktank.to' + : getBackend() === 'mainnet' + ? process.env.ELECTRUM_HOST ?? 'electrum.bitkit.to' + : process.env.ELECTRUM_HOST ?? '127.0.0.1'; +export const electrumPort = Number.parseInt( + process.env.ELECTRUM_PORT ?? (getBackend() === 'regtest' ? '9999' : getBackend() === 'mainnet' ? '50001' : '60001'), + 10 +); // Blocktank API for regtest operations (deposit, mine blocks, pay invoices) export const blocktankURL = diff --git a/test/helpers/regtest.ts b/test/helpers/regtest.ts index 01583d6..7e2b4d0 100644 --- a/test/helpers/regtest.ts +++ b/test/helpers/regtest.ts @@ -12,6 +12,12 @@ import { bitcoinURL, blocktankURL, getBackend, type Backend } from './constants' export { getBackend, type Backend }; +function requireRegtestBackend(operation: string): void { + if (getBackend() !== 'regtest') { + throw new Error(`${operation} is only available with BACKEND=regtest`); + } +} + // Local backend (Bitcoin RPC) let _rpc: BitcoinJsonRpc | null = null; @@ -44,6 +50,7 @@ async function localMineBlocks(count: number): Promise { // Blocktank backend (regtest API over HTTPS) async function blocktankDeposit(address: string, amountSat?: number): Promise { + requireRegtestBackend('deposit'); const url = `${blocktankURL}/regtest/chain/deposit`; const body: { address: string; amountSat?: number } = { address }; if (amountSat !== undefined) { @@ -68,6 +75,7 @@ async function blocktankDeposit(address: string, amountSat?: number): Promise { + requireRegtestBackend('mineBlocks'); const url = `${blocktankURL}/regtest/chain/mine`; console.info(`→ [blocktank] Mining ${count} block(s)...`); @@ -86,6 +94,7 @@ async function blocktankMineBlocks(count: number): Promise { } async function blocktankPayInvoice(invoice: string, amountSat?: number): Promise { + requireRegtestBackend('payInvoice'); const url = `${blocktankURL}/regtest/channel/pay`; const body: { invoice: string; amountSat?: number } = { invoice }; if (amountSat !== undefined) { @@ -165,6 +174,9 @@ export async function getExternalAddress(): Promise { const rpc = getRpc(); return rpc.getNewAddress(); } + if (backend === 'mainnet') { + throw new Error('getExternalAddress() is not available with BACKEND=mainnet'); + } return REGTEST_TEST_ADDRESS; } @@ -188,13 +200,15 @@ export async function sendToAddress( ? (amountBtcOrSats / 100_000_000).toString() : amountBtcOrSats; return rpc.sendToAddress(address, btc); - } else { + } + if (backend === 'regtest') { const sats = typeof amountBtcOrSats === 'string' ? Math.round(parseFloat(amountBtcOrSats) * 100_000_000) : amountBtcOrSats; return blocktankDeposit(address, sats); } + throw new Error('sendToAddress() is not available with BACKEND=mainnet'); } /** @@ -209,9 +223,11 @@ export async function deposit(address: string, amountSat?: number): Promise { const backend = getBackend(); if (backend === 'local') { return localMineBlocks(count); - } else { + } + if (backend === 'regtest') { return blocktankMineBlocks(count); } + throw new Error('mineBlocks() is not available with BACKEND=mainnet'); } /** @@ -239,8 +257,8 @@ export async function mineBlocks(count: number = 1): Promise { */ export async function payInvoice(invoice: string, amountSat?: number): Promise { const backend = getBackend(); - if (backend === 'local') { - throw new Error('payInvoice is only available with BACKEND=regtest (Blocktank API)'); + if (backend !== 'regtest') { + throw new Error('payInvoice() is only available with BACKEND=regtest (Blocktank API)'); } return blocktankPayInvoice(invoice, amountSat); } diff --git a/test/specs/mainnet.cjit.e2e.ts b/test/specs/mainnet.cjit.e2e.ts new file mode 100644 index 0000000..931cb5c --- /dev/null +++ b/test/specs/mainnet.cjit.e2e.ts @@ -0,0 +1,10 @@ +import { ciIt } from '../helpers/suite'; + +describe('@cjit_mainnet - CJIT smoke', () => { + ciIt.skip( + '@cjit_1 - Can create and settle CJIT invoice', + async () => { + throw new Error('Enable after mainnet CJIT flow is validated.'); + } + ); +}); diff --git a/test/specs/mainnet.strike.e2e.ts b/test/specs/mainnet.strike.e2e.ts new file mode 100644 index 0000000..f1b7135 --- /dev/null +++ b/test/specs/mainnet.strike.e2e.ts @@ -0,0 +1,46 @@ +import { + dragOnElement, + elementById, + enterAddress, + restoreWallet, + sleep, + tap, +} from '../helpers/actions'; +import { ciIt } from '../helpers/suite'; + +const strikeSeed = process.env.STRIKE_SEED; +const strikeLnAddress = process.env.STRIKE_LN_ADDR; +const strikeAmountSats = Number.parseInt(process.env.STRIKE_AMOUNT_SATS ?? '5', 10); + +describe('@strike_mainnet - Strike smoke', () => { + before(() => { + if (!strikeSeed) { + throw new Error('Missing STRIKE_SEED env var'); + } + if (!strikeLnAddress) { + throw new Error('Missing STRIKE_LN_ADDR env var'); + } + if (!Number.isInteger(strikeAmountSats) || strikeAmountSats <= 0) { + throw new Error(`Invalid STRIKE_AMOUNT_SATS value: ${process.env.STRIKE_AMOUNT_SATS}`); + } + }); + + ciIt('@strike_1 - Can pay Strike LN address', async () => { + await restoreWallet(strikeSeed!, { + expectBackupSheet: false, + }); + + await enterAddress(strikeLnAddress!, { acceptCameraPermission: true }); + + const digits = `${strikeAmountSats}`.split(''); + for (const digit of digits) { + await tap(`N${digit}`); + await sleep(150); + } + + await tap('ContinueAmount'); + await dragOnElement('GRAB', 'right', 0.95); + await elementById('SendSuccess').waitForDisplayed({ timeout: 60_000 }); + await tap('Close'); + }); +}); From a78a50080413a2d946c72e7e0dbe09c233259887 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Wed, 11 Feb 2026 19:00:43 +0100 Subject: [PATCH 2/2] adjust strike test --- test/helpers/actions.ts | 6 +++++- test/specs/mainnet.strike.e2e.ts | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 5256a83..2b843ab 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -543,12 +543,14 @@ export async function restoreWallet( expectBackupSheet = false, expectBackGroundPaymentsSheet = false, reinstall = true, + expectAndroidAlert = true, }: { passphrase?: string; expectQuickPayTimedSheet?: boolean; expectBackupSheet?: boolean; expectBackGroundPaymentsSheet?: boolean; reinstall?: boolean; + expectAndroidAlert?: boolean; } = {} ) { console.info('→ Restoring wallet with seed:', seed); @@ -595,7 +597,9 @@ export async function restoreWallet( await getStarted.waitForDisplayed({ timeout: 120000 }); await tap('GetStartedButton'); await sleep(1000); - await handleAndroidAlert(); + if (expectAndroidAlert) { + await handleAndroidAlert(); + } if (expectBackupSheet) { await dismissBackupTimedSheet(); diff --git a/test/specs/mainnet.strike.e2e.ts b/test/specs/mainnet.strike.e2e.ts index f1b7135..d3193e1 100644 --- a/test/specs/mainnet.strike.e2e.ts +++ b/test/specs/mainnet.strike.e2e.ts @@ -28,9 +28,11 @@ describe('@strike_mainnet - Strike smoke', () => { ciIt('@strike_1 - Can pay Strike LN address', async () => { await restoreWallet(strikeSeed!, { expectBackupSheet: false, + reinstall: false, + expectAndroidAlert: false, }); - await enterAddress(strikeLnAddress!, { acceptCameraPermission: true }); + await enterAddress(strikeLnAddress!, { acceptCameraPermission: false }); const digits = `${strikeAmountSats}`.split(''); for (const digit of digits) {