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-26 20:06:07 +03:00
|
|
|
await serialSigner('static/components/serial-signer/serial-signer.html')
|
2022-07-26 10:44:36 +03:00
|
|
|
|
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 {
|
|
|
|
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-08-01 10:45:31 +03:00
|
|
|
config: {sats_denominated: true},
|
2022-07-04 17:40:47 +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 13:26:18 +03:00
|
|
|
showPayment: false,
|
2022-08-01 11:51:10 +03:00
|
|
|
fetchedUtxos: false,
|
|
|
|
network: null
|
2022-07-22 16:27:35 +03:00
|
|
|
}
|
|
|
|
},
|
2022-08-01 10:45:31 +03:00
|
|
|
computed: {
|
2022-08-01 11:51:10 +03:00
|
|
|
mempoolHostname: function () {
|
2022-08-01 10:45:31 +03:00
|
|
|
if (!this.config.isLoaded) return
|
2022-08-01 11:51:10 +03:00
|
|
|
let hostname = new URL(this.config.mempool_endpoint).hostname
|
|
|
|
if (this.config.network === 'Testnet') {
|
2022-08-01 10:45:31 +03:00
|
|
|
hostname += '/testnet'
|
|
|
|
}
|
|
|
|
return hostname
|
|
|
|
}
|
|
|
|
},
|
2022-07-22 16:27:35 +03:00
|
|
|
|
|
|
|
methods: {
|
|
|
|
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
|
|
|
},
|
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-28 18:05:26 +03:00
|
|
|
await this.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 ###################
|
|
|
|
|
2022-07-27 11:32:03 +03:00
|
|
|
updateSignedPsbt: async function (psbtBase64) {
|
|
|
|
this.$refs.paymentRef.updateSignedPsbt(psbtBase64)
|
2022-07-22 16:27:35 +03:00
|
|
|
},
|
2022-07-22 14:49:14 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
//################### SERIAL PORT ###################
|
2022-07-13 17:22:45 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
//################### HARDWARE WALLET ###################
|
2022-07-26 20:06:07 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
//################### UTXOs ###################
|
|
|
|
scanAllAddresses: async function () {
|
2022-07-28 18:05:26 +03:00
|
|
|
await this.refreshAddresses()
|
2022-07-25 14:57:54 +03:00
|
|
|
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()
|
2022-07-28 18:05:26 +03:00
|
|
|
await this.refreshAddresses()
|
2022-07-25 14:57:54 +03:00
|
|
|
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
|
|
|
},
|
2022-07-28 18:05:26 +03:00
|
|
|
refreshAddresses: async function () {
|
|
|
|
if (!this.walletAccounts) return
|
|
|
|
this.addresses = []
|
|
|
|
for (const {id, type} of this.walletAccounts) {
|
|
|
|
const newAddresses = await this.getAddressesForWallet(id)
|
|
|
|
const uniqueAddresses = newAddresses.filter(
|
|
|
|
newAddr => !this.addresses.find(a => a.address === newAddr.address)
|
|
|
|
)
|
|
|
|
|
|
|
|
const lastAcctiveAddress =
|
|
|
|
uniqueAddresses.filter(a => !a.isChange && a.hasActivity).pop() ||
|
|
|
|
{}
|
|
|
|
|
|
|
|
uniqueAddresses.forEach(a => {
|
|
|
|
a.expanded = false
|
|
|
|
a.accountType = type
|
|
|
|
a.gapLimitExceeded =
|
|
|
|
!a.isChange &&
|
|
|
|
a.addressIndex >
|
|
|
|
lastAcctiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT
|
|
|
|
})
|
|
|
|
this.addresses.push(...uniqueAddresses)
|
|
|
|
}
|
|
|
|
this.$emit('update:addresses', this.addresses)
|
|
|
|
},
|
|
|
|
getAddressesForWallet: async function (walletId) {
|
|
|
|
try {
|
|
|
|
const {data} = await LNbits.api.request(
|
|
|
|
'GET',
|
|
|
|
'/watchonly/api/v1/addresses/' + walletId,
|
|
|
|
this.g.user.wallets[0].inkey
|
|
|
|
)
|
|
|
|
return data.map(mapAddressesData)
|
|
|
|
} catch (error) {
|
|
|
|
this.$q.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: `Failed to fetch addresses for wallet with id ${walletId}.`,
|
|
|
|
timeout: 10000
|
|
|
|
})
|
|
|
|
LNbits.utils.notifyApiError(error)
|
|
|
|
}
|
|
|
|
return []
|
|
|
|
},
|
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-04 17:40:47 +03:00
|
|
|
|
2022-07-22 16:27:35 +03:00
|
|
|
//################### MEMPOOL API ###################
|
|
|
|
getAddressTxsDelayed: async function (addrData) {
|
2022-08-01 13:21:14 +03:00
|
|
|
const accounts = this.walletAccounts
|
|
|
|
const {
|
|
|
|
bitcoin: {addresses: addressesAPI}
|
|
|
|
} = mempoolJS({
|
|
|
|
hostname: this.mempoolHostname
|
|
|
|
})
|
2022-08-01 10:45:31 +03:00
|
|
|
const fn = async () => {
|
2022-08-01 14:52:11 +03:00
|
|
|
if (!accounts.find(w => w.id === addrData.wallet)) return []
|
2022-08-01 10:45:31 +03:00
|
|
|
return addressesAPI.getAddressTxs({
|
2022-07-22 16:27:35 +03:00
|
|
|
address: addrData.address
|
|
|
|
})
|
2022-08-01 10:45:31 +03:00
|
|
|
}
|
2022-07-22 16:27:35 +03:00
|
|
|
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) {
|
2022-08-01 13:21:14 +03:00
|
|
|
const endpoint = this.mempoolHostname
|
|
|
|
const {
|
|
|
|
bitcoin: {addresses: addressesAPI}
|
|
|
|
} = mempoolJS({
|
|
|
|
hostname: endpoint
|
|
|
|
})
|
2022-07-04 17:40:47 +03:00
|
|
|
|
2022-08-01 13:21:14 +03:00
|
|
|
const fn = async () => {
|
|
|
|
if (endpoint !== this.mempoolHostname) return []
|
2022-08-01 10:45:31 +03:00
|
|
|
return addressesAPI.getAddressTxsUtxo({
|
2022-07-22 16:27:35 +03:00
|
|
|
address
|
|
|
|
})
|
2022-08-01 10:45:31 +03:00
|
|
|
}
|
2022-07-22 16:27:35 +03:00
|
|
|
return retryWithDelay(fn)
|
|
|
|
},
|
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-25 10:50:12 +03:00
|
|
|
updateAccounts: async function (accounts) {
|
|
|
|
this.walletAccounts = accounts
|
2022-07-28 18:05:26 +03:00
|
|
|
await this.refreshAddresses()
|
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-26 13:26:18 +03:00
|
|
|
},
|
|
|
|
initUtxos: function (addresses) {
|
|
|
|
if (!this.fetchedUtxos && addresses.length) {
|
|
|
|
this.fetchedUtxos = true
|
|
|
|
this.addresses = addresses
|
|
|
|
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-28 18:05:26 +03:00
|
|
|
await this.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()
|