mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-24 22:58:46 +01:00
refactor: payment: first migration
This commit is contained in:
parent
3765900be0
commit
5638bca2d7
5 changed files with 259 additions and 258 deletions
|
@ -0,0 +1,140 @@
|
|||
<div>
|
||||
<q-form @submit="createPsbt" class="q-gutter-md">
|
||||
<q-tabs v-model="paymentTab" no-caps class="bg-dark text-white shadow-2">
|
||||
<q-tab name="destination" label="Send To"></q-tab>
|
||||
<q-tab name="coinControl" label="Coin Control"></q-tab>
|
||||
</q-tabs>
|
||||
<q-tab-panels v-model="paymentTab">
|
||||
<q-tab-panel name="destination">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
{{sendToList}}
|
||||
<send-to
|
||||
:data.sync="sendToList"
|
||||
:tx:size="txSizeNoChange"
|
||||
:sats-denominated="sats_denominated"
|
||||
></send-to>
|
||||
<!-- <div class="row items-center no-wrap q-mb-md">
|
||||
<div class="col-12">
|
||||
<q-table
|
||||
:columns="summaryTable.columns"
|
||||
:data="summary.data"
|
||||
hide-bottom
|
||||
>
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td key="totalInputs" :props="props">
|
||||
<q-badge class="text-subtitle2" color="green">
|
||||
{{satBtc(getTotalSelectedUtxoAmount())}}
|
||||
</q-badge>
|
||||
</q-td>
|
||||
<q-td key="totalOutputs" :props="props">
|
||||
<q-badge class="text-subtitle2" color="blue">
|
||||
{{satBtc(getTotalPaymentAmount())}}
|
||||
</q-badge>
|
||||
</q-td>
|
||||
<q-td key="fees" :props="props">
|
||||
<q-badge class="text-subtitle2" color="orange">
|
||||
{{satBtc(feeValue)}}
|
||||
</q-badge>
|
||||
</q-td>
|
||||
<q-td key="change" :props="props">
|
||||
<q-badge
|
||||
v-if="payment.changeAmount >= 0"
|
||||
class="text-subtitle2"
|
||||
color="green"
|
||||
>
|
||||
{{payment.changeAmount ?
|
||||
satBtc(payment.changeAmount): 'no change'}}
|
||||
</q-badge>
|
||||
<q-badge
|
||||
v-if="payment.changeAmount > 0 && payment.changeAmount < DUST_LIMIT"
|
||||
color="red"
|
||||
>
|
||||
Below dust limit. Will be used as feee.
|
||||
</q-badge>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!--
|
||||
<div
|
||||
v-if="payment.changeAmount < 0"
|
||||
class="row items-center no-wrap q-mb-md"
|
||||
>
|
||||
<div class="col-12">
|
||||
<q-badge
|
||||
class="text-subtitle2 float-left"
|
||||
color="yellow"
|
||||
text-color="black"
|
||||
>
|
||||
The payed amount is higher than the selected amount!
|
||||
</q-badge>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</q-card-section>
|
||||
</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">
|
||||
{{feeRate}}
|
||||
<fee-rate :totalfee="feeValue" :rate.sync="feeRate"></fee-rate>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="coinControl">
|
||||
<utxo-list
|
||||
:utxos="utxos.data"
|
||||
:selectable="true"
|
||||
:payed-amount="getTotalPaymentAmount()"
|
||||
:mempool_endpoint="mempool_endpoint"
|
||||
:sats-denominated="sats_denominated"
|
||||
></utxo-list>
|
||||
<div class="row items-center no-wrap q-mb-md q-pt-lg">
|
||||
<div class="col-2 q-pr-lg">Change Account:</div>
|
||||
<div class="col-3 q-pr-lg">
|
||||
<q-select
|
||||
filled
|
||||
dense
|
||||
emit-value
|
||||
v-model="changeWallet"
|
||||
:options="accounts"
|
||||
@input="selectChangeAddress"
|
||||
:rules="[val => !!val || 'Field is required']"
|
||||
label="Wallet Account"
|
||||
></q-select>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
readonly
|
||||
v-model.trim="changeAddress.address"
|
||||
:rules="[val => !!val || 'Field is required']"
|
||||
type="text"
|
||||
label="Change Address"
|
||||
></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</q-form>
|
||||
</div>
|
||||
|
|
@ -1,18 +1,125 @@
|
|||
async function payment(path) {
|
||||
const template = await loadTemplateAsync(path)
|
||||
const t = await loadTemplateAsync(path)
|
||||
console.log('### template', path, t)
|
||||
Vue.component('payment', {
|
||||
name: 'payment',
|
||||
template,
|
||||
template: t,
|
||||
|
||||
props: ['mempool_endpoint', 'sats_denominated'],
|
||||
props: ['accounts', 'utxos', 'mempool_endpoint', 'sats_denominated'],
|
||||
|
||||
data: function () {
|
||||
return {}
|
||||
return {
|
||||
paymentTab: 'destination',
|
||||
sendToList: [],
|
||||
changeWallet: null,
|
||||
changeAddress: {},
|
||||
changeAmount: 0,
|
||||
showCustomFee: false,
|
||||
feeRate: 1
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
txSize: function () {
|
||||
const tx = this.createTx()
|
||||
return Math.round(txSize(tx))
|
||||
},
|
||||
txSizeNoChange: function () {
|
||||
const tx = this.createTx(true)
|
||||
return Math.round(txSize(tx))
|
||||
},
|
||||
feeValue: function () {
|
||||
return this.feeRate * this.txSize
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
satBtc(val, showUnit = true) {
|
||||
return satOrBtc(val, showUnit, this['sats_denominated'])
|
||||
},
|
||||
createPsbt: async function () {
|
||||
const wallet = this.g.user.wallets[0]
|
||||
try {
|
||||
// this.computeFee(this.feeRate)
|
||||
const tx = this.createTx()
|
||||
// txSize(tx)
|
||||
for (const input of tx.inputs) {
|
||||
input.tx_hex = await this.fetchTxHex(input.tx_id)
|
||||
}
|
||||
|
||||
this.payment.tx = tx
|
||||
const {data} = await LNbits.api.request(
|
||||
'POST',
|
||||
'/watchonly/api/v1/psbt',
|
||||
wallet.adminkey,
|
||||
tx
|
||||
)
|
||||
|
||||
this.payment.psbtBase64 = data
|
||||
} catch (err) {
|
||||
LNbits.utils.notifyApiError(err)
|
||||
}
|
||||
},
|
||||
createTx: function (excludeChange = false) {
|
||||
const tx = {
|
||||
fee_rate: this.feeRate,
|
||||
// tx_size: this.payment.txSize, ???
|
||||
masterpubs: this.accounts.map(w => ({
|
||||
public_key: w.masterpub,
|
||||
fingerprint: w.fingerprint
|
||||
}))
|
||||
}
|
||||
tx.inputs = this.utxos.data
|
||||
.filter(utxo => utxo.selected)
|
||||
.map(mapUtxoToPsbtInput)
|
||||
.sort((a, b) =>
|
||||
a.tx_id < b.tx_id ? -1 : a.tx_id > b.tx_id ? 1 : a.vout - b.vout
|
||||
)
|
||||
|
||||
tx.outputs = this.sendToList.map(out => ({
|
||||
address: out.address,
|
||||
amount: out.amount
|
||||
}))
|
||||
|
||||
if (excludeChange) {
|
||||
this.changeAmount = 0
|
||||
} else {
|
||||
const change = this.createChangeOutput()
|
||||
this.changeAmount = change.amount // todo: compute separately
|
||||
if (change.amount >= this.DUST_LIMIT) {
|
||||
tx.outputs.push(change)
|
||||
}
|
||||
}
|
||||
// Only sort by amount on UI level (no lib for address decode)
|
||||
// Should sort by scriptPubKey (as byte array) on the backend
|
||||
// todo: just shuffle
|
||||
tx.outputs.sort((a, b) => a.amount - b.amount)
|
||||
|
||||
return tx
|
||||
},
|
||||
createChangeOutput: function () {
|
||||
const change = this.changeAddress
|
||||
// const inputAmount = this.getTotalSelectedUtxoAmount() // todo: set amount separately
|
||||
// const payedAmount = this.getTotalPaymentAmount()
|
||||
const walletAcount =
|
||||
this.accounts.find(w => w.id === change.wallet) || {}
|
||||
|
||||
return {
|
||||
address: change.address,
|
||||
// amount: inputAmount - payedAmount - this.feeValue,
|
||||
addressIndex: change.addressIndex,
|
||||
addressIndex: change.addressIndex,
|
||||
masterpub_fingerprint: walletAcount.fingerprint
|
||||
}
|
||||
},
|
||||
selectChangeAddress: function (wallet = {}) {
|
||||
this.changeAddress =
|
||||
this.addresses.find(
|
||||
a => a.wallet === wallet.id && a.isChange && !a.hasActivity
|
||||
) || {}
|
||||
},
|
||||
getTotalPaymentAmount: function () {
|
||||
return this.sendToList.reduce((t, a) => t + (a.amount || 0), 0)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@ const watchOnly = async () => {
|
|||
await sendTo('static/components/send-to/send-to.html')
|
||||
await payment('static/components/payment/payment.html')
|
||||
|
||||
//emplate static/components/payment/payment.html
|
||||
//lnbits/extensions/watchonly/static/components/payment/payment.html
|
||||
|
||||
Vue.filter('reverse', function (value) {
|
||||
// slice to make a copy of array, then reverse the copy
|
||||
return value.slice().reverse()
|
||||
|
@ -32,7 +35,6 @@ const watchOnly = async () => {
|
|||
currentAddress: null,
|
||||
|
||||
tab: 'addresses',
|
||||
paymentTab: 'destination',
|
||||
|
||||
config: {
|
||||
data: {
|
||||
|
@ -84,24 +86,7 @@ const watchOnly = async () => {
|
|||
|
||||
showAddress: false,
|
||||
addressNote: '',
|
||||
showPayment: false,
|
||||
showCustomFee: false,
|
||||
feeRate: 1,
|
||||
sendToList: []
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
txSize: function() {
|
||||
const tx = this.createTx()
|
||||
return Math.round(txSize(tx))
|
||||
},
|
||||
txSizeNoChange: function() {
|
||||
const tx = this.createTx(true)
|
||||
return Math.round(txSize(tx))
|
||||
},
|
||||
feeValue: function(){
|
||||
return this.feeRate * this.txSize
|
||||
showPayment: false
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -205,58 +190,6 @@ const watchOnly = async () => {
|
|||
},
|
||||
|
||||
//################### PAYMENT ###################
|
||||
createTx: function (excludeChange = false) {
|
||||
const tx = {
|
||||
fee_rate: this.feeRate,
|
||||
tx_size: this.payment.txSize,
|
||||
masterpubs: this.walletAccounts.map(w => ({
|
||||
public_key: w.masterpub,
|
||||
fingerprint: w.fingerprint
|
||||
}))
|
||||
}
|
||||
tx.inputs = this.utxos.data
|
||||
.filter(utxo => utxo.selected)
|
||||
.map(mapUtxoToPsbtInput)
|
||||
.sort((a, b) =>
|
||||
a.tx_id < b.tx_id ? -1 : a.tx_id > b.tx_id ? 1 : a.vout - b.vout
|
||||
)
|
||||
|
||||
tx.outputs = this.sendToList.map(out => ({
|
||||
address: out.address,
|
||||
amount: out.amount
|
||||
}))
|
||||
|
||||
if (excludeChange) {
|
||||
this.payment.changeAmount = 0
|
||||
} else {
|
||||
const change = this.createChangeOutput()
|
||||
this.payment.changeAmount = change.amount
|
||||
if (change.amount >= this.DUST_LIMIT) {
|
||||
tx.outputs.push(change)
|
||||
}
|
||||
}
|
||||
// Only sort by amount on UI level (no lib for address decode)
|
||||
// Should sort by scriptPubKey (as byte array) on the backend
|
||||
// todo: just shuffle
|
||||
tx.outputs.sort((a, b) => a.amount - b.amount)
|
||||
|
||||
return tx
|
||||
},
|
||||
createChangeOutput: function () {
|
||||
const change = this.payment.changeAddress
|
||||
// const inputAmount = this.getTotalSelectedUtxoAmount() // todo: set amount separately
|
||||
// const payedAmount = this.getTotalPaymentAmount()
|
||||
const walletAcount =
|
||||
this.walletAccounts.find(w => w.id === change.wallet) || {}
|
||||
|
||||
return {
|
||||
address: change.address,
|
||||
// amount: inputAmount - payedAmount - this.feeValue,
|
||||
addressIndex: change.addressIndex,
|
||||
addressIndex: change.addressIndex,
|
||||
masterpub_fingerprint: walletAcount.fingerprint
|
||||
}
|
||||
},
|
||||
|
||||
initPaymentData: async function () {
|
||||
if (!this.payment.show) return
|
||||
|
@ -265,19 +198,8 @@ const watchOnly = async () => {
|
|||
this.payment.showAdvanced = false
|
||||
this.payment.changeWallet = this.walletAccounts[0]
|
||||
this.selectChangeAddress(this.payment.changeWallet)
|
||||
|
||||
this.payment.feeRate = this.payment.recommededFees.halfHourFee
|
||||
},
|
||||
|
||||
getTotalPaymentAmount: function () {
|
||||
return this.payment.data.reduce((t, a) => t + (a.amount || 0), 0)
|
||||
},
|
||||
selectChangeAddress: function (wallet = {}) {
|
||||
this.payment.changeAddress =
|
||||
this.addresses.find(
|
||||
a => a.wallet === wallet.id && a.isChange && !a.hasActivity
|
||||
) || {}
|
||||
},
|
||||
goToPaymentView: async function () {
|
||||
// this.payment.show = true
|
||||
this.showPayment = true
|
||||
|
@ -286,29 +208,7 @@ const watchOnly = async () => {
|
|||
},
|
||||
|
||||
//################### PSBT ###################
|
||||
createPsbt: async function () {
|
||||
const wallet = this.g.user.wallets[0]
|
||||
try {
|
||||
// this.computeFee(this.feeRate)
|
||||
const tx = this.createTx()
|
||||
// txSize(tx)
|
||||
for (const input of tx.inputs) {
|
||||
input.tx_hex = await this.fetchTxHex(input.tx_id)
|
||||
}
|
||||
|
||||
this.payment.tx = tx
|
||||
const {data} = await LNbits.api.request(
|
||||
'POST',
|
||||
'/watchonly/api/v1/psbt',
|
||||
wallet.adminkey,
|
||||
tx
|
||||
)
|
||||
|
||||
this.payment.psbtBase64 = data
|
||||
} catch (err) {
|
||||
LNbits.utils.notifyApiError(err)
|
||||
}
|
||||
},
|
||||
extractTxFromPsbt: async function (psbtBase64) {
|
||||
const wallet = this.g.user.wallets[0]
|
||||
try {
|
||||
|
@ -789,9 +689,7 @@ const watchOnly = async () => {
|
|||
this.utxos.data = []
|
||||
this.utxos.total = 0
|
||||
this.history = []
|
||||
console.log('### scanAddressWithAmount1', this.addresses)
|
||||
const addresses = this.addresses.filter(a => a.hasActivity)
|
||||
console.log('### scanAddressWithAmount2', addresses)
|
||||
await this.updateUtxosForAddresses(addresses)
|
||||
},
|
||||
scanAddress: async function (addressData) {
|
||||
|
@ -954,7 +852,7 @@ const watchOnly = async () => {
|
|||
handleAddressesUpdated: async function (addresses) {
|
||||
this.addresses = addresses
|
||||
await this.scanAddressWithAmount()
|
||||
},
|
||||
}
|
||||
},
|
||||
created: async function () {
|
||||
if (this.g.user.wallets.length) {
|
||||
|
|
|
@ -32,8 +32,7 @@ const tableData = {
|
|||
},
|
||||
payment: {
|
||||
data: [{address: '', amount: undefined}], // todo: remove
|
||||
changeWallet: null,
|
||||
changeAddress: {},
|
||||
|
||||
changeAmount: 0,
|
||||
|
||||
fee: 0,
|
||||
|
|
|
@ -493,150 +493,7 @@
|
|||
</q-tab-panels>
|
||||
</q-card-section>
|
||||
<q-card-section v-show="showPayment">
|
||||
<q-form @submit="createPsbt" class="q-gutter-md">
|
||||
<q-tabs
|
||||
v-model="paymentTab"
|
||||
no-caps
|
||||
class="bg-dark text-white shadow-2"
|
||||
>
|
||||
<q-tab name="destination" label="Send To"></q-tab>
|
||||
<q-tab name="coinControl" label="Coin Control"></q-tab>
|
||||
</q-tabs>
|
||||
<q-tab-panels v-model="paymentTab">
|
||||
<q-tab-panel name="destination">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
{{sendToList}}
|
||||
<send-to
|
||||
:data.sync="sendToList"
|
||||
:tx:size="txSizeNoChange"
|
||||
:sats-denominated="config.data.sats_denominated"
|
||||
></send-to>
|
||||
<!-- <div class="row items-center no-wrap q-mb-md">
|
||||
<div class="col-12">
|
||||
<q-table
|
||||
:columns="summaryTable.columns"
|
||||
:data="summary.data"
|
||||
hide-bottom
|
||||
>
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td key="totalInputs" :props="props">
|
||||
<q-badge class="text-subtitle2" color="green">
|
||||
{{satBtc(getTotalSelectedUtxoAmount())}}
|
||||
</q-badge>
|
||||
</q-td>
|
||||
<q-td key="totalOutputs" :props="props">
|
||||
<q-badge class="text-subtitle2" color="blue">
|
||||
{{satBtc(getTotalPaymentAmount())}}
|
||||
</q-badge>
|
||||
</q-td>
|
||||
<q-td key="fees" :props="props">
|
||||
<q-badge class="text-subtitle2" color="orange">
|
||||
{{satBtc(feeValue)}}
|
||||
</q-badge>
|
||||
</q-td>
|
||||
<q-td key="change" :props="props">
|
||||
<q-badge
|
||||
v-if="payment.changeAmount >= 0"
|
||||
class="text-subtitle2"
|
||||
color="green"
|
||||
>
|
||||
{{payment.changeAmount ?
|
||||
satBtc(payment.changeAmount): 'no change'}}
|
||||
</q-badge>
|
||||
<q-badge
|
||||
v-if="payment.changeAmount > 0 && payment.changeAmount < DUST_LIMIT"
|
||||
color="red"
|
||||
>
|
||||
Below dust limit. Will be used as feee.
|
||||
</q-badge>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!--
|
||||
<div
|
||||
v-if="payment.changeAmount < 0"
|
||||
class="row items-center no-wrap q-mb-md"
|
||||
>
|
||||
<div class="col-12">
|
||||
<q-badge
|
||||
class="text-subtitle2 float-left"
|
||||
color="yellow"
|
||||
text-color="black"
|
||||
>
|
||||
The payed amount is higher than the selected amount!
|
||||
</q-badge>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</q-card-section>
|
||||
</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">
|
||||
{{feeRate}}
|
||||
<fee-rate
|
||||
:totalfee="feeValue"
|
||||
:rate.sync="feeRate"
|
||||
></fee-rate>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="coinControl">
|
||||
<utxo-list
|
||||
:utxos="utxos.data"
|
||||
:selectable="true"
|
||||
:payed-amount="getTotalPaymentAmount()"
|
||||
:mempool_endpoint="config.data.mempool_endpoint"
|
||||
:sats-denominated="config.data.sats_denominated"
|
||||
></utxo-list>
|
||||
<div class="row items-center no-wrap q-mb-md q-pt-lg">
|
||||
<div class="col-2 q-pr-lg">Change Account:</div>
|
||||
<div class="col-3 q-pr-lg">
|
||||
<q-select
|
||||
filled
|
||||
dense
|
||||
emit-value
|
||||
v-model="payment.changeWallet"
|
||||
:options="walletAccounts"
|
||||
@input="selectChangeAddress"
|
||||
:rules="[val => !!val || 'Field is required']"
|
||||
label="Wallet Account"
|
||||
></q-select>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
readonly
|
||||
v-model.trim="payment.changeAddress.address"
|
||||
:rules="[val => !!val || 'Field is required']"
|
||||
type="text"
|
||||
label="Change Address"
|
||||
></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</q-form>
|
||||
<payment :accounts="walletAccounts" :utxos="utxos"></payment>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
|
@ -870,4 +727,4 @@
|
|||
<script src="{{ url_for('watchonly_static', path='components/send-to/send-to.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>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
Loading…
Add table
Reference in a new issue