2022-07-22 16:27:35 +03:00
|
|
|
const watchOnly = async () => {
|
|
|
|
Vue.component(VueQrcode.name, VueQrcode)
|
2022-07-22 18:01:47 +03:00
|
|
|
|
|
|
|
await walletConfig('static/components/wallet-config/wallet-config.html')
|
2022-07-25 10:50:12 +03:00
|
|
|
await walletList('static/components/wallet-list/wallet-list.html')
|
2022-07-25 14:57:54 +03:00
|
|
|
await addressList('static/components/address-list/address-list.html')
|
2022-07-25 15:34:41 +03:00
|
|
|
await history('static/components/history/history.html')
|
2022-07-25 15:58:18 +03:00
|
|
|
await utxoList('static/components/utxo-list/utxo-list.html')
|
2022-07-25 20:03:48 +03:00
|
|
|
await feeRate('static/components/fee-rate/fee-rate.html')
|
2022-07-26 10:11:29 +03:00
|
|
|
await sendTo('static/components/send-to/send-to.html')
|
2022-07-25 17:37:06 +03:00
|
|
|
await payment('static/components/payment/payment.html')
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-26 10:44:36 +03:00
|
|
|
//emplate static/components/payment/payment.html
|
|
|
|
//lnbits/extensions/watchonly/static/components/payment/payment.html
|
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
Vue.filter('reverse', function (value) {
|
|
|
|
// slice to make a copy of array, then reverse the copy
|
|
|
|
return value.slice().reverse()
|
|
|
|
})
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
new Vue({
|
|
|
|
el: '#vue',
|
|
|
|
mixins: [windowMixin],
|
|
|
|
data: function () {
|
|
|
|
return {
|
|
|
|
DUST_LIMIT: 546,
|
2022-07-25 10:50:12 +03:00
|
|
|
filter: '', // todo: remove?
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
scan: {
|
|
|
|
scanning: false,
|
|
|
|
scanCount: 0,
|
|
|
|
scanIndex: 0
|
|
|
|
},
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
currentAddress: null,
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
tab: 'addresses',
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
config: {
|
|
|
|
data: {
|
|
|
|
mempool_endpoint: 'https://mempool.space',
|
|
|
|
receive_gap_limit: 20,
|
|
|
|
change_gap_limit: 5
|
|
|
|
},
|
2022-07-25 10:50:12 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
show: false
|
2022-07-04 17:40:47 +03:00
|
|
|
},
|
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
serial: {
|
|
|
|
selectedPort: null,
|
|
|
|
writableStreamClosed: null,
|
|
|
|
writer: null,
|
|
|
|
readableStreamClosed: null,
|
|
|
|
reader: null,
|
|
|
|
showAdvancedConfig: false,
|
|
|
|
receivedData: '',
|
|
|
|
config: {}
|
|
|
|
},
|
|
|
|
|
|
|
|
hww: {
|
|
|
|
password: null,
|
|
|
|
showPassword: false,
|
|
|
|
mnemonic: null,
|
|
|
|
showMnemonic: false,
|
|
|
|
authenticated: false,
|
|
|
|
showPasswordDialog: false,
|
|
|
|
showWipeDialog: false,
|
|
|
|
showRestoreDialog: false,
|
|
|
|
showConsole: false,
|
|
|
|
showSignedPsbt: false,
|
|
|
|
sendingPsbt: false,
|
|
|
|
signingPsbt: false,
|
|
|
|
psbtSent: false
|
|
|
|
},
|
2022-07-13 17:22:45 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
qrCodeDialog: {
|
|
|
|
show: false,
|
|
|
|
data: null
|
|
|
|
},
|
|
|
|
...tables,
|
2022-07-25 10:50:12 +03:00
|
|
|
...tableData,
|
|
|
|
|
2022-07-25 14:57:54 +03:00
|
|
|
walletAccounts: [],
|
|
|
|
addresses: [],
|
|
|
|
history: [],
|
|
|
|
|
|
|
|
showAddress: false,
|
2022-07-25 17:37:06 +03:00
|
|
|
addressNote: '',
|
2022-07-26 10:44:36 +03:00
|
|
|
showPayment: false
|
2022-07-22 16:27:35 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
//################### CONFIG ###################
|
2022-07-20 16:57:32 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
//################### WALLETS ###################
|
2022-07-25 14:57:54 +03:00
|
|
|
|
2022-07-25 10:50:12 +03:00
|
|
|
getWalletName: function (walletId) {
|
|
|
|
const wallet = this.walletAccounts.find(wl => wl.id === walletId)
|
|
|
|
return wallet ? wallet.title : 'unknown'
|
2022-07-04 17:40:47 +03:00
|
|
|
},
|
2022-07-22 16:27:35 +03:00
|
|
|
//################### ADDRESSES ###################
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
updateAmountForAddress: async function (addressData, amount = 0) {
|
|
|
|
try {
|
|
|
|
const wallet = this.g.user.wallets[0]
|
|
|
|
addressData.amount = amount
|
|
|
|
if (!addressData.isChange) {
|
|
|
|
const addressWallet = this.walletAccounts.find(
|
|
|
|
w => w.id === addressData.wallet
|
2022-07-04 17:40:47 +03:00
|
|
|
)
|
2022-07-05 15:18:00 +03:00
|
|
|
if (
|
2022-07-22 16:27:35 +03:00
|
|
|
addressWallet &&
|
|
|
|
addressWallet.address_no < addressData.addressIndex
|
2022-07-05 15:18:00 +03:00
|
|
|
) {
|
2022-07-22 16:27:35 +03:00
|
|
|
addressWallet.address_no = addressData.addressIndex
|
2022-07-05 15:18:00 +03:00
|
|
|
}
|
2022-07-04 17:40:47 +03:00
|
|
|
}
|
2022-07-22 16:27:35 +03:00
|
|
|
|
2022-07-26 11:31:23 +03:00
|
|
|
// todo: account deleted
|
2022-07-22 16:27:35 +03:00
|
|
|
await LNbits.api.request(
|
|
|
|
'PUT',
|
|
|
|
`/watchonly/api/v1/address/${addressData.id}`,
|
|
|
|
wallet.adminkey,
|
|
|
|
{amount}
|
|
|
|
)
|
|
|
|
} catch (err) {
|
|
|
|
addressData.error = 'Failed to refresh amount for address'
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: `Failed to refresh amount for address ${addressData.address}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
LNbits.utils.notifyApiError(err)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
updateNoteForAddress: async function (addressData, note) {
|
|
|
|
try {
|
|
|
|
const wallet = this.g.user.wallets[0]
|
|
|
|
await LNbits.api.request(
|
|
|
|
'PUT',
|
|
|
|
`/watchonly/api/v1/address/${addressData.id}`,
|
|
|
|
wallet.adminkey,
|
|
|
|
{note: addressData.note}
|
|
|
|
)
|
|
|
|
const updatedAddress =
|
2022-07-25 14:57:54 +03:00
|
|
|
this.addresses.find(a => a.id === addressData.id) || {}
|
2022-07-22 16:27:35 +03:00
|
|
|
updatedAddress.note = note
|
|
|
|
} catch (err) {
|
|
|
|
LNbits.utils.notifyApiError(err)
|
|
|
|
}
|
|
|
|
},
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
//################### ADDRESS HISTORY ###################
|
|
|
|
addressHistoryFromTxs: function (addressData, txs) {
|
|
|
|
const addressHistory = []
|
|
|
|
txs.forEach(tx => {
|
|
|
|
const sent = tx.vin
|
|
|
|
.filter(
|
|
|
|
vin => vin.prevout.scriptpubkey_address === addressData.address
|
|
|
|
)
|
|
|
|
.map(vin => mapInputToSentHistory(tx, addressData, vin))
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
const received = tx.vout
|
|
|
|
.filter(vout => vout.scriptpubkey_address === addressData.address)
|
|
|
|
.map(vout => mapOutputToReceiveHistory(tx, addressData, vout))
|
|
|
|
addressHistory.push(...sent, ...received)
|
2022-07-04 17:40:47 +03:00
|
|
|
})
|
2022-07-22 16:27:35 +03:00
|
|
|
return addressHistory
|
|
|
|
},
|
2022-07-25 15:34:41 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
markSameTxAddressHistory: function () {
|
2022-07-25 14:57:54 +03:00
|
|
|
this.history
|
2022-07-22 16:27:35 +03:00
|
|
|
.filter(s => s.sent)
|
|
|
|
.forEach((el, i, arr) => {
|
|
|
|
if (el.isSubItem) return
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
const sameTxItems = arr.slice(i + 1).filter(e => e.txId === el.txId)
|
|
|
|
if (!sameTxItems.length) return
|
|
|
|
sameTxItems.forEach(e => {
|
|
|
|
e.isSubItem = true
|
|
|
|
})
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
el.totalAmount =
|
|
|
|
el.amount + sameTxItems.reduce((t, e) => (t += e.amount || 0), 0)
|
|
|
|
el.sameTxItems = sameTxItems
|
2022-07-04 17:40:47 +03:00
|
|
|
})
|
2022-07-22 16:27:35 +03:00
|
|
|
},
|
|
|
|
showAddressHistoryDetails: function (addressHistory) {
|
|
|
|
addressHistory.expanded = true
|
|
|
|
},
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
//################### PAYMENT ###################
|
2022-07-26 10:11:29 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
initPaymentData: async function () {
|
|
|
|
if (!this.payment.show) return
|
2022-07-25 14:57:54 +03:00
|
|
|
await this.$refs.addressList.refreshAddresses()
|
2022-07-22 16:27:35 +03:00
|
|
|
},
|
2022-07-25 19:37:16 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
goToPaymentView: async function () {
|
2022-07-25 17:37:06 +03:00
|
|
|
this.showPayment = true
|
2022-07-22 16:27:35 +03:00
|
|
|
await this.initPaymentData()
|
|
|
|
},
|
2022-07-26 10:11:29 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
//################### PSBT ###################
|
|
|
|
|
|
|
|
extractTxFromPsbt: async function (psbtBase64) {
|
2022-07-20 16:57:32 +03:00
|
|
|
const wallet = this.g.user.wallets[0]
|
2022-07-22 16:27:35 +03:00
|
|
|
try {
|
|
|
|
const {data} = await LNbits.api.request(
|
|
|
|
'PUT',
|
|
|
|
'/watchonly/api/v1/psbt/extract',
|
|
|
|
wallet.adminkey,
|
|
|
|
{
|
|
|
|
psbtBase64,
|
|
|
|
inputs: this.payment.tx.inputs
|
|
|
|
}
|
|
|
|
)
|
|
|
|
return data
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Cannot finalize PSBT!',
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
LNbits.utils.notifyApiError(error)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
updateSignedPsbt: async function (value) {
|
|
|
|
this.payment.psbtBase64Signed = value
|
2022-07-22 14:49:14 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
const data = await this.extractTxFromPsbt(this.payment.psbtBase64Signed)
|
|
|
|
if (data) {
|
|
|
|
this.payment.signedTx = JSON.parse(data.tx_json)
|
|
|
|
this.payment.signedTxHex = data.tx_hex
|
|
|
|
} else {
|
|
|
|
this.payment.signedTx = null
|
|
|
|
this.payment.signedTxHex = null
|
|
|
|
}
|
|
|
|
},
|
|
|
|
broadcastTransaction: async function () {
|
|
|
|
try {
|
|
|
|
const wallet = this.g.user.wallets[0]
|
|
|
|
const {data} = await LNbits.api.request(
|
|
|
|
'POST',
|
|
|
|
'/watchonly/api/v1/tx',
|
|
|
|
wallet.adminkey,
|
|
|
|
{tx_hex: this.payment.signedTxHex}
|
|
|
|
)
|
|
|
|
this.payment.sentTxId = data
|
2022-07-22 14:49:14 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
this.$q.notify({
|
|
|
|
type: 'positive',
|
|
|
|
message: 'Transaction broadcasted!',
|
|
|
|
caption: `${data}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
2022-07-22 14:49:14 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
this.hww.psbtSent = false
|
|
|
|
this.payment.psbtBase64Signed = null
|
|
|
|
this.payment.signedTxHex = null
|
|
|
|
this.payment.signedTx = null
|
|
|
|
this.payment.psbtBase64 = null
|
2022-07-12 17:34:40 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
await this.scanAddressWithAmount()
|
|
|
|
} catch (error) {
|
|
|
|
this.payment.sentTxId = null
|
2022-07-21 17:09:57 +03:00
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
2022-07-22 16:27:35 +03:00
|
|
|
message: 'Failed to broadcast!',
|
|
|
|
caption: `${error}`,
|
2022-07-21 17:09:57 +03:00
|
|
|
timeout: 10000
|
|
|
|
})
|
2022-07-22 16:27:35 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
//################### SERIAL PORT ###################
|
|
|
|
checkSerialPortSupported: function () {
|
|
|
|
if (!navigator.serial) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Serial port communication not supported!',
|
|
|
|
caption:
|
|
|
|
'Make sure your browser supports Serial Port and that you are using HTTPS.',
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
openSerialPort: async function () {
|
|
|
|
if (!this.checkSerialPortSupported()) return
|
|
|
|
console.log('### openSerialPort', this.serial.selectedPort)
|
|
|
|
try {
|
|
|
|
navigator.serial.addEventListener('connect', event => {
|
|
|
|
console.log('### navigator.serial event: connected!', event)
|
2022-07-13 17:56:12 +03:00
|
|
|
})
|
2022-07-13 17:22:45 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
navigator.serial.addEventListener('disconnect', () => {
|
|
|
|
this.hww.authenticated = false
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Disconnected from Serial Port!',
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
})
|
|
|
|
this.serial.selectedPort = await navigator.serial.requestPort()
|
|
|
|
// Wait for the serial port to open.
|
|
|
|
await this.serial.selectedPort.open({baudRate: 9600})
|
|
|
|
this.startSerialPortReading()
|
2022-07-13 17:22:45 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
const textEncoder = new TextEncoderStream()
|
|
|
|
this.serial.writableStreamClosed = textEncoder.readable.pipeTo(
|
|
|
|
this.serial.selectedPort.writable
|
|
|
|
)
|
2022-07-13 17:22:45 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
this.serial.writer = textEncoder.writable.getWriter()
|
2022-07-13 17:22:45 +03:00
|
|
|
} catch (error) {
|
2022-07-22 16:27:35 +03:00
|
|
|
this.serial.selectedPort = null
|
2022-07-13 17:22:45 +03:00
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
2022-07-22 16:27:35 +03:00
|
|
|
message: 'Cannot open serial port!',
|
2022-07-13 17:22:45 +03:00
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
2022-07-22 16:27:35 +03:00
|
|
|
},
|
|
|
|
closeSerialPort: async function () {
|
|
|
|
try {
|
|
|
|
console.log('### closeSerialPort', this.serial.selectedPort)
|
|
|
|
if (this.serial.writer) this.serial.writer.close()
|
|
|
|
if (this.serial.writableStreamClosed)
|
|
|
|
await this.serial.writableStreamClosed
|
|
|
|
if (this.serial.reader) this.serial.reader.cancel()
|
|
|
|
if (this.serial.readableStreamClosed)
|
|
|
|
await this.serial.readableStreamClosed.catch(() => {
|
|
|
|
/* Ignore the error */
|
|
|
|
})
|
|
|
|
if (this.serial.selectedPort) await this.serial.selectedPort.close()
|
|
|
|
this.serial.selectedPort = null
|
2022-07-04 17:40:47 +03:00
|
|
|
this.$q.notify({
|
|
|
|
type: 'positive',
|
2022-07-22 16:27:35 +03:00
|
|
|
message: 'Serial port disconnected!',
|
|
|
|
timeout: 5000
|
|
|
|
})
|
|
|
|
} catch (error) {
|
|
|
|
this.serial.selectedPort = null
|
|
|
|
console.log('### error', error)
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Cannot close serial port!',
|
|
|
|
caption: `${error}`,
|
2022-07-04 17:40:47 +03:00
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
2022-07-22 16:27:35 +03:00
|
|
|
},
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
startSerialPortReading: async function () {
|
|
|
|
const port = this.serial.selectedPort
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
while (port && port.readable) {
|
|
|
|
const textDecoder = new TextDecoderStream()
|
|
|
|
this.serial.readableStreamClosed = port.readable.pipeTo(
|
|
|
|
textDecoder.writable
|
2022-07-04 17:40:47 +03:00
|
|
|
)
|
2022-07-22 16:27:35 +03:00
|
|
|
this.serial.reader = textDecoder.readable.getReader()
|
|
|
|
const readStringUntil = readFromSerialPort(this.serial)
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
try {
|
|
|
|
while (true) {
|
|
|
|
const {value, done} = await readStringUntil('\n')
|
|
|
|
console.log('### value', value)
|
|
|
|
if (value) {
|
|
|
|
this.handleSerialPortResponse(value)
|
|
|
|
this.updateSerialPortConsole(value)
|
|
|
|
}
|
|
|
|
console.log('### startSerialPortReading DONE', done)
|
|
|
|
if (done) return
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Serial port communication error!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
2022-07-04 17:40:47 +03:00
|
|
|
}
|
2022-07-22 16:27:35 +03:00
|
|
|
}
|
|
|
|
console.log('### startSerialPortReading port', port)
|
|
|
|
},
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
handleSerialPortResponse: function (value) {
|
|
|
|
const msg = value.split(' ')
|
|
|
|
if (msg[0] == COMMAND_SIGN_PSBT) this.handleSignResponse(msg[1])
|
|
|
|
else if (msg[0] == COMMAND_PASSWORD) this.handleLoginResponse(msg[1])
|
|
|
|
else if (msg[0] == COMMAND_PASSWORD_CLEAR)
|
|
|
|
this.handleLogoutResponse(msg[1])
|
|
|
|
else if (msg[0] == COMMAND_SEND_PSBT)
|
|
|
|
this.handleSendPsbtResponse(msg[1])
|
|
|
|
else if (msg[0] == COMMAND_WIPE) this.handleWipeResponse(msg[1])
|
|
|
|
else console.log('### handleSerialPortResponse', value)
|
|
|
|
},
|
|
|
|
updateSerialPortConsole: function (value) {
|
|
|
|
this.serial.receivedData += value + '\n'
|
|
|
|
const textArea = document.getElementById(
|
|
|
|
'watchonly-serial-port-data-input'
|
|
|
|
)
|
|
|
|
if (textArea) textArea.scrollTop = textArea.scrollHeight
|
|
|
|
},
|
|
|
|
sharePsbtWithAnimatedQRCode: async function () {
|
|
|
|
console.log('### sharePsbtWithAnimatedQRCode')
|
|
|
|
},
|
|
|
|
//################### HARDWARE WALLET ###################
|
|
|
|
hwwShowPasswordDialog: async function () {
|
|
|
|
try {
|
|
|
|
this.hww.showPasswordDialog = true
|
|
|
|
await this.serial.writer.write(COMMAND_PASSWORD + '\n')
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to connect to Hardware Wallet!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
hwwShowWipeDialog: async function () {
|
|
|
|
try {
|
|
|
|
this.hww.showWipeDialog = true
|
|
|
|
await this.serial.writer.write(COMMAND_WIPE + '\n')
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to connect to Hardware Wallet!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
hwwShowRestoreDialog: async function () {
|
|
|
|
try {
|
|
|
|
this.hww.showRestoreDialog = true
|
|
|
|
await this.serial.writer.write(COMMAND_WIPE + '\n')
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to connect to Hardware Wallet!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
hwwLogin: async function () {
|
|
|
|
try {
|
|
|
|
await this.serial.writer.write(
|
|
|
|
COMMAND_PASSWORD + ' ' + this.hww.password + '\n'
|
|
|
|
)
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to send password to Hardware Wallet!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
} finally {
|
|
|
|
this.hww.showPasswordDialog = false
|
|
|
|
this.hww.password = null
|
|
|
|
this.hww.showPassword = false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
handleLoginResponse: function (res = '') {
|
|
|
|
this.hww.authenticated = res.trim() === '1'
|
|
|
|
if (this.hww.authenticated) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'positive',
|
|
|
|
message: 'Login successfull!',
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Wrong password, try again!',
|
|
|
|
timeout: 10000
|
|
|
|
})
|
2022-07-04 17:40:47 +03:00
|
|
|
}
|
2022-07-22 16:27:35 +03:00
|
|
|
},
|
|
|
|
hwwLogout: async function () {
|
|
|
|
try {
|
|
|
|
await this.serial.writer.write(COMMAND_PASSWORD_CLEAR + '\n')
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to logout from Hardware Wallet!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
handleLogoutResponse: function (res = '') {
|
|
|
|
this.hww.authenticated = !(res.trim() === '1')
|
|
|
|
if (this.hww.authenticated) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to logout from Hardware Wallet',
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
hwwExecuteDefaultCommand: function () {
|
|
|
|
if (this.hww.authenticated) {
|
|
|
|
this.hwwSendPsbt()
|
|
|
|
} else {
|
|
|
|
this.hwwShowPasswordDialog()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
hwwSendPsbt: async function () {
|
|
|
|
try {
|
|
|
|
this.hww.sendingPsbt = true
|
|
|
|
await this.serial.writer.write(
|
|
|
|
COMMAND_SEND_PSBT + ' ' + this.payment.psbtBase64 + '\n'
|
|
|
|
)
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'positive',
|
|
|
|
message: 'Data sent to serial port device!',
|
|
|
|
timeout: 5000
|
|
|
|
})
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to send data to serial port!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
handleSendPsbtResponse: function (res = '') {
|
|
|
|
this.hww.psbtSent = true
|
|
|
|
this.hww.sendingPsbt = false
|
|
|
|
},
|
|
|
|
hwwSignPsbt: async function () {
|
|
|
|
try {
|
|
|
|
this.hww.signingPsbt = true
|
|
|
|
await this.serial.writer.write(COMMAND_SIGN_PSBT + '\n')
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'positive',
|
|
|
|
message: 'PSBT signed!',
|
|
|
|
timeout: 5000
|
|
|
|
})
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to sign PSBT!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
handleSignResponse: function (res = '') {
|
|
|
|
this.hww.signingPsbt = false
|
|
|
|
this.updateSignedPsbt(res)
|
|
|
|
if (this.hww.authenticated) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'positive',
|
|
|
|
message: 'Transaction Signed',
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
hwwHelp: async function () {
|
|
|
|
try {
|
|
|
|
await this.serial.writer.write(COMMAND_HELP + '\n')
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'positive',
|
|
|
|
message: 'Check display or console for details!',
|
|
|
|
timeout: 5000
|
|
|
|
})
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to ask for help!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
hwwWipe: async function () {
|
|
|
|
try {
|
|
|
|
this.hww.showWipeDialog = false
|
|
|
|
await this.serial.writer.write(
|
|
|
|
COMMAND_WIPE + ' ' + this.hww.password + '\n'
|
|
|
|
)
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to ask for help!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
} finally {
|
|
|
|
this.hww.password = null
|
|
|
|
this.hww.showPassword = false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
handleWipeResponse: function (res = '') {
|
|
|
|
const wiped = res.trim() === '1'
|
|
|
|
console.log('### wiped', wiped)
|
|
|
|
if (wiped) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'positive',
|
|
|
|
message: 'Wallet wiped!',
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to wipe wallet!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
hwwShowSeed: async function () {
|
|
|
|
try {
|
|
|
|
await this.serial.writer.write(COMMAND_SEED + '\n')
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to show seed!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
hwwRestore: async function () {
|
|
|
|
try {
|
|
|
|
await this.serial.writer.write(
|
|
|
|
COMMAND_RESTORE + ' ' + this.hww.mnemonic + '\n'
|
|
|
|
)
|
|
|
|
await this.serial.writer.write(
|
|
|
|
COMMAND_PASSWORD + ' ' + this.hww.password + '\n'
|
|
|
|
)
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to restore from seed!',
|
|
|
|
caption: `${error}`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
} finally {
|
|
|
|
this.hww.showRestoreDialog = false
|
|
|
|
this.hww.mnemonic = null
|
|
|
|
this.hww.showMnemonic = false
|
|
|
|
this.hww.password = null
|
|
|
|
this.hww.showPassword = false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
//################### UTXOs ###################
|
|
|
|
scanAllAddresses: async function () {
|
2022-07-25 14:57:54 +03:00
|
|
|
await this.$refs.addressList.refreshAddresses()
|
|
|
|
this.history = []
|
|
|
|
let addresses = this.addresses
|
2022-07-22 16:27:35 +03:00
|
|
|
this.utxos.data = []
|
|
|
|
this.utxos.total = 0
|
|
|
|
// Loop while new funds are found on the gap adresses.
|
|
|
|
// Use 1000 limit as a safety check (scan 20 000 addresses max)
|
|
|
|
for (let i = 0; i < 1000 && addresses.length; i++) {
|
|
|
|
await this.updateUtxosForAddresses(addresses)
|
2022-07-25 14:57:54 +03:00
|
|
|
const oldAddresses = this.addresses.slice()
|
|
|
|
await this.$refs.addressList.refreshAddresses()
|
|
|
|
const newAddresses = this.addresses.slice()
|
2022-07-22 16:27:35 +03:00
|
|
|
// check if gap addresses have been extended
|
|
|
|
addresses = newAddresses.filter(
|
|
|
|
newAddr => !oldAddresses.find(oldAddr => oldAddr.id === newAddr.id)
|
|
|
|
)
|
|
|
|
if (addresses.length) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'positive',
|
|
|
|
message: 'Funds found! Scanning for more...',
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
scanAddressWithAmount: async function () {
|
|
|
|
this.utxos.data = []
|
|
|
|
this.utxos.total = 0
|
2022-07-25 14:57:54 +03:00
|
|
|
this.history = []
|
|
|
|
const addresses = this.addresses.filter(a => a.hasActivity)
|
2022-07-22 16:27:35 +03:00
|
|
|
await this.updateUtxosForAddresses(addresses)
|
|
|
|
},
|
|
|
|
scanAddress: async function (addressData) {
|
|
|
|
this.updateUtxosForAddresses([addressData])
|
2022-07-04 17:40:47 +03:00
|
|
|
this.$q.notify({
|
2022-07-22 16:27:35 +03:00
|
|
|
type: 'positive',
|
|
|
|
message: 'Address Rescanned',
|
2022-07-04 17:40:47 +03:00
|
|
|
timeout: 10000
|
|
|
|
})
|
2022-07-22 16:27:35 +03:00
|
|
|
},
|
|
|
|
updateUtxosForAddresses: async function (addresses = []) {
|
|
|
|
this.scan = {scanning: true, scanCount: addresses.length, scanIndex: 0}
|
|
|
|
|
|
|
|
try {
|
|
|
|
for (addrData of addresses) {
|
|
|
|
const addressHistory = await this.getAddressTxsDelayed(addrData)
|
|
|
|
// remove old entries
|
2022-07-25 14:57:54 +03:00
|
|
|
this.history = this.history.filter(
|
2022-07-22 16:27:35 +03:00
|
|
|
h => h.address !== addrData.address
|
|
|
|
)
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-25 15:34:41 +03:00
|
|
|
// add new entries
|
2022-07-25 14:57:54 +03:00
|
|
|
this.history.push(...addressHistory)
|
|
|
|
this.history.sort((a, b) => (!a.height ? -1 : b.height - a.height))
|
2022-07-22 16:27:35 +03:00
|
|
|
this.markSameTxAddressHistory()
|
|
|
|
|
|
|
|
if (addressHistory.length) {
|
|
|
|
// search only if it ever had any activity
|
|
|
|
const utxos = await this.getAddressTxsUtxoDelayed(
|
|
|
|
addrData.address
|
|
|
|
)
|
|
|
|
this.updateUtxosForAddress(addrData, utxos)
|
|
|
|
}
|
|
|
|
|
|
|
|
this.scan.scanIndex++
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error)
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: 'Failed to scan addresses',
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
} finally {
|
|
|
|
this.scan.scanning = false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
updateUtxosForAddress: function (addressData, utxos = []) {
|
|
|
|
const wallet =
|
|
|
|
this.walletAccounts.find(w => w.id === addressData.wallet) || {}
|
|
|
|
|
|
|
|
const newUtxos = utxos.map(utxo =>
|
|
|
|
mapAddressDataToUtxo(wallet, addressData, utxo)
|
|
|
|
)
|
|
|
|
// remove old utxos
|
|
|
|
this.utxos.data = this.utxos.data.filter(
|
|
|
|
u => u.address !== addressData.address
|
|
|
|
)
|
|
|
|
// add new utxos
|
|
|
|
this.utxos.data.push(...newUtxos)
|
|
|
|
if (utxos.length) {
|
|
|
|
this.utxos.data.sort((a, b) => b.sort - a.sort)
|
|
|
|
this.utxos.total = this.utxos.data.reduce(
|
|
|
|
(total, y) => (total += y?.amount || 0),
|
|
|
|
0
|
|
|
|
)
|
|
|
|
}
|
|
|
|
const addressTotal = utxos.reduce(
|
|
|
|
(total, y) => (total += y?.value || 0),
|
2022-07-04 17:40:47 +03:00
|
|
|
0
|
|
|
|
)
|
2022-07-22 16:27:35 +03:00
|
|
|
this.updateAmountForAddress(addressData, addressTotal)
|
|
|
|
},
|
2022-07-25 15:58:18 +03:00
|
|
|
// todo: move/dedup
|
2022-07-22 16:27:35 +03:00
|
|
|
getTotalSelectedUtxoAmount: function () {
|
|
|
|
const total = this.utxos.data
|
|
|
|
.filter(u => u.selected)
|
|
|
|
.reduce((t, a) => t + (a.amount || 0), 0)
|
|
|
|
return total
|
|
|
|
},
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
//################### MEMPOOL API ###################
|
|
|
|
getAddressTxsDelayed: async function (addrData) {
|
|
|
|
const {
|
|
|
|
bitcoin: {addresses: addressesAPI}
|
|
|
|
} = mempoolJS()
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
const fn = async () =>
|
|
|
|
addressesAPI.getAddressTxs({
|
|
|
|
address: addrData.address
|
|
|
|
})
|
|
|
|
const addressTxs = await retryWithDelay(fn)
|
|
|
|
return this.addressHistoryFromTxs(addrData, addressTxs)
|
|
|
|
},
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
getAddressTxsUtxoDelayed: async function (address) {
|
|
|
|
const {
|
|
|
|
bitcoin: {addresses: addressesAPI}
|
|
|
|
} = mempoolJS()
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
const fn = async () =>
|
|
|
|
addressesAPI.getAddressTxsUtxo({
|
|
|
|
address
|
|
|
|
})
|
|
|
|
return retryWithDelay(fn)
|
|
|
|
},
|
|
|
|
fetchTxHex: async function (txId) {
|
|
|
|
const {
|
|
|
|
bitcoin: {transactions: transactionsAPI}
|
|
|
|
} = mempoolJS()
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
try {
|
|
|
|
const response = await transactionsAPI.getTxHex({txid: txId})
|
|
|
|
return response
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: `Failed to fetch transaction details for tx id: '${txId}'`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
LNbits.utils.notifyApiError(error)
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
},
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
//################### OTHER ###################
|
2022-07-25 10:50:12 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
openQrCodeDialog: function (addressData) {
|
|
|
|
this.currentAddress = addressData
|
2022-07-25 14:57:54 +03:00
|
|
|
this.addressNote = addressData.note || ''
|
|
|
|
this.showAddress = true
|
2022-07-22 16:27:35 +03:00
|
|
|
},
|
|
|
|
searchInTab: function (tab, value) {
|
|
|
|
this.tab = tab
|
|
|
|
this[`${tab}Table`].filter = value
|
|
|
|
},
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
satBtc(val, showUnit = true) {
|
2022-07-22 18:01:47 +03:00
|
|
|
return satOrBtc(val, showUnit, this.config.data.sats_denominated)
|
2022-07-22 16:27:35 +03:00
|
|
|
},
|
2022-07-25 10:50:12 +03:00
|
|
|
updateAccounts: async function (accounts) {
|
|
|
|
this.walletAccounts = accounts
|
2022-07-25 14:57:54 +03:00
|
|
|
// await this.refreshAddressesxx() // todo: automatic now?
|
2022-07-25 11:24:02 +03:00
|
|
|
await this.scanAddressWithAmount()
|
2022-07-25 10:50:12 +03:00
|
|
|
},
|
2022-07-25 14:57:54 +03:00
|
|
|
showAddressDetails: function (addressData) {
|
2022-07-25 10:50:12 +03:00
|
|
|
this.openQrCodeDialog(addressData)
|
2022-07-25 14:57:54 +03:00
|
|
|
},
|
2022-07-25 15:34:41 +03:00
|
|
|
handleAddressesUpdated: async function (addresses) {
|
2022-07-25 14:57:54 +03:00
|
|
|
this.addresses = addresses
|
2022-07-25 15:34:41 +03:00
|
|
|
await this.scanAddressWithAmount()
|
2022-07-26 10:44:36 +03:00
|
|
|
}
|
2022-07-04 17:40:47 +03:00
|
|
|
},
|
2022-07-22 16:27:35 +03:00
|
|
|
created: async function () {
|
|
|
|
if (this.g.user.wallets.length) {
|
2022-07-25 15:34:41 +03:00
|
|
|
// await this.$refs.addressList.refreshAddresses()
|
2022-07-22 16:27:35 +03:00
|
|
|
await this.scanAddressWithAmount()
|
|
|
|
}
|
2022-07-04 17:40:47 +03:00
|
|
|
}
|
2022-07-22 16:27:35 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
watchOnly()
|