* feat: add `Share PSBT` button with options * feat: add basic communication via the serial port * chore: code format * feat: send data to and from serial port * fix: port disconnect * feat: handle psbt extract * feat: show signed transaction details * fix: handle Connect/Disconnect failure state * feat:small UI improvements * feat: broadcast transaction (partial solution) * feat: integrate psbt response from HWW * feat: login and send commands to HWW * feat: ui improvements * feat: ui/ux improvements * feat: more small UI impreovemsnts * feat: simplify UI * feat: add `help` command * feat: add wipe command * feet: add `seed` command * feat: add `restore` command * feat: always show PSBT input text (for outside PSBTs) * feat: show spinner while signing tx * feat: hide panels after transaction is broadcast * feat: basic use of custom components * refactor: move components one folder up * refactor: extract wallet-config * refactor: extract `wallet-list` component * refactor: clean-up * chore: code format html component files * refactor: extract address-list component * refactor: extract `history` component * refactor: extract `utxo-list` component * feat: UI/UX improvements * feat: partial payment redesign * refactor: rename `fee` to `fee-rate` * refactor: rename component * refactor: extract `send-to` component * refactor: payment: first migration * fix: init `sendToList` * fix: change address * fix: change address and `Select All` coins * feat: show custom fees & two way binding for addresses * fix: scanAddressesWithAmount * fix: max amount * fix: coin selection mode * chore: code clean-up * feat: shuffle the UI * fix: change amount * feat: update tx size in real time * fix: coin selection * fix: show erro messages * fix: psbt generation * refactor: move serial port logic * refactor: payment component * refactor: code clean-up; use `slot` for `serial-signer` * feat: toggle serial port * feat: add Disconnect command * feat: prompt for `Connect` and `Login` before signing * refactor: send psbt to device * feat: extract signed transaction * refactor: code clean-up * feat: show auth green icon * chore: code clean-up * feat: show console * feat: allow `Connect` from dropdown menu * fix: stop if serial port cannot be open * feat: confirm outputs and fee * feat: add cancel command * fix: add `sats-denominated` for confirmations * feat: wait for HWW to authenticate, then open dialog * feat: share PSBT as text * refactor: extract `refreshAddresses` * feat: small UI improvements * feat: add default `Mainnet` network * feat: fix mempool endpint * feat: propagate config update only when explicitly updated * feat: add network for wallet accounts * fix: stop scanning when network changed * chore: code clean-up * chore: code clean-up * feat: show hardware device Xpub option * fix: handle failed to parse psbt * feat: add accounts using the HWW * fix: testnet is in the bip32 derivation path * feat: add spinner while wallet account is created * fix: check network and masterpub for duplicate accounts * feat: integrate transaction broadcast * feat: add password confirmation for `Wipe` and `Restore` * fix: fingerprint is not unique per account (it is the fingerprint of the master) * chore: code clean-up, remove `masterpub_fingerprint` * fix: account name diplay * chore: code format * fix: memppol links * fix: shortcut buttons * fix: note update * chore: code format * chore: clean-up rebase left overs * chore: clean-up * feat: less technical labels for addresses * feat: add serial port config params * fix: address type selection * chore: drop `mempool` table * fix: change & fee value * fix: handle no input signed scenario * fix: sat/btc unit * fix: small UI stuff * doc: update the readme * Update README.md
184 lines
5.1 KiB
JavaScript
184 lines
5.1 KiB
JavaScript
const PSBT_BASE64_PREFIX = 'cHNidP8'
|
|
const COMMAND_PASSWORD = '/password'
|
|
const COMMAND_PASSWORD_CLEAR = '/password-clear'
|
|
const COMMAND_SEND_PSBT = '/psbt'
|
|
const COMMAND_SIGN_PSBT = '/sign'
|
|
const COMMAND_HELP = '/help'
|
|
const COMMAND_WIPE = '/wipe'
|
|
const COMMAND_SEED = '/seed'
|
|
const COMMAND_RESTORE = '/restore'
|
|
const COMMAND_CONFIRM_NEXT = '/confirm-next'
|
|
const COMMAND_CANCEL = '/cancel'
|
|
const COMMAND_XPUB = '/xpub'
|
|
|
|
const DEFAULT_RECEIVE_GAP_LIMIT = 20
|
|
|
|
const blockTimeToDate = blockTime =>
|
|
blockTime ? moment(blockTime * 1000).format('LLL') : ''
|
|
|
|
const currentDateTime = () => moment().format('LLL')
|
|
|
|
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
|
|
const retryWithDelay = async function (fn, retryCount = 0) {
|
|
try {
|
|
await sleep(25)
|
|
// Do not return the call directly, use result.
|
|
// Otherwise the error will not be cought in this try-catch block.
|
|
const result = await fn()
|
|
return result
|
|
} catch (err) {
|
|
if (retryCount > 100) throw err
|
|
await sleep((retryCount + 1) * 1000)
|
|
return retryWithDelay(fn, retryCount + 1)
|
|
}
|
|
}
|
|
|
|
const txSize = tx => {
|
|
// https://bitcoinops.org/en/tools/calc-size/
|
|
// overhead size
|
|
const nVersion = 4
|
|
const inCount = 1
|
|
const outCount = 1
|
|
const nlockTime = 4
|
|
const hasSegwit = !!tx.inputs.find(inp =>
|
|
['p2wsh', 'p2wpkh', 'p2tr'].includes(inp.accountType)
|
|
)
|
|
const segwitFlag = hasSegwit ? 0.5 : 0
|
|
const overheadSize = nVersion + inCount + outCount + nlockTime + segwitFlag
|
|
|
|
// inputs size
|
|
const outpoint = 36 // txId plus vout index number
|
|
const scriptSigLength = 1
|
|
const nSequence = 4
|
|
const inputsSize = tx.inputs.reduce((t, inp) => {
|
|
const scriptSig =
|
|
inp.accountType === 'p2pkh' ? 107 : inp.accountType === 'p2sh' ? 254 : 0
|
|
const witnessItemCount = hasSegwit ? 0.25 : 0
|
|
const witnessItems =
|
|
inp.accountType === 'p2wpkh'
|
|
? 27
|
|
: inp.accountType === 'p2wsh'
|
|
? 63.5
|
|
: inp.accountType === 'p2tr'
|
|
? 16.5
|
|
: 0
|
|
t +=
|
|
outpoint +
|
|
scriptSigLength +
|
|
nSequence +
|
|
scriptSig +
|
|
witnessItemCount +
|
|
witnessItems
|
|
return t
|
|
}, 0)
|
|
|
|
// outputs size
|
|
const nValue = 8
|
|
const scriptPubKeyLength = 1
|
|
|
|
const outputsSize = tx.outputs.reduce((t, out) => {
|
|
const type = guessAddressType(out.address)
|
|
|
|
const scriptPubKey =
|
|
type === 'p2pkh'
|
|
? 25
|
|
: type === 'p2wpkh'
|
|
? 22
|
|
: type === 'p2sh'
|
|
? 23
|
|
: type === 'p2wsh'
|
|
? 34
|
|
: 34 // default to the largest size (p2tr included)
|
|
t += nValue + scriptPubKeyLength + scriptPubKey
|
|
return t
|
|
}, 0)
|
|
|
|
return overheadSize + inputsSize + outputsSize
|
|
}
|
|
const guessAddressType = (a = '') => {
|
|
if (a.startsWith('1') || a.startsWith('n')) return 'p2pkh'
|
|
if (a.startsWith('3') || a.startsWith('2')) return 'p2sh'
|
|
if (a.startsWith('bc1q') || a.startsWith('tb1q'))
|
|
return a.length === 42 ? 'p2wpkh' : 'p2wsh'
|
|
if (a.startsWith('bc1p') || a.startsWith('tb1p')) return 'p2tr'
|
|
}
|
|
|
|
const ACCOUNT_TYPES = {
|
|
p2tr: 'Taproot, BIP86, P2TR, Bech32m',
|
|
p2wpkh: 'SegWit, BIP84, P2WPKH, Bech32',
|
|
p2sh: 'BIP49, P2SH-P2WPKH, Base58',
|
|
p2pkh: 'Legacy, BIP44, P2PKH, Base58'
|
|
}
|
|
|
|
const getAccountDescription = type => ACCOUNT_TYPES[type] || 'nonstandard'
|
|
|
|
const readFromSerialPort = reader => {
|
|
let partialChunk
|
|
let fulliness = []
|
|
|
|
const readStringUntil = async (separator = '\n') => {
|
|
if (fulliness.length) return fulliness.shift().trim()
|
|
const chunks = []
|
|
if (partialChunk) {
|
|
// leftovers from previous read
|
|
chunks.push(partialChunk)
|
|
partialChunk = undefined
|
|
}
|
|
while (true) {
|
|
const {value, done} = await reader.read()
|
|
if (value) {
|
|
const values = value.split(separator)
|
|
// found one or more separators
|
|
if (values.length > 1) {
|
|
chunks.push(values.shift()) // first element
|
|
partialChunk = values.pop() // last element
|
|
fulliness = values // full lines
|
|
return {value: chunks.join('').trim(), done: false}
|
|
}
|
|
chunks.push(value)
|
|
}
|
|
if (done) return {value: chunks.join('').trim(), done: true}
|
|
}
|
|
}
|
|
return readStringUntil
|
|
}
|
|
|
|
function satOrBtc(val, showUnit = true, showSats = false) {
|
|
const value = showSats
|
|
? LNbits.utils.formatSat(val)
|
|
: val == 0
|
|
? 0.0
|
|
: (val / 100000000).toFixed(8)
|
|
if (!showUnit) return value
|
|
return showSats ? value + ' sat' : value + ' BTC'
|
|
}
|
|
|
|
function loadTemplateAsync(path) {
|
|
const result = new Promise(resolve => {
|
|
const xhttp = new XMLHttpRequest()
|
|
|
|
xhttp.onreadystatechange = function () {
|
|
if (this.readyState == 4) {
|
|
if (this.status == 200) resolve(this.responseText)
|
|
|
|
if (this.status == 404) resolve(`<div>Page not found: ${path}</div>`)
|
|
}
|
|
}
|
|
|
|
xhttp.open('GET', path, true)
|
|
xhttp.send()
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
function findAccountPathIssues(path = '') {
|
|
const p = path.split('/')
|
|
if (p[0] !== 'm') return "Path must start with 'm/'"
|
|
for (let i = 1; i < p.length; i++) {
|
|
if (p[i].endsWith('')) p[i] = p[i].substring(0, p[i].length - 1)
|
|
if (isNaN(p[i])) return `${p[i]} is not a valid value`
|
|
}
|
|
}
|