const { execSync, spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const { WalletClient, ANYONE_KEY, buildP2PKH } = require('./wallet-client');
const { fetchBeef, buildAtomicBEEF, sleep } = require('./woc');
const BSV = path.resolve(__dirname, '../../../target/release/bsv-wallet');
const FUNDER_PORT = parseInt(process.env.FUNDER_PORT || '3320');
const FUNDER_DIR = process.env.FUNDER_DIR || path.resolve(__dirname, '..', '..', '..', 'e2e-funder');
async function startWallet(port, name) {
const dir = fs.mkdtempSync(`/tmp/bsv-wallet-${name}-`);
execSync(`cd "${dir}" && "${BSV}" init`, { stdio: 'pipe' });
const proc = spawn(BSV, ['--port', String(port), 'serve'], {
cwd: dir,
stdio: ['ignore', 'pipe', 'pipe'],
detached: false,
});
const client = new WalletClient(port, name);
for (let i = 0; i < 20; i++) {
try {
await client.isAuthenticated();
break;
} catch {
if (i === 19) throw new Error(`${name} on :${port} failed to start`);
await sleep(500);
}
}
const identityKey = await client.identityKey();
const addrResult = execSync(`cd "${dir}" && "${BSV}" address`, { encoding: 'utf-8' }).trim();
return { dir, proc, client, identityKey, address: addrResult, port };
}
async function fundWallet(wallet, satoshis) {
const funderClient = new WalletClient(FUNDER_PORT, 'funder');
const result = await funderClient.transferTo(wallet.client, satoshis, 'e2e funding');
return { txid: result.txid, sats: satoshis };
}
async function sweepToFunder(wallet) {
const balance = await wallet.client.balance();
if (balance < 600) {
console.log(` ${wallet.client.name}: ${balance} sats (dust, skipping sweep)`);
return 0;
}
const funderClient = new WalletClient(FUNDER_PORT, 'funder');
const funderKey = await funderClient.getPublicKey(
[2, '3241645161d8'], 'SfKxPIJNgdI= NaGLC6fMH50=', ANYONE_KEY, true,
);
const funderScript = buildP2PKH(funderKey.publicKey);
let sweepAmount = balance - 300; let swept = 0;
for (let attempt = 0; attempt < 4 && sweepAmount >= 600; attempt++) {
try {
const result = await wallet.client.createAction(
[{ lockingScript: funderScript, satoshis: sweepAmount, outputDescription: 'sweep to funder' }],
`sweep ${sweepAmount} sats back to funder`,
);
await funderClient.internalizeAction(result.tx, [{
outputIndex: 0,
protocol: 'wallet payment',
paymentRemittance: {
derivationPrefix: 'SfKxPIJNgdI=',
derivationSuffix: 'NaGLC6fMH50=',
senderIdentityKey: ANYONE_KEY,
},
}], `sweep from ${wallet.client.name}`);
console.log(` ${wallet.client.name}: swept ${sweepAmount} sats back to funder`);
swept += sweepAmount;
break;
} catch (e) {
const balNow = await wallet.client.balance().catch(() => balance);
if (balNow < balance - 500) {
console.log(` ${wallet.client.name}: sweep timed out but balance dropped (${balance} → ${balNow}) — tx went through`);
swept += balance - balNow;
break;
}
if (attempt < 3) {
const prev = sweepAmount;
sweepAmount = Math.floor(sweepAmount / 2);
console.log(` ${wallet.client.name}: sweep ${prev} failed (${e.message.slice(0, 60)}), retrying with ${sweepAmount}`);
} else {
console.log(` WARNING: ${wallet.client.name}: could not sweep (${sweepAmount} sats stuck: ${e.message.slice(0, 80)})`);
}
}
}
return swept;
}
function teardownWallet(wallet) {
if (wallet.proc && !wallet.proc.killed) {
wallet.proc.kill('SIGTERM');
}
if (wallet.dir && fs.existsSync(wallet.dir)) {
fs.rmSync(wallet.dir, { recursive: true, force: true });
}
}
function extractJson(output) {
const lines = output.split('\n');
for (let i = lines.length - 1; i >= 0; i--) {
const line = lines[i].trim();
if (line.startsWith('{')) return line;
}
throw new Error(`No JSON found in output: ${output.slice(0, 200)}`);
}
function parseCliJson(output) {
const trimmed = output.trim();
try { return JSON.parse(trimmed); } catch { }
const start = trimmed.indexOf('{');
const end = trimmed.lastIndexOf('}');
if (start === -1 || end === -1 || end <= start) {
throw new Error(`No JSON found in output: ${trimmed.slice(0, 200)}`);
}
return JSON.parse(trimmed.slice(start, end + 1));
}
module.exports = { startWallet, fundWallet, sweepToFunder, teardownWallet, extractJson, parseCliJson, BSV, FUNDER_PORT };