diff --git a/lnbits/extensions/watchonly/README.md b/lnbits/extensions/watchonly/README.md index be7bf351..7cdd7bf0 100644 --- a/lnbits/extensions/watchonly/README.md +++ b/lnbits/extensions/watchonly/README.md @@ -48,7 +48,7 @@ You can now use this wallet on the LNBits [SatsPayServer](https://github.com/lnb - shows the UTXOs for all wallets - there can be multiple UTXOs for the same address -### Make Payment +### New Payment - create a new `Partially Signed Bitcoin Transaction` - multiple `Send Addresses` can be added - the `Max` button next to an address is for sending the remaining funds to this address (no change) @@ -57,7 +57,7 @@ You can now use this wallet on the LNBits [SatsPayServer](https://github.com/lnb - `Show Advanced` allows to (see `screenshot 2`): - select from which account the change address will be selected (defaults to the first one) - select the `Fee Rate` - - it defaults to the `Medium` value at the moment the `Make Payment` button was clicked + - it defaults to the `Medium` value at the moment the `New Payment` button was clicked - it can be refreshed - warnings are shown if the fee is too Low or to High diff --git a/lnbits/extensions/watchonly/static/components/address-list/address-list.html b/lnbits/extensions/watchonly/static/components/address-list/address-list.html new file mode 100644 index 00000000..9dcedf4d --- /dev/null +++ b/lnbits/extensions/watchonly/static/components/address-list/address-list.html @@ -0,0 +1,204 @@ +
+
+
+ +
+
+ +
+
+ + + +
+
+ + + +
diff --git a/lnbits/extensions/watchonly/static/components/address-list/address-list.js b/lnbits/extensions/watchonly/static/components/address-list/address-list.js new file mode 100644 index 00000000..6614c6e2 --- /dev/null +++ b/lnbits/extensions/watchonly/static/components/address-list/address-list.js @@ -0,0 +1,169 @@ +async function addressList(path) { + const template = await loadTemplateAsync(path) + Vue.component('address-list', { + name: 'address-list', + template, + + props: ['accounts', 'mempool_endpoint', 'inkey'], + data: function () { + return { + addresses: [], + show: false, + data: [], + history: [], + selectedWallet: null, + note: '', + filterOptions: [ + 'Show Change Addresses', + 'Show Gap Addresses', + 'Only With Amount' + ], + filterValues: [], + + addressesTable: { + columns: [ + { + name: 'expand', + align: 'left', + label: '' + }, + { + name: 'address', + align: 'left', + label: 'Address', + field: 'address', + sortable: true + }, + { + name: 'amount', + align: 'left', + label: 'Amount', + field: 'amount', + sortable: true + }, + { + name: 'note', + align: 'left', + label: 'Note', + field: 'note', + sortable: true + }, + { + name: 'wallet', + align: 'left', + label: 'Account', + field: 'wallet', + sortable: true + } + ], + pagination: { + rowsPerPage: 0, + sortBy: 'amount', + descending: true + }, + filter: '' + } + } + }, + watch: { + immediate: true, + accounts(newVal, oldVal) { + if ((newVal || []).length !== (oldVal || []).length) { + console.log('### refreshAddresses') + this.refreshAddresses() // todo await + } + } + }, + + methods: { + satBtc(val, showUnit = true) { + return satOrBtc(val, showUnit, this['sats_denominated']) + }, + getWalletName: function (walletId) { + const wallet = (this.accounts || []).find(wl => wl.id === walletId) + return wallet ? wallet.title : 'unknown' + }, + getFilteredAddresses: function () { + const selectedWalletId = this.selectedWallet?.id + const filter = this.filterValues || [] + const includeChangeAddrs = filter.includes('Show Change Addresses') + const includeGapAddrs = filter.includes('Show Gap Addresses') + const excludeNoAmount = filter.includes('Only With Amount') + + const walletsLimit = (this.accounts || []).reduce((r, w) => { + r[`_${w.id}`] = w.address_no + return r + }, {}) + + console.log('### walletsLimit', walletsLimit) + console.log('### this.addresses', this.addresses) + const fAddresses = this.addresses.filter( + a => + (includeChangeAddrs || !a.isChange) && + (includeGapAddrs || + a.isChange || + a.addressIndex <= walletsLimit[`_${a.wallet}`]) && + !(excludeNoAmount && a.amount === 0) && + (!selectedWalletId || a.wallet === selectedWalletId) + ) + console.log('### fAddresses', fAddresses) + return fAddresses + }, + getAddressesForWallet: async function (walletId) { + try { + const {data} = await LNbits.api.request( + 'GET', + '/watchonly/api/v1/addresses/' + walletId, + this.inkey + ) + return data.map(mapAddressesData) + } catch (err) { + this.$q.notify({ + type: 'warning', + message: `Failed to fetch addresses for wallet with id ${walletId}.`, + timeout: 10000 + }) + LNbits.utils.notifyApiError(err) + } + return [] + }, + refreshAddresses: async function () { + console.log('### refreshAddresses, this.accounts', this.accounts) + if (!this.accounts) return + this.addresses = [] + for (const {id, type} of this.accounts) { + 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) + } + console.log('### refreshAddresses, this.addresse', this.addresses) + this.$emit('update:addresses', this.addresses) + }, + scanAddress: async function (addressData) { + this.$emit('scan:address', addressData) + }, + showAddressDetails: function (addressData) { + this.$emit('show-address-details', addressData) + } + }, + + created: async function () { + await this.refreshAddresses() + } + }) +} diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index be0dff4c..66ecd3c1 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -3,6 +3,7 @@ const watchOnly = async () => { await walletConfig('static/components/wallet-config/wallet-config.html') await walletList('static/components/wallet-list/wallet-list.html') + await addressList('static/components/address-list/address-list.html') Vue.filter('reverse', function (value) { // slice to make a copy of array, then reverse the copy @@ -71,7 +72,12 @@ const watchOnly = async () => { ...tables, ...tableData, - walletAccounts: [] + walletAccounts: [], + addresses: [], + history: [], + + showAddress: false, + addressNote: '' } }, @@ -79,56 +85,13 @@ const watchOnly = async () => { //################### CONFIG ################### //################### WALLETS ################### - 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 (err) { - this.$q.notify({ - type: 'warning', - message: `Failed to fetch addresses for wallet with id ${walletId}.`, - timeout: 10000 - }) - LNbits.utils.notifyApiError(err) - } - return [] - }, + getWalletName: function (walletId) { const wallet = this.walletAccounts.find(wl => wl.id === walletId) return wallet ? wallet.title : 'unknown' }, //################### ADDRESSES ################### - refreshAddresses: async function () { - // const wallets = await this.getWatchOnlyWallets() todo: revisit - // const wallets = - this.addresses.data = [] - for (const {id, type} of this.walletAccounts) { - const newAddresses = await this.getAddressesForWallet(id) - const uniqueAddresses = newAddresses.filter( - newAddr => - !this.addresses.data.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.data.push(...uniqueAddresses) - } - }, updateAmountForAddress: async function (addressData, amount = 0) { try { const wallet = this.g.user.wallets[0] @@ -171,35 +134,12 @@ const watchOnly = async () => { {note: addressData.note} ) const updatedAddress = - this.addresses.data.find(a => a.id === addressData.id) || {} + this.addresses.find(a => a.id === addressData.id) || {} updatedAddress.note = note } catch (err) { LNbits.utils.notifyApiError(err) } }, - getFilteredAddresses: function () { - const selectedWalletId = this.addresses.selectedWallet?.id - const filter = this.addresses.filterValues || [] - const includeChangeAddrs = filter.includes('Show Change Addresses') - const includeGapAddrs = filter.includes('Show Gap Addresses') - const excludeNoAmount = filter.includes('Only With Amount') - - const walletsLimit = this.walletAccounts.reduce((r, w) => { - r[`_${w.id}`] = w.address_no - return r - }, {}) - - const addresses = this.addresses.data.filter( - a => - (includeChangeAddrs || !a.isChange) && - (includeGapAddrs || - a.isChange || - a.addressIndex <= walletsLimit[`_${a.wallet}`]) && - !(excludeNoAmount && a.amount === 0) && - (!selectedWalletId || a.wallet === selectedWalletId) - ) - return addresses - }, //################### ADDRESS HISTORY ################### addressHistoryFromTxs: function (addressData, txs) { @@ -219,9 +159,7 @@ const watchOnly = async () => { return addressHistory }, getFilteredAddressesHistory: function () { - return this.addresses.history.filter( - a => (!a.isChange || a.sent) && !a.isSubItem - ) + return this.history.filter(a => (!a.isChange || a.sent) && !a.isSubItem) }, exportHistoryToCSV: function () { const history = this.getFilteredAddressesHistory().map(a => ({ @@ -235,7 +173,7 @@ const watchOnly = async () => { ) }, markSameTxAddressHistory: function () { - this.addresses.history + this.history .filter(s => s.sent) .forEach((el, i, arr) => { if (el.isSubItem) return @@ -321,7 +259,7 @@ const watchOnly = async () => { }, initPaymentData: async function () { if (!this.payment.show) return - await this.refreshAddresses() + await this.$refs.addressList.refreshAddresses() this.payment.showAdvanced = false this.payment.changeWallet = this.walletAccounts[0] @@ -347,7 +285,7 @@ const watchOnly = async () => { }, selectChangeAddress: function (wallet = {}) { this.payment.changeAddress = - this.addresses.data.find( + this.addresses.find( a => a.wallet === wallet.id && a.isChange && !a.hasActivity ) || {} }, @@ -840,18 +778,18 @@ const watchOnly = async () => { }, //################### UTXOs ################### scanAllAddresses: async function () { - await this.refreshAddresses() - this.addresses.history = [] - let addresses = this.addresses.data + await this.$refs.addressList.refreshAddresses() + this.history = [] + let addresses = this.addresses 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) - const oldAddresses = this.addresses.data.slice() - await this.refreshAddresses() - const newAddresses = this.addresses.data.slice() + const oldAddresses = this.addresses.slice() + await this.$refs.addressList.refreshAddresses() + const newAddresses = this.addresses.slice() // check if gap addresses have been extended addresses = newAddresses.filter( newAddr => !oldAddresses.find(oldAddr => oldAddr.id === newAddr.id) @@ -868,11 +806,12 @@ const watchOnly = async () => { scanAddressWithAmount: async function () { this.utxos.data = [] this.utxos.total = 0 - this.addresses.history = [] - const addresses = this.addresses.data.filter(a => a.hasActivity) + this.history = [] + const addresses = this.addresses.filter(a => a.hasActivity) await this.updateUtxosForAddresses(addresses) }, scanAddress: async function (addressData) { + console.log('### scanAddress', addressData) this.updateUtxosForAddresses([addressData]) this.$q.notify({ type: 'positive', @@ -887,15 +826,13 @@ const watchOnly = async () => { for (addrData of addresses) { const addressHistory = await this.getAddressTxsDelayed(addrData) // remove old entries - this.addresses.history = this.addresses.history.filter( + this.history = this.history.filter( h => h.address !== addrData.address ) // add new entrie - this.addresses.history.push(...addressHistory) - this.addresses.history.sort((a, b) => - !a.height ? -1 : b.height - a.height - ) + this.history.push(...addressHistory) + this.history.sort((a, b) => (!a.height ? -1 : b.height - a.height)) this.markSameTxAddressHistory() if (addressHistory.length) { @@ -1038,9 +975,10 @@ const watchOnly = async () => { //################### OTHER ################### openQrCodeDialog: function (addressData) { + console.log('### addressData', addressData) this.currentAddress = addressData - this.addresses.note = addressData.note || '' - this.addresses.show = true + this.addressNote = addressData.note || '' + this.showAddress = true }, searchInTab: function (tab, value) { this.tab = tab @@ -1052,7 +990,7 @@ const watchOnly = async () => { }, updateAccounts: async function (accounts) { this.walletAccounts = accounts - await this.refreshAddresses() + // await this.refreshAddressesxx() // todo: automatic now? await this.scanAddressWithAmount() if (this.payment.changeWallet) { @@ -1066,13 +1004,17 @@ const watchOnly = async () => { } } }, - handleNewReceiveAddress: function (addressData) { + showAddressDetails: function (addressData) { + console.log('### showAddressDetails addressData', addressData) this.openQrCodeDialog(addressData) + }, + handleAddressesUpdated: function (addresses) { + this.addresses = addresses } }, created: async function () { if (this.g.user.wallets.length) { - await this.refreshAddresses() + // await this.refreshAddressesxxx() todo: done when + {% raw %} - -
@@ -46,7 +45,7 @@ class="btn-full" @click="goToPaymentView" :disabled="scan.scanning == true" - >Make PaymentNew Payment
@@ -70,210 +69,16 @@ -
-
- -
-
- -
-
- - - -
-
- - - +
@@ -427,7 +232,7 @@
- - - - {% raw %} - + {% raw %} + +
Address Details

@@ -1296,7 +1099,7 @@ @@ -1313,7 +1116,7 @@ outline v-close-popup color="grey" - @click="updateNoteForAddress(currentAddress, addresses.note)" + @click="updateNoteForAddress(currentAddress, addressNote)" class="q-ml-sm" >Save Note @@ -1473,5 +1276,6 @@ + {% endblock %}