diff --git a/packages/bitcore-cli/src/cli.ts b/packages/bitcore-cli/src/cli.ts index 448547cc2c0..5d803aa1594 100755 --- a/packages/bitcore-cli/src/cli.ts +++ b/packages/bitcore-cli/src/cli.ts @@ -21,7 +21,7 @@ const { version } = JSON.parse(fs.readFileSync(path.join(__dirname, '../../packa program .addHelpText('beforeAll', bitcoreLogo) - .usage(' [options]') + .usage('|list [options]') .description('A command line tool for Bitcore wallets') .argument('', 'Name of the wallet you want to create, join, or interact with. Use "list" to see all wallets in the specified directory.') .optionsGroup('Global Options') @@ -31,7 +31,6 @@ program .option('--no-status', 'Do not display the wallet status on startup. Defaults to true when running with --command') .option('-s, --pageSize ', 'Number of items per page of a list output', (value) => parseInt(value, 10), 10) .option('-v, --verbose', 'Show more data and logs') - .option('--list', 'See all wallets in the specified directory') .option('--register', 'Register the wallet with the Bitcore Wallet Service if it does not exist') .option('--walletId ', 'Support Staff Only: Wallet ID to provide support for') .option('-h, --help', 'Display help message. Use with --command to get help for a specific command') diff --git a/packages/bitcore-cli/src/commands/create/createThresholdSig.ts b/packages/bitcore-cli/src/commands/create/createThresholdSig.ts index ed6265f0729..67259b580d7 100644 --- a/packages/bitcore-cli/src/commands/create/createThresholdSig.ts +++ b/packages/bitcore-cli/src/commands/create/createThresholdSig.ts @@ -72,28 +72,47 @@ export async function createThresholdSigWallet( extra: tssPassword }); - const goBack = await prompt.select({ - message: `Join code for party ${i}:${os.EOL}${joinCode}`, - initialValue: false, - options: [ - { - label: 'Continue →', - value: false - }, - { - label: '↩ Go Back', - value: true, - hint: `Re-enter party ${i}'s public key` - } - ] - }); - if (prompt.isCancel(goBack)) { - throw new UserCancelled(); - } + let joinCodeAction: 'copy' | 'continue' | 'goBack' | symbol; + do { + joinCodeAction = await prompt.select({ + message: joinCodeAction === 'copy' ? 'Copied!' : `Join code for party ${i}:${os.EOL}${joinCode}`, + initialValue: joinCodeAction === 'copy' ? 'continue' : 'copy', + options: [ + { + label: 'Continue →', + value: 'continue' + }, + { + label: 'Copy to clipboard ⎘', + value: 'copy' + }, + { + label: '↩ Go Back', + value: 'goBack', + hint: `Re-enter party ${i}'s public key` + } + ] + }); + if (prompt.isCancel(joinCodeAction)) { + throw new UserCancelled(); + } - if (goBack) { - i--; // Retry this party - } + switch (joinCodeAction) { + case 'goBack': + i--; // Retry this party + break; + case 'copy': + try { + Utils.copyToClipboard(joinCode); + } catch (error) { + prompt.log.error(`Error copying to clipboard: ${error instanceof Error ? error.message : String(error)}`); + joinCodeAction = null; // Reset to re-prompt the user + } + break; + case 'continue': + break; + } + } while (joinCodeAction !== 'continue'); } const spinner = prompt.spinner({ indicator: 'timer' }); diff --git a/packages/bitcore-cli/src/commands/join/joinThresholdSig.ts b/packages/bitcore-cli/src/commands/join/joinThresholdSig.ts index a8d64fb5322..eec47243ed8 100644 --- a/packages/bitcore-cli/src/commands/join/joinThresholdSig.ts +++ b/packages/bitcore-cli/src/commands/join/joinThresholdSig.ts @@ -34,13 +34,28 @@ export async function joinThresholdSigWallet( }); const authPubKey = tss.getAuthPublicKey(); - const done = await prompt.select({ - message: `Give the following public key to the session leader:${os.EOL}${Utils.colorText(authPubKey, 'blue')}`, - options: [{ label: 'Done', value: true, hint: 'Hit Enter/Return to continue' }] - }); - if (prompt.isCancel(done)) { - throw new UserCancelled(); - } + let pubKeyAction: 'copy' | 'done' | symbol; + do { + pubKeyAction = await prompt.select({ + message: pubKeyAction === 'copy' ? 'Copied!' : `Give the following public key to the session leader:${os.EOL}${Utils.colorText(authPubKey, 'blue')}`, + initialValue: pubKeyAction === 'copy' ? 'done' : 'copy', + options: [ + { label: 'Done', value: 'done', hint: 'Hit Enter/Return to continue' }, + { label: 'Copy to clipboard ⎘', value: 'copy' } + ] + }); + if (prompt.isCancel(pubKeyAction)) { + throw new UserCancelled(); + } + if (pubKeyAction === 'copy') { + try { + Utils.copyToClipboard(authPubKey); + } catch (error) { + prompt.log.error(`Error copying to clipboard: ${error instanceof Error ? error.message : String(error)}`); + pubKeyAction = null; // Reset to re-prompt the user + } + } + } while (pubKeyAction !== 'done'); const joinCode = await prompt.text({ message: 'Enter the join code from the session leader:', diff --git a/packages/bitcore-cli/src/commands/transaction.ts b/packages/bitcore-cli/src/commands/transaction.ts index 342ae919af4..2c2f78eee2e 100755 --- a/packages/bitcore-cli/src/commands/transaction.ts +++ b/packages/bitcore-cli/src/commands/transaction.ts @@ -163,7 +163,7 @@ export async function createTransaction( return; // valid value, optional } const val = parseInt(value); - if (isNaN(val) || val < 0) { + if (isNaN(val) || val < 0 || !(/^\d+$/.test(value))) { return 'Please enter a valid destination tag'; } return; // valid value @@ -256,7 +256,7 @@ export async function createTransaction( throw new UserCancelled(); } if (BWCUtils.isUtxoChain(chain)) { - customFeeRate = (Number(customFeeRate) * 1000).toString(); // convert to sats/KB + customFeeRate = Math.round(Number(customFeeRate) * 1000).toString(); // convert to sats/KB } } @@ -267,8 +267,8 @@ export async function createTransaction( }], message: note, feeLevel: feeLevel === 'custom' ? undefined : feeLevel, - feePerKb: feeLevel === 'custom' ? parseFloat(customFeeRate) : undefined, - fee: opts.fee ? parseFloat(opts.fee) : undefined, + feePerKb: feeLevel === 'custom' ? BigInt(Math.ceil(Number(customFeeRate))) : undefined, + fee: opts.fee ? BigInt(Math.ceil(parseFloat(opts.fee))) : undefined, sendMax, tokenAddress: tokenObj?.contractAddress, flags: opts.flags, @@ -294,7 +294,7 @@ export async function createTransaction( : Utils.renderAmount(currency, BigInt(txp.amount) + BigInt(txp.fee)) }`); if (txp.nonce != null) { - lines.push(`Nonce: ${txp.nonce}`); + lines.push(`Nonce: ${BigInt(txp.nonce)}`); } if (note) { lines.push(`Note: ${txp.message}`); diff --git a/packages/bitcore-cli/src/commands/txproposals.ts b/packages/bitcore-cli/src/commands/txproposals.ts index 54add869878..4f933b9fb0e 100755 --- a/packages/bitcore-cli/src/commands/txproposals.ts +++ b/packages/bitcore-cli/src/commands/txproposals.ts @@ -225,12 +225,12 @@ export async function getTxProposals( const extraChoices = [] .concat( - txp.status !== 'broadcasted' && !txp.actions.find(a => a.copayerId === myCopayerId) && txp.status !== 'deleted' ? [ + txp.status !== 'broadcasted' && !txp.actions?.find(a => a.copayerId === myCopayerId) && txp.status !== 'deleted' ? [ { label: 'Accept', value: ViewAction.ACCEPT, hint: 'Accept and sign this proposal' }, { label: 'Reject', value: ViewAction.REJECT, hint: 'Reject this proposal' }, ] : [] ).concat( - txp.status !== 'broadcasted' && txp.actions.filter(a => a.type === 'accept').length >= txp.requiredSignatures && txp.status !== 'deleted' ? [ + txp.status !== 'broadcasted' && txp.actions?.filter(a => a.type === 'accept').length >= txp.requiredSignatures && txp.status !== 'deleted' ? [ { label: 'Broadcast', value: ViewAction.BROADCAST, hint: 'Broadcast this proposal' } ] : [] ).concat( diff --git a/packages/bitcore-cli/src/prompts.ts b/packages/bitcore-cli/src/prompts.ts index 52e1c850a5c..b0853d94b25 100644 --- a/packages/bitcore-cli/src/prompts.ts +++ b/packages/bitcore-cli/src/prompts.ts @@ -326,7 +326,7 @@ export async function promptKeyshareBackup(): Promise { 'This keyshare backup file contains both your 12-word mnemonic AND your keyshare data, encrypted with a password you will set in the following prompts.' + os.EOL + 'Make sure to:' + os.EOL + ` - Store the file in a ${Utils.underlineText('safe place')}, like a USB drive in a safe, and do not share it with anyone.` + os.EOL + - ` - ${Utils.boldText('DO NOT FORGET')} the encryption password! The file is useless without it, and there is no way to reset the password.` + os.EOL + + ` - ${Utils.boldText('DO NOT FORGET', true)} the encryption password! The file is useless without it, and there is no way to reset the password.` + os.EOL + 'Both the file + encryption password are as valuable as a non-TSS wallet\'s 12-24 word phrase, so treat them with the same level of security.' ); const a = await prompt.select({ diff --git a/packages/bitcore-cli/src/tss.ts b/packages/bitcore-cli/src/tss.ts index a2484ae4d5d..84eaa22e754 100644 --- a/packages/bitcore-cli/src/tss.ts +++ b/packages/bitcore-cli/src/tss.ts @@ -67,7 +67,7 @@ export async function sign(args: { tssSign.subscribe(); tssSign.on('roundsubmitted', (round) => spinner.message(`Round ${round} submitted`)); - tssSign.on('error', prompt.log.error); + tssSign.on('error', e => prompt.log.error('Unexpected error during TSS signing: ' + e)); tssSign.on('complete', async () => { try { spinner.stop(logMessageCompleted || 'TSS signature generated'); diff --git a/packages/bitcore-cli/src/utils.ts b/packages/bitcore-cli/src/utils.ts index dec6139fd07..071ecfc5224 100644 --- a/packages/bitcore-cli/src/utils.ts +++ b/packages/bitcore-cli/src/utils.ts @@ -1,3 +1,4 @@ +import { type StdioOptions, spawnSync } from 'child_process'; import os from 'os'; import path from 'path'; import * as prompt from '@clack/prompts'; @@ -66,8 +67,12 @@ export class Utils { return Constants.COLOR[color.toLowerCase()].replace('%s', text); } - static boldText(text: string) { - return '\x1b[1m' + text + '\x1b[22m'; + static boldText(text: string, isDim?: boolean) { + return '\x1b[1m' + text + '\x1b[22m' + (isDim ? '\x1b[2m' : ''); // 22 is the ANSI code to turn off bold AND dim. So, need to re-apply dim if applicable + } + + static dimText(text: string, isBold?: boolean) { + return '\x1b[2m' + text + '\x1b[22m' + (isBold ? '\x1b[1m' : ''); // 22 is the ANSI code to turn off bold AND dim. So, need to re-apply bold if applicable } static italicText(text: string) { @@ -436,4 +441,69 @@ export class Utils { static colorizeChain(chain: string) { return Utils.colorTextByChain(chain, chain); } + + static copyToClipboard(text: string): void { + const platform = os.platform(); + let attempts: Array<{ cmd: string; args: string[] }>; + + if (platform === 'darwin') { + attempts = [{ cmd: 'pbcopy', args: [] }]; + } else if (platform === 'linux') { + // Prefer wl-copy first (Wayland), then fall back to X11 tools. + attempts = [ + { cmd: 'wl-copy', args: [] }, + { cmd: 'xclip', args: ['-selection', 'clipboard'] }, + { cmd: 'xsel', args: ['--clipboard', '--input'] } + ]; + } else if (platform === 'win32') { + attempts = [{ cmd: 'clip', args: [] }]; + } else { + throw new Error(`Unsupported platform: ${platform}`); + } + + const missing: string[] = []; + const failures: string[] = []; + + for (const attempt of attempts) { + // wl-copy can fork and keep inherited pipes open; piping stderr/stdout can + // cause spawnSync to block even after successful copy. + const stdio: StdioOptions = attempt.cmd === 'wl-copy' + ? ['pipe', 'ignore', 'ignore'] + : ['pipe', 'ignore', 'pipe']; + + const result = spawnSync(attempt.cmd, attempt.args, { + input: text, + encoding: 'utf8', + stdio + }); + + if (result.error) { + const err = result.error as NodeJS.ErrnoException; + if (err.code === 'ENOENT') { + missing.push(attempt.cmd); + continue; + } + failures.push(`${attempt.cmd}: ${err.message}`); + continue; + } + + if (result.status === 0) { + return; + } + + const stderr = (result.stderr ?? '').trim(); + failures.push(`${attempt.cmd}: ${stderr || `exited with code ${result.status}`}`); + } + + if (missing.length === attempts.length) { + throw new Error(`No clipboard utility found. Tried: ${attempts.map(a => a.cmd).join(', ')}`); + } + + const detailParts = [ + failures.length ? `Failures: ${failures.join(' | ')}` : '', + missing.length ? `Not installed: ${missing.join(', ')}` : '' + ].filter(Boolean); + + throw new Error(`Failed to copy to clipboard. ${detailParts.join(' ; ')}`); + } }; diff --git a/packages/bitcore-cli/test/create.test.ts b/packages/bitcore-cli/test/create.test.ts index 7fa10dd1e5a..5f41d3db1e0 100644 --- a/packages/bitcore-cli/test/create.test.ts +++ b/packages/bitcore-cli/test/create.test.ts @@ -697,6 +697,7 @@ describe('Create', function() { // Checkpoint1: Wait for copayer2's pubkey [copayer2PubKey, KEYSTROKES.ENTER], // Done sharing -- (checkpoint1) // Checkpoint2: Extract join code to share with copayer2 + [KEYSTROKES.ARROW_UP], // Copy to clipboard -> Done -- (checkpoint2) [KEYSTROKES.ENTER], // Done sharing -- (checkpoint2) [KEYSTROKES.ENTER], // Yes, continue with keyshare export [...Array(50).fill(KEYSTROKES.BACKSPACE), `${TEMP_DIR}/${walletName1}-export.json`, KEYSTROKES.ENTER], // Export keyshare backup file to temp dir @@ -713,7 +714,8 @@ describe('Create', function() { ['copayer2', KEYSTROKES.ENTER], // Copayer name ['testpassword', KEYSTROKES.ENTER], // Password // Checkpoint1: Extract pubkey to give to session leader (copayer1) - [KEYSTROKES.ENTER], // Done sharing -- (checkpoint1) + [KEYSTROKES.ARROW_UP], // Copy to clipboard -> Done -- (checkpoint1) + [KEYSTROKES.ENTER], // Done sharing // Checkpoint2: Wait for and enter join code from copayer1 to join session [joinCode, KEYSTROKES.ENTER], // Enter session code from leader (copayer1) [KEYSTROKES.ENTER], // Confirm decoded join code looks correct @@ -733,7 +735,7 @@ describe('Create', function() { // stepInputs indexes corresponding to checkpoints in test flow where we want to assert on CLI output const checkpoints = { [walletName1]: new Set([10, 11]), - [walletName2]: new Set([8, 9]) + [walletName2]: new Set([8, 10]) }; function pushInputs(walletName, stepInputs) { for (const input of stepInputs) { @@ -765,7 +767,7 @@ describe('Create', function() { case Array.from(checkpoints[walletName])[0]: if (walletName === walletName1) { const startIdx = lines.findIndex(l => l.includes('Enter party 1\'s public key:')); - assert.ok(startIdx > -1); + assert.ok(startIdx > -1, 'Did not find expected prompt to enter party 1\'s public key.'); const cachedStep = step[walletName]; // cache the step num so it's preserved for the promise handler copayer2PubKeySet.then(() => { stepInputs[cachedStep][0] = copayer2PubKey; @@ -774,7 +776,7 @@ describe('Create', function() { } else { const startIdx = lines.findIndex(l => l.includes('Give the following public key to the session leader:')); const endIdx = lines.findIndex(l => l.includes('Done')); - assert.ok(startIdx > -1, 'Did not find expected prompt to share public key with session leader. Output was: ' + checkpointOutput); + assert.ok(startIdx > -1, 'Did not find expected prompt to share public key with session leader.'); copayer2PubKey = helpers.decolor(lines.slice(startIdx + 1, endIdx).map(l => l.replace('│', '').trim()).join('')); assert.match(copayer2PubKey, /^[0-9a-f]{66}$/, 'Invalid copayer2 public key. Got: ' + copayer2PubKey); // 66 byte hex pubkey string emitter.emit('copayer2PubKey'); @@ -786,14 +788,14 @@ describe('Create', function() { if (walletName === walletName1) { const startIdx = lines.findIndex(l => l.includes('Join code for party 1:')); const endIdx = lines.findIndex(l => l.includes('Continue')); - assert.ok(startIdx > -1, 'Did not find expected prompt to share join code with session leader. Output was: ' + checkpointOutput); + assert.ok(startIdx > -1, 'Did not find expected prompt to share join code with session leader.'); joinCode = helpers.decolor(lines.slice(startIdx + 1, endIdx).map(l => l.replace('│', '').trim()).join('')); assert.match(joinCode, /^[0-9a-f]{400,500}$/, 'Invalid join code. Got: ' + joinCode); // hex string between 400-500 chars long (expected to be around 418 chars. Length is just a sanity check. If any data is added to join code it'll need to be adjusted) emitter.emit('joinCode'); pushInputs.call(this, walletName, stepInputs[step[walletName]]); } else { const startIdx = lines.findIndex(l => l.includes('Enter the join code from the session leader:')); - assert.ok(startIdx > -1); + assert.ok(startIdx > -1, 'Did not find expected prompt to enter join code from session leader.'); const cachedStep = step[walletName]; // cache the step num so it's preserved for the promise handler joinCodeSet.then(() => { stepInputs[cachedStep][0] = joinCode; @@ -932,6 +934,7 @@ describe('Create', function() { // Checkpoint1: Wait for copayer2's pubkey [copayer2PubKey, KEYSTROKES.ENTER], // Done sharing -- (checkpoint1) // Checkpoint2: Extract join code to share with copayer2 + [KEYSTROKES.ARROW_UP], // Copy to clipboard -> Done -- (checkpoint2) [KEYSTROKES.ENTER], // Done sharing -- (checkpoint2) [KEYSTROKES.ENTER], // Yes, continue with keyshare export [...Array(50).fill(KEYSTROKES.BACKSPACE), `${TEMP_DIR}/${walletName1}-export.json`, KEYSTROKES.ENTER], // Export keyshare backup file to temp dir @@ -947,7 +950,8 @@ describe('Create', function() { ['copayer2', KEYSTROKES.ENTER], // Copayer name ['testpassword', KEYSTROKES.ENTER], // Password // Checkpoint1: Extract pubkey to give to session leader (copayer1) - [KEYSTROKES.ENTER], // Done sharing -- (checkpoint1) + [KEYSTROKES.ARROW_UP], // Copy to clipboard -> Done -- (checkpoint1) + [KEYSTROKES.ENTER], // Done sharing // Checkpoint2: Wait for and enter join code from copayer1 to join session [joinCode, KEYSTROKES.ENTER], // Enter session code from leader (copayer1) [KEYSTROKES.ENTER], // Confirm decoded join code looks correct @@ -967,7 +971,7 @@ describe('Create', function() { // stepInputs indexes corresponding to checkpoints in test flow where we want to assert on CLI output const checkpoints = { [walletName1]: new Set([7, 8]), - [walletName2]: new Set([6, 7]) + [walletName2]: new Set([6, 8]) }; function pushInputs(walletName, stepInputs) { for (const input of stepInputs) { @@ -1000,7 +1004,7 @@ describe('Create', function() { case Array.from(checkpoints[walletName])[0]: if (walletName === walletName1) { const startIdx = lines.findIndex(l => l.includes('Enter party 1\'s public key:')); - assert.ok(startIdx > -1); + assert.ok(startIdx > -1, 'Did not find expected prompt to enter party 1\'s public key.'); const cachedStep = step[walletName]; // cache the step num so it's preserved for the promise handler copayer2PubKeySet.then(() => { stepInputs[cachedStep][0] = copayer2PubKey; @@ -1009,7 +1013,7 @@ describe('Create', function() { } else { const startIdx = lines.findIndex(l => l.includes('Give the following public key to the session leader:')); const endIdx = lines.findIndex(l => l.includes('Done')); - assert.ok(startIdx > -1, 'Did not find expected prompt to share public key with session leader. Output was: ' + checkpointOutput); + assert.ok(startIdx > -1, 'Did not find expected prompt to share public key with session leader.'); copayer2PubKey = helpers.decolor(lines.slice(startIdx + 1, endIdx).map(l => l.replace('│', '').trim()).join('')); assert.match(copayer2PubKey, /^[0-9a-f]{66}$/, 'Invalid copayer2 public key. Got: ' + copayer2PubKey); // 66 byte hex pubkey string emitter.emit('copayer2PubKey'); @@ -1021,14 +1025,14 @@ describe('Create', function() { if (walletName === walletName1) { const startIdx = lines.findIndex(l => l.includes('Join code for party 1:')); const endIdx = lines.findIndex(l => l.includes('Continue')); - assert.ok(startIdx > -1, 'Did not find expected prompt to share join code with session leader. Output was: ' + checkpointOutput); + assert.ok(startIdx > -1, 'Did not find expected prompt to share join code with session leader.'); joinCode = helpers.decolor(lines.slice(startIdx + 1, endIdx).map(l => l.replace('│', '').trim()).join('')); assert.match(joinCode, /^[0-9a-f]{400,500}$/, 'Invalid join code. Got: ' + joinCode); // hex string between 400-500 chars long (expected to be around 418 chars. Length is just a sanity check. If any data is added to join code it'll need to be adjusted) emitter.emit('joinCode'); pushInputs.call(this, walletName, stepInputs[step[walletName]]); } else { const startIdx = lines.findIndex(l => l.includes('Enter the join code from the session leader:')); - assert.ok(startIdx > -1); + assert.ok(startIdx > -1, 'Did not find expected prompt to enter join code from session leader.'); const cachedStep = step[walletName]; // cache the step num so it's preserved for the promise handler joinCodeSet.then(() => { stepInputs[cachedStep][0] = joinCode; @@ -1167,6 +1171,7 @@ describe('Create', function() { // Checkpoint1: Wait for copayer2's pubkey [copayer2PubKey, KEYSTROKES.ENTER], // Done sharing -- (checkpoint1) // Checkpoint2: Extract join code to share with copayer2 + [KEYSTROKES.ARROW_UP], // Copy to clipboard -> Done -- (checkpoint2) [KEYSTROKES.ENTER], // Done sharing -- (checkpoint2) [KEYSTROKES.ENTER], // Yes, continue with keyshare export [...Array(50).fill(KEYSTROKES.BACKSPACE), `${TEMP_DIR}/${walletName1}-export.json`, KEYSTROKES.ENTER], // Export keyshare backup file to temp dir @@ -1182,7 +1187,8 @@ describe('Create', function() { ['copayer2', KEYSTROKES.ENTER], // Copayer name ['testpassword', KEYSTROKES.ENTER], // Password // Checkpoint1: Extract pubkey to give to session leader (copayer1) - [KEYSTROKES.ENTER], // Done sharing -- (checkpoint1) + [KEYSTROKES.ARROW_UP], // Copy to clipboard -> Done -- (checkpoint1) + [KEYSTROKES.ENTER], // Done sharing // Checkpoint2: Wait for and enter join code from copayer1 to join session [joinCode, KEYSTROKES.ENTER], // Enter session code from leader (copayer1) [KEYSTROKES.ENTER], // Confirm decoded join code looks correct @@ -1202,7 +1208,7 @@ describe('Create', function() { // stepInputs indexes corresponding to checkpoints in test flow where we want to assert on CLI output const checkpoints = { [walletName1]: new Set([7, 8]), - [walletName2]: new Set([6, 7]) + [walletName2]: new Set([6, 8]) }; function pushInputs(walletName, stepInputs) { for (const input of stepInputs) { @@ -1234,7 +1240,7 @@ describe('Create', function() { case Array.from(checkpoints[walletName])[0]: if (walletName === walletName1) { const startIdx = lines.findIndex(l => l.includes('Enter party 1\'s public key:')); - assert.ok(startIdx > -1); + assert.ok(startIdx > -1, 'Did not find expected prompt to enter party 1\'s public key.'); const cachedStep = step[walletName]; // cache the step num so it's preserved for the promise handler copayer2PubKeySet.then(() => { stepInputs[cachedStep][0] = copayer2PubKey; @@ -1243,7 +1249,7 @@ describe('Create', function() { } else { const startIdx = lines.findIndex(l => l.includes('Give the following public key to the session leader:')); const endIdx = lines.findIndex(l => l.includes('Done')); - assert.ok(startIdx > -1, 'Did not find expected prompt to share public key with session leader. Output was: ' + checkpointOutput); + assert.ok(startIdx > -1, 'Did not find expected prompt to share public key with session leader.'); copayer2PubKey = helpers.decolor(lines.slice(startIdx + 1, endIdx).map(l => l.replace('│', '').trim()).join('')); assert.match(copayer2PubKey, /^[0-9a-f]{66}$/, 'Invalid copayer2 public key. Got: ' + copayer2PubKey); // 66 byte hex pubkey string emitter.emit('copayer2PubKey'); @@ -1255,14 +1261,14 @@ describe('Create', function() { if (walletName === walletName1) { const startIdx = lines.findIndex(l => l.includes('Join code for party 1:')); const endIdx = lines.findIndex(l => l.includes('Continue')); - assert.ok(startIdx > -1, 'Did not find expected prompt to share join code with session leader. Output was: ' + checkpointOutput); + assert.ok(startIdx > -1, 'Did not find expected prompt to share join code with session leader.'); joinCode = helpers.decolor(lines.slice(startIdx + 1, endIdx).map(l => l.replace('│', '').trim()).join('')); assert.match(joinCode, /^[0-9a-f]{400,500}$/, 'Invalid join code. Got: ' + joinCode); // hex string between 400-500 chars long (expected to be around 418 chars. Length is just a sanity check. If any data is added to join code it'll need to be adjusted) emitter.emit('joinCode'); pushInputs.call(this, walletName, stepInputs[step[walletName]]); } else { const startIdx = lines.findIndex(l => l.includes('Enter the join code from the session leader:')); - assert.ok(startIdx > -1); + assert.ok(startIdx > -1, 'Did not find expected prompt to enter join code from session leader.'); const cachedStep = step[walletName]; // cache the step num so it's preserved for the promise handler joinCodeSet.then(() => { stepInputs[cachedStep][0] = joinCode;