mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-25 07:07:48 +01:00
feat: partial payment redesign
This commit is contained in:
parent
799fb99661
commit
e98e3504ad
6 changed files with 164 additions and 104 deletions
63
lnbits/extensions/watchonly/static/components/fees/fees.html
Normal file
63
lnbits/extensions/watchonly/static/components/fees/fees.html
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<div>
|
||||||
|
<div class="row items-center no-wrap q-mb-md">
|
||||||
|
<div class="col-2 q-pr-lg">Fee Rate:</div>
|
||||||
|
<div class="col-3 q-pr-lg">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.number="feeRate"
|
||||||
|
:rules="[val => !!val || 'Field is required']"
|
||||||
|
type="number"
|
||||||
|
label="sats/vbyte"
|
||||||
|
@input="feeRateChanged"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-7">
|
||||||
|
<q-slider
|
||||||
|
v-model="feeRate"
|
||||||
|
color="orange"
|
||||||
|
markers
|
||||||
|
snap
|
||||||
|
label
|
||||||
|
label-always
|
||||||
|
:label-value="getFeeRateLabel(feeRate)"
|
||||||
|
:min="1"
|
||||||
|
:max="recommededFees.fastestFee"
|
||||||
|
@input="feeRateChanged"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="feeRate < recommededFees.hourFee || feeRate > recommededFees.fastestFee"
|
||||||
|
class="row items-center no-wrap q-mb-md"
|
||||||
|
>
|
||||||
|
<div class="col-2 q-pr-lg"></div>
|
||||||
|
<div class="col-10 q-pr-lg">
|
||||||
|
<q-badge v-if="feeRate < recommededFees.hourFee" color="pink" size="lg">
|
||||||
|
Warning! The fee is too low. The transaction might take a long time to
|
||||||
|
confirm.
|
||||||
|
</q-badge>
|
||||||
|
<q-badge v-if="feeRate > recommededFees.fastestFee" color="pink">
|
||||||
|
Warning! The fee is too high. You might be overpaying for this
|
||||||
|
transaction.
|
||||||
|
</q-badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row items-center no-wrap q-mb-md">
|
||||||
|
<div class="col-2 q-pr-lg">Fee:</div>
|
||||||
|
<div class="col-3 q-pr-lg">{{totalfee}} sats</div>
|
||||||
|
<div class="col-7">
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
dense
|
||||||
|
size="md"
|
||||||
|
icon="refresh"
|
||||||
|
color="grey"
|
||||||
|
class="float-right"
|
||||||
|
@click="refreshRecommendedFees()"
|
||||||
|
>Refresh Fee Rates</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
62
lnbits/extensions/watchonly/static/components/fees/fees.js
Normal file
62
lnbits/extensions/watchonly/static/components/fees/fees.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
async function fees(path) {
|
||||||
|
const template = await loadTemplateAsync(path)
|
||||||
|
Vue.component('fees', {
|
||||||
|
name: 'fees',
|
||||||
|
template,
|
||||||
|
|
||||||
|
props: ['totalfee', 'sats_denominated'],
|
||||||
|
watch: {
|
||||||
|
immediate: true,
|
||||||
|
'totalfee': function(newVal, oldVal) {
|
||||||
|
console.log('### ', newVal, oldVal)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
feeRate: 1,
|
||||||
|
recommededFees: {
|
||||||
|
fastestFee: 1,
|
||||||
|
halfHourFee: 1,
|
||||||
|
hourFee: 1,
|
||||||
|
economyFee: 1,
|
||||||
|
minimumFee: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
satBtc(val, showUnit = true) {
|
||||||
|
return satOrBtc(val, showUnit, this['sats_denominated'])
|
||||||
|
},
|
||||||
|
feeRateChanged: function (newFeeRate) {
|
||||||
|
console.log('### value', newFeeRate)
|
||||||
|
this.$emit('update:fee-rate', +newFeeRate)
|
||||||
|
},
|
||||||
|
refreshRecommendedFees: async function () {
|
||||||
|
const {
|
||||||
|
bitcoin: {fees: feesAPI}
|
||||||
|
} = mempoolJS()
|
||||||
|
|
||||||
|
const fn = async () => feesAPI.getFeesRecommended()
|
||||||
|
this.recommededFees = await retryWithDelay(fn)
|
||||||
|
},
|
||||||
|
getFeeRateLabel: function (feeRate) {
|
||||||
|
const fees = this.recommededFees
|
||||||
|
if (feeRate >= fees.fastestFee)
|
||||||
|
return `High Priority (${feeRate} sat/vB)`
|
||||||
|
if (feeRate >= fees.halfHourFee)
|
||||||
|
return `Medium Priority (${feeRate} sat/vB)`
|
||||||
|
if (feeRate >= fees.hourFee) return `Low Priority (${feeRate} sat/vB)`
|
||||||
|
return `No Priority (${feeRate} sat/vB)`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created: async function () {
|
||||||
|
console.log('### created fees ')
|
||||||
|
await this.refreshRecommendedFees()
|
||||||
|
this.feeRate = this.recommededFees.halfHourFee
|
||||||
|
this.feeRateChanged(this.recommededFees.halfHourFee)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -81,7 +81,9 @@ async function utxoList(path) {
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
columns: function () {
|
columns: function () {
|
||||||
return this.utxosTable.columns.filter(c => c.selectable ? this.selectable : true)
|
return this.utxosTable.columns.filter(c =>
|
||||||
|
c.selectable ? this.selectable : true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ const watchOnly = async () => {
|
||||||
await addressList('static/components/address-list/address-list.html')
|
await addressList('static/components/address-list/address-list.html')
|
||||||
await history('static/components/history/history.html')
|
await history('static/components/history/history.html')
|
||||||
await utxoList('static/components/utxo-list/utxo-list.html')
|
await utxoList('static/components/utxo-list/utxo-list.html')
|
||||||
|
await fees('static/components/fees/fees.html')
|
||||||
await payment('static/components/payment/payment.html')
|
await payment('static/components/payment/payment.html')
|
||||||
|
|
||||||
Vue.filter('reverse', function (value) {
|
Vue.filter('reverse', function (value) {
|
||||||
|
@ -82,7 +83,9 @@ const watchOnly = async () => {
|
||||||
|
|
||||||
showAddress: false,
|
showAddress: false,
|
||||||
addressNote: '',
|
addressNote: '',
|
||||||
showPayment: false
|
showPayment: false,
|
||||||
|
showCustomFee: false,
|
||||||
|
feeValue: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -238,10 +241,10 @@ const watchOnly = async () => {
|
||||||
masterpub_fingerprint: walletAcount.fingerprint
|
masterpub_fingerprint: walletAcount.fingerprint
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computeFee: function () {
|
computeFee: function (feeRate) {
|
||||||
const tx = this.createTx()
|
const tx = this.createTx()
|
||||||
this.payment.txSize = Math.round(txSize(tx))
|
this.payment.txSize = Math.round(txSize(tx))
|
||||||
return this.payment.feeRate * this.payment.txSize
|
return feeRate * this.payment.txSize
|
||||||
},
|
},
|
||||||
deletePaymentAddress: function (v) {
|
deletePaymentAddress: function (v) {
|
||||||
const index = this.payment.data.indexOf(v)
|
const index = this.payment.data.indexOf(v)
|
||||||
|
@ -257,18 +260,9 @@ const watchOnly = async () => {
|
||||||
this.payment.changeWallet = this.walletAccounts[0]
|
this.payment.changeWallet = this.walletAccounts[0]
|
||||||
this.selectChangeAddress(this.payment.changeWallet)
|
this.selectChangeAddress(this.payment.changeWallet)
|
||||||
|
|
||||||
await this.refreshRecommendedFees()
|
|
||||||
this.payment.feeRate = this.payment.recommededFees.halfHourFee
|
this.payment.feeRate = this.payment.recommededFees.halfHourFee
|
||||||
},
|
},
|
||||||
getFeeRateLabel: function (feeRate) {
|
|
||||||
const fees = this.payment.recommededFees
|
|
||||||
if (feeRate >= fees.fastestFee)
|
|
||||||
return `High Priority (${feeRate} sat/vB)`
|
|
||||||
if (feeRate >= fees.halfHourFee)
|
|
||||||
return `Medium Priority (${feeRate} sat/vB)`
|
|
||||||
if (feeRate >= fees.hourFee) return `Low Priority (${feeRate} sat/vB)`
|
|
||||||
return `No Priority (${feeRate} sat/vB)`
|
|
||||||
},
|
|
||||||
addPaymentAddress: function () {
|
addPaymentAddress: function () {
|
||||||
this.payment.data.push({address: '', amount: undefined})
|
this.payment.data.push({address: '', amount: undefined})
|
||||||
},
|
},
|
||||||
|
@ -300,7 +294,7 @@ const watchOnly = async () => {
|
||||||
createPsbt: async function () {
|
createPsbt: async function () {
|
||||||
const wallet = this.g.user.wallets[0]
|
const wallet = this.g.user.wallets[0]
|
||||||
try {
|
try {
|
||||||
this.computeFee()
|
this.computeFee(this.payment.feeRate)
|
||||||
const tx = this.createTx()
|
const tx = this.createTx()
|
||||||
txSize(tx)
|
txSize(tx)
|
||||||
for (const input of tx.inputs) {
|
for (const input of tx.inputs) {
|
||||||
|
@ -824,7 +818,6 @@ const watchOnly = async () => {
|
||||||
h => h.address !== addrData.address
|
h => h.address !== addrData.address
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log('### addressHistory', addressHistory)
|
|
||||||
// add new entries
|
// add new entries
|
||||||
this.history.push(...addressHistory)
|
this.history.push(...addressHistory)
|
||||||
this.history.sort((a, b) => (!a.height ? -1 : b.height - a.height))
|
this.history.sort((a, b) => (!a.height ? -1 : b.height - a.height))
|
||||||
|
@ -899,14 +892,6 @@ const watchOnly = async () => {
|
||||||
return this.addressHistoryFromTxs(addrData, addressTxs)
|
return this.addressHistoryFromTxs(addrData, addressTxs)
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshRecommendedFees: async function () {
|
|
||||||
const {
|
|
||||||
bitcoin: {fees: feesAPI}
|
|
||||||
} = mempoolJS()
|
|
||||||
|
|
||||||
const fn = async () => feesAPI.getFeesRecommended()
|
|
||||||
this.payment.recommededFees = await retryWithDelay(fn)
|
|
||||||
},
|
|
||||||
getAddressTxsUtxoDelayed: async function (address) {
|
getAddressTxsUtxoDelayed: async function (address) {
|
||||||
const {
|
const {
|
||||||
bitcoin: {addresses: addressesAPI}
|
bitcoin: {addresses: addressesAPI}
|
||||||
|
@ -974,6 +959,10 @@ const watchOnly = async () => {
|
||||||
handleAddressesUpdated: async function (addresses) {
|
handleAddressesUpdated: async function (addresses) {
|
||||||
this.addresses = addresses
|
this.addresses = addresses
|
||||||
await this.scanAddressWithAmount()
|
await this.scanAddressWithAmount()
|
||||||
|
},
|
||||||
|
handleFeeRateChanged: function (newFeeRate) {
|
||||||
|
console.log('### newFeeRate', newFeeRate)
|
||||||
|
this.feeValue = this.computeFee(newFeeRate)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: async function () {
|
created: async function () {
|
||||||
|
|
|
@ -48,14 +48,6 @@ const tableData = {
|
||||||
changeAddress: {},
|
changeAddress: {},
|
||||||
changeAmount: 0,
|
changeAmount: 0,
|
||||||
|
|
||||||
feeRate: 1,
|
|
||||||
recommededFees: {
|
|
||||||
fastestFee: 1,
|
|
||||||
halfHourFee: 1,
|
|
||||||
hourFee: 1,
|
|
||||||
economyFee: 1,
|
|
||||||
minimumFee: 1
|
|
||||||
},
|
|
||||||
fee: 0,
|
fee: 0,
|
||||||
txSize: 0,
|
txSize: 0,
|
||||||
tx: null,
|
tx: null,
|
||||||
|
|
|
@ -500,7 +500,6 @@
|
||||||
class="bg-dark text-white shadow-2"
|
class="bg-dark text-white shadow-2"
|
||||||
>
|
>
|
||||||
<q-tab name="destination" label="Send To"></q-tab>
|
<q-tab name="destination" label="Send To"></q-tab>
|
||||||
<q-tab name="fees" label="Fees"></q-tab>
|
|
||||||
<q-tab name="coinControl" label="Coin Control"></q-tab>
|
<q-tab name="coinControl" label="Coin Control"></q-tab>
|
||||||
</q-tabs>
|
</q-tabs>
|
||||||
<q-tab-panels v-model="paymentTab">
|
<q-tab-panels v-model="paymentTab">
|
||||||
|
@ -596,6 +595,7 @@
|
||||||
</q-table>
|
</q-table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="row items-center no-wrap q-mb-md">
|
<!-- <div class="row items-center no-wrap q-mb-md">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<q-table
|
<q-table
|
||||||
|
@ -660,78 +660,29 @@
|
||||||
-->
|
-->
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
<div class="row items-center no-wrap q-mb-md q-pt-lg">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-toggle
|
||||||
|
label="Custom Fee"
|
||||||
|
color="secodary"
|
||||||
|
v-model="showCustomFee"
|
||||||
|
></q-toggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-card v-show="showCustomFee">
|
||||||
|
<q-card-section>
|
||||||
|
<div class="row items-center no-wrap q-mb-md q-pt-md">
|
||||||
|
<div class="col-12">
|
||||||
|
<fees
|
||||||
|
:totalfee="feeValue"
|
||||||
|
@update:fee-rate="handleFeeRateChanged"
|
||||||
|
></fees>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
<q-tab-panel name="fees">
|
|
||||||
<q-card
|
|
||||||
><q-card-section>
|
|
||||||
<div class="row items-center no-wrap q-mb-md">
|
|
||||||
<div class="col-2 q-pr-lg">Fee Rate:</div>
|
|
||||||
<div class="col-3 q-pr-lg">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.number="payment.feeRate"
|
|
||||||
:rules="[val => !!val || 'Field is required']"
|
|
||||||
type="number"
|
|
||||||
label="sats/vbyte"
|
|
||||||
></q-input>
|
|
||||||
</div>
|
|
||||||
<div class="col-7">
|
|
||||||
<q-slider
|
|
||||||
v-model="payment.feeRate"
|
|
||||||
color="orange"
|
|
||||||
markers
|
|
||||||
snap
|
|
||||||
label
|
|
||||||
label-always
|
|
||||||
:label-value="getFeeRateLabel(payment.feeRate)"
|
|
||||||
:min="1"
|
|
||||||
:max="payment.recommededFees.fastestFee"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="payment.feeRate < payment.recommededFees.hourFee || payment.feeRate > payment.recommededFees.fastestFee"
|
|
||||||
class="row items-center no-wrap q-mb-md"
|
|
||||||
>
|
|
||||||
<div class="col-2 q-pr-lg"></div>
|
|
||||||
<div class="col-10 q-pr-lg">
|
|
||||||
<q-badge
|
|
||||||
v-if="payment.feeRate < payment.recommededFees.hourFee"
|
|
||||||
color="pink"
|
|
||||||
size="lg"
|
|
||||||
>
|
|
||||||
Warning! The fee is too low. The transaction might take
|
|
||||||
a long time to confirm.
|
|
||||||
</q-badge>
|
|
||||||
<q-badge
|
|
||||||
v-if="payment.feeRate > payment.recommededFees.fastestFee"
|
|
||||||
color="pink"
|
|
||||||
>
|
|
||||||
Warning! The fee is too high. You might be overpaying
|
|
||||||
for this transaction.
|
|
||||||
</q-badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row items-center no-wrap q-mb-md">
|
|
||||||
<div class="col-2 q-pr-lg">Fee:</div>
|
|
||||||
<div class="col-3 q-pr-lg">{{computeFee()}} sats</div>
|
|
||||||
<div class="col-7 q-pr-lg">
|
|
||||||
<q-btn
|
|
||||||
outline
|
|
||||||
dense
|
|
||||||
size="md"
|
|
||||||
icon="refresh"
|
|
||||||
color="grey"
|
|
||||||
@click="refreshRecommendedFees()"
|
|
||||||
>Refresh Fee Rates</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section></q-card
|
|
||||||
>
|
|
||||||
</q-tab-panel>
|
|
||||||
<q-tab-panel name="coinControl">
|
<q-tab-panel name="coinControl">
|
||||||
<utxo-list
|
<utxo-list
|
||||||
:utxos="utxos.data"
|
:utxos="utxos.data"
|
||||||
|
@ -998,6 +949,7 @@
|
||||||
<script src="{{ url_for('watchonly_static', path='components/address-list/address-list.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/address-list/address-list.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/history/history.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/history/history.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/utxo-list/utxo-list.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/utxo-list/utxo-list.js') }}"></script>
|
||||||
|
<script src="{{ url_for('watchonly_static', path='components/fees/fees.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/payment/payment.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/payment/payment.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='js/index.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='js/index.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Add table
Reference in a new issue