refactor: payment: first migration

This commit is contained in:
Vlad Stan 2022-07-26 10:44:36 +03:00
parent 3765900be0
commit 5638bca2d7
5 changed files with 259 additions and 258 deletions

View file

@ -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>

View file

@ -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)
}
},

View file

@ -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) {

View file

@ -32,8 +32,7 @@ const tableData = {
},
payment: {
data: [{address: '', amount: undefined}], // todo: remove
changeWallet: null,
changeAddress: {},
changeAmount: 0,
fee: 0,

View file

@ -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 %}