diff options
| author | Adam Soltys <[email protected]> | 2026-07-04 07:53:03 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-07-04 07:53:03 -0700 |
| commit | 2da22a3dc82bc8c0beb456097c2749ebc02d8017 (patch) | |
| tree | 8560c3a3bb161045e57031bce0e796544653f6be | |
| parent | d8da74e50a374d59629b8ec8731e8cb38bc6b42e (diff) | |
| download | coinosv3-2da22a3dc82bc8c0beb456097c2749ebc02d8017.tar.gz coinosv3-2da22a3dc82bc8c0beb456097c2749ebc02d8017.zip | |
Gift claim: make the wallet chooser read as a choice
The existing-wallet buttons looked like inert inputs. Now: a "Receive
into one of your wallets:" label, a chevron on each wallet row so it
reads as tappable, and an "or" divider before the new-wallet button.
(tools/ark/gift-shot.js: screenshot harness that mints a real regtest
gift and opens its link.)
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
| -rw-r--r-- | src/app.js | 10 | ||||
| -rw-r--r-- | src/i18n.js | 2 | ||||
| -rw-r--r-- | tools/ark/gift-shot.js | 82 |
3 files changed, 91 insertions, 3 deletions
@@ -2302,11 +2302,15 @@ function claimChooseView() { h('p', { class: 'muted', style: 'margin:0' }, t('claimIntoPrompt')) ), h('div', { class: 'card col gap6' }, + h('div', { class: 'small muted' }, t('claimIntoExisting')), ...accounts.map((a) => - h('button', { class: 'btn-block', style: 'text-align:left', onClick: () => claimIntoAccount(a, code) }, - a.label, - a.type === 'watch' ? h('span', { class: 'small faint', style: 'margin-left:6px' }, '(' + t('watchOnly') + ')') : null) + h('button', { class: 'btn-block', style: 'display:flex;justify-content:space-between;align-items:center;text-align:left', onClick: () => claimIntoAccount(a, code) }, + h('span', {}, + a.label, + a.type === 'watch' ? h('span', { class: 'small faint', style: 'margin-left:6px' }, '(' + t('watchOnly') + ')') : null), + h('span', { class: 'muted', style: 'font-size:18px;line-height:1' }, '\u203a')) ), + h('div', { class: 'small faint', style: 'text-align:center' }, t('claimOr')), h('button', { class: 'btn-primary btn-block', onClick: () => { ui.claimChoose = null; enterWallet(newMnemonic(), '', { gift: code }); } }, t('claimNewWallet')) ) ); diff --git a/src/i18n.js b/src/i18n.js index 9cbc09d..552795d 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -290,6 +290,8 @@ const STR = { claimBody: 'Claim it into a brand-new wallet that only you control.', claimIntoPrompt: 'Choose where to receive it.', claimNewWallet: 'Receive into a new wallet', + claimIntoExisting: 'Receive into one of your wallets:', + claimOr: 'or', goToWallet: 'Go to wallet', giftClaimedToast: 'Gift claimed', claimBtn: 'Claim it', diff --git a/tools/ark/gift-shot.js b/tools/ark/gift-shot.js new file mode 100644 index 0000000..9874e8c --- /dev/null +++ b/tools/ark/gift-shot.js @@ -0,0 +1,82 @@ +// Screenshot the gift-claim wallet chooser: mint a real gift from a funded +// wallet, open its link in the same browser (wallet 1 still in session), +// capture the "choose where to receive" screen. +import { spawn, execSync } from 'node:child_process'; +import puppeteer from 'puppeteer-core'; +import { generateMnemonic } from '@scure/bip39'; +import { wordlist } from '@scure/bip39/wordlists/english'; + +const PORT = 5199; +const APP = `http://localhost:${PORT}`; +const BCLI = `bitcoin-cli -regtest -datadir=${process.env.HOME}/ark-regtest/bitcoind -rpcport=18543 -rpcwallet=miner`; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +const sh = (cmd) => execSync(cmd, { shell: '/bin/bash' }).toString().trim(); + +const dev = spawn('bun', ['run', 'dev'], { cwd: process.env.HOME + '/halwallet', env: { ...process.env, PORT: String(PORT) }, stdio: 'ignore' }); +for (let i = 0; i < 30; i++) { try { if ((await fetch(APP)).ok) break; } catch {} await sleep(500); } + +const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] }); +const page = await browser.newPage(); +await page.setViewport({ width: 420, height: 860, deviceScaleFactor: 2 }); +const clickText = (sel, text) => page.evaluate((sel, text) => { + const el = [...document.querySelectorAll(sel)].find((e) => e.textContent.trim().toLowerCase().includes(text.toLowerCase())); + if (el) { el.click(); return true; } return false; +}, sel, text); +const waitText = async (text, ms = 20000) => { + for (let i = 0; i < ms / 250; i++) { + if (await page.evaluate((t) => document.body.innerText.toLowerCase().includes(t.toLowerCase()), text)) return true; + await sleep(250); + } + return false; +}; + +await page.goto(APP, { 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'); +}); +await page.reload({ waitUntil: 'domcontentloaded' }); +await sleep(500); +await clickText('button', 'Import existing'); +await sleep(200); +await page.type('textarea', generateMnemonic(wordlist)); +await clickText('button', 'Open wallet'); +await waitText('receive'); + +// fund on-chain, confirmed +const addr = await page.evaluate(() => document.querySelector('.addr-box')?.textContent.trim()); +sh(`${BCLI} sendtoaddress ${addr} 0.0005`); +sh(`${BCLI} generatetoaddress 1 $(${BCLI} getnewaddress) >/dev/null`); +await waitText('50,000', 30000); +if (await page.evaluate(() => document.body.innerText.includes('Payment received'))) { + await clickText('.card', 'Payment received'); + await sleep(300); +} + +// make a 2000-sat gift +await clickText('.tabs button', 'Send'); +await sleep(300); +await clickText('button', 'Gift some Bitcoin'); +await sleep(300); +const amtInput = await page.evaluateHandle(() => [...document.querySelectorAll('input[type="number"]')][0]); +await amtInput.asElement().type('2000'); +await clickText('button', 'Create gift link'); +await sleep(500); +if (await page.evaluate(() => document.body.innerText.includes('Lock the whole coin'))) { + await clickText('button', 'Lock the whole coin'); +} +await waitText('/g/', 20000); +const link = await page.evaluate(() => + [...document.querySelectorAll('.addr-box')].map((e) => e.textContent.trim()).find((x) => x.includes('/g/'))); +console.log('gift link:', (link || '').slice(0, 50) + '…'); + +// open the gift link — wallet 1 exists, so the chooser should appear +await page.goto(link, { waitUntil: 'domcontentloaded' }); +const shown = await waitText('Choose where', 15000); +console.log('chooser shown:', shown); +await sleep(400); +await page.screenshot({ path: '/tmp/gift-choose.png' }); +console.log('done'); +await browser.close(); +dev.kill(); |
