⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions ci_run_android.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
31 changes: 31 additions & 0 deletions docs/mainnet-nightly.md
Original file line number Diff line number Diff line change
@@ -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`)
14 changes: 11 additions & 3 deletions test/helpers/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -606,9 +610,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...');
}
}
Expand Down
21 changes: 14 additions & 7 deletions test/helpers/constants.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -21,19 +21,26 @@ export function getAppPath(): string {
export const bitcoinURL =
process.env.BITCOIN_RPC_URL ?? 'http://polaruser:[email protected]: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 =
Expand Down
28 changes: 23 additions & 5 deletions test/helpers/regtest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -44,6 +50,7 @@ async function localMineBlocks(count: number): Promise<void> {
// Blocktank backend (regtest API over HTTPS)

async function blocktankDeposit(address: string, amountSat?: number): Promise<string> {
requireRegtestBackend('deposit');
const url = `${blocktankURL}/regtest/chain/deposit`;
const body: { address: string; amountSat?: number } = { address };
if (amountSat !== undefined) {
Expand All @@ -68,6 +75,7 @@ async function blocktankDeposit(address: string, amountSat?: number): Promise<st
}

async function blocktankMineBlocks(count: number): Promise<void> {
requireRegtestBackend('mineBlocks');
const url = `${blocktankURL}/regtest/chain/mine`;

console.info(`→ [blocktank] Mining ${count} block(s)...`);
Expand All @@ -86,6 +94,7 @@ async function blocktankMineBlocks(count: number): Promise<void> {
}

async function blocktankPayInvoice(invoice: string, amountSat?: number): Promise<string> {
requireRegtestBackend('payInvoice');
const url = `${blocktankURL}/regtest/channel/pay`;
const body: { invoice: string; amountSat?: number } = { invoice };
if (amountSat !== undefined) {
Expand Down Expand Up @@ -165,6 +174,9 @@ export async function getExternalAddress(): Promise<string> {
const rpc = getRpc();
return rpc.getNewAddress();
}
if (backend === 'mainnet') {
throw new Error('getExternalAddress() is not available with BACKEND=mainnet');
}
return REGTEST_TEST_ADDRESS;
}

Expand All @@ -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');
}

/**
Expand All @@ -209,9 +223,11 @@ export async function deposit(address: string, amountSat?: number): Promise<stri
const backend = getBackend();
if (backend === 'local') {
return localDeposit(address, amountSat);
} else {
}
if (backend === 'regtest') {
return blocktankDeposit(address, amountSat);
}
throw new Error('deposit() is not available with BACKEND=mainnet');
}

/**
Expand All @@ -224,9 +240,11 @@ export async function mineBlocks(count: number = 1): Promise<void> {
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');
}

/**
Expand All @@ -239,8 +257,8 @@ export async function mineBlocks(count: number = 1): Promise<void> {
*/
export async function payInvoice(invoice: string, amountSat?: number): Promise<string> {
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);
}
10 changes: 10 additions & 0 deletions test/specs/mainnet.cjit.e2e.ts
Original file line number Diff line number Diff line change
@@ -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.');
}
);
});
48 changes: 48 additions & 0 deletions test/specs/mainnet.strike.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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,
reinstall: false,
expectAndroidAlert: false,
});

await enterAddress(strikeLnAddress!, { acceptCameraPermission: false });

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');
});
});