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