aboutsummaryrefslogtreecommitdiffstats
path: root/tools/minimal-smoke.js
blob: 76162d5cd15e82726a74ac20ede70545b5abe64d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// Smoke test for the minimal build profile (COINOS_FEATURES=none): a plain
// on-chain wallet with no swap/Ark/gift/SP surfaces — and still fully working
// (imports a wallet, receives a payment) against the regtest stack.
//
// Usage: bun tools/minimal-smoke.js

import { execSync } from 'node:child_process';
import puppeteer from './puppeteer.js';
import { generateMnemonic } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english';
import { buildHtml } from '../build.js';

const BCLI = `docker exec bc bitcoin-cli -rpcwallet=coinos`;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const sh = (cmd) => execSync(cmd, { shell: '/bin/bash' }).toString().trim();

let ok = true;
const check = (name, cond, detail = '') => {
  console.log(` ${cond ? '✓' : '✗'} ${name}${detail ? ' — ' + detail : ''}`);
  if (!cond) ok = false;
};

// --- build the minimal profile and serve it ---
const html = await buildHtml({ minify: true, pwa: false, features: 'none' });
const full = await buildHtml({ minify: true, pwa: false });
console.log(`minimal bundle: ${(html.length / 1024).toFixed(0)}KB (full: ${(full.length / 1024).toFixed(0)}KB)`);
check('meaningfully smaller than full', html.length < full.length * 0.9);
check('no ark protocol code', !html.includes('Ark VTXO mailbox authorization'));
check('no swap protocol code', !html.includes('swap/reverse'));
check('no musig code', !html.includes('MuSig/noncecoef'));
check('no silent-payment engine', !html.includes('BIP0352') && !html.includes('__SP_WORKER__'));
// The sync EVENT KIND must be gone; the coinos relay hostname alone is not a
// sync marker anymore — recipient search (send-to-npub, core UX) carries
// PROFILE_RELAYS for profile lookups even in the minimal build.
check('no nostr sync code', !html.includes('30078'));

const server = Bun.serve({ port: 5198, fetch: () => new Response(html, { headers: { 'content-type': 'text/html' } }) });

const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] });
const page = await browser.newPage();
const errs = [];
page.on('pageerror', (e) => errs.push(e.message));
const bodyText = () => page.evaluate(() => document.body.innerText);
const waitText = async (text, ms = 20000) => {
  for (let i = 0; i < ms / 250; i++) {
    if ((await bodyText()).toLowerCase().includes(text.toLowerCase())) return true;
    await sleep(250);
  }
  return false;
};
const clickText = (sel, text) => page.evaluate((s, x) => {
  const el = [...document.querySelectorAll(s)].find((e) => e.textContent.trim().toLowerCase().includes(x.toLowerCase()));
  if (el) { el.click(); return true; }
  return false;
}, sel, text);

try {
  await page.goto('http://localhost:5198', { waitUntil: 'domcontentloaded' });
  await page.evaluate(() => {
    localStorage.setItem('btc-wallet-network', 'regtest');
    localStorage.setItem('btc-wallet-source:regtest', JSON.stringify({ id: 'custom', url: 'http://localhost:30002' }));
    localStorage.setItem('btc-wallet-ark-provider:regtest', 'local'); // must be ignored by this build
  });
  await page.reload({ waitUntil: 'domcontentloaded' });
  await sleep(400);

  console.log('\n[1] import + core wallet');
  await clickText('button', 'Create a new wallet'); // the front door gained a step
  await sleep(300);
  await clickText('button', 'Have an existing seed');
  await sleep(200);
  await page.type('textarea', generateMnemonic(wordlist));
  await clickText('button', 'Open wallet');
  check('wallet opened', await waitText('receive', 15000));

  console.log('\n[2] no feature surfaces');
  await sleep(2500); // give any (wrongly present) feature time to appear
  check('no receive-mode dropdown', await page.evaluate(() =>
    ![...document.querySelectorAll('option')].some((o) => ['ln', 'ark', 'sp'].includes(o.value))));
  await clickText('.tabs button', 'Send');
  await sleep(300);
  check('no gift button', !(await bodyText()).includes('Gift some Bitcoin'));
  await clickText('.tabs button', 'Settings');
  await sleep(300);
  const settings = await bodyText();
  check('no swap provider card', !settings.includes('Swap provider'));
  check('no ark card', !/⚔ Ark/.test(settings));
  check('no sp indexer card', !settings.includes('Silent payment indexer'));
  check('no sync card', !settings.toLowerCase().includes('sync'));

  console.log('\n[3] core receive still works');
  await clickText('.tabs button', 'Receive');
  await sleep(300);
  const addr = await page.evaluate(() => document.querySelector('.addr-box')?.textContent.trim());
  check('onchain address shown', /^bcrt1/.test(addr || ''), (addr || '').slice(0, 16) + '…');
  sh(`${BCLI} sendtoaddress ${addr} 0.0004`);
  sh(`${BCLI} generatetoaddress 1 $(${BCLI} getnewaddress) >/dev/null`);
  const landed = await waitText('40,000', 30000);
  if (!landed) console.log('  [balance area]', (await bodyText()).slice(0, 200).replace(/\n+/g, ' | '));
  check('payment lands', landed);
  check('no page errors', errs.length === 0, errs.join(' | '));

  console.log(ok ? '\n✅ SUCCESS: minimal profile is a working on-chain-only wallet'
                : '\n❌ some checks failed');
} finally {
  await browser.close();
  server.stop(true);
}
process.exit(ok ? 0 : 1);