feat: broadcast transaction (partial solution)
This commit is contained in:
parent
6098b8308e
commit
367faf437a
5 changed files with 101 additions and 17 deletions
|
|
@ -82,7 +82,7 @@ class CreatePsbt(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class ExtractPsbt(BaseModel):
|
class ExtractPsbt(BaseModel):
|
||||||
psbtBase64 = ""
|
psbtBase64 = "" # // todo snake case
|
||||||
|
|
||||||
|
|
||||||
class SignedTransaction(BaseModel):
|
class SignedTransaction(BaseModel):
|
||||||
|
|
@ -90,6 +90,10 @@ class SignedTransaction(BaseModel):
|
||||||
tx_json: Optional[str]
|
tx_json: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
class BroadcastTransaction(BaseModel):
|
||||||
|
tx_hex: str
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseModel):
|
class Config(BaseModel):
|
||||||
mempool_endpoint = "https://mempool.space"
|
mempool_endpoint = "https://mempool.space"
|
||||||
receive_gap_limit = 20
|
receive_gap_limit = 20
|
||||||
|
|
|
||||||
|
|
@ -639,8 +639,10 @@ new Vue({
|
||||||
)
|
)
|
||||||
if (data) {
|
if (data) {
|
||||||
this.payment.signedTx = JSON.parse(data.tx_json)
|
this.payment.signedTx = JSON.parse(data.tx_json)
|
||||||
|
this.payment.signedTxHex = data.tx_hex
|
||||||
} else {
|
} else {
|
||||||
this.payment.signedTx = null
|
this.payment.signedTx = null
|
||||||
|
his.payment.signedTxHex = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -688,6 +690,27 @@ new Vue({
|
||||||
console.log('### sharePsbtWithAnimatedQRCode')
|
console.log('### sharePsbtWithAnimatedQRCode')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
broadcastTransaction: async function () {
|
||||||
|
console.log('### broadcastTransaction', this.payment.signedTxHex)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const wallet = this.g.user.wallets[0]
|
||||||
|
await LNbits.api.request(
|
||||||
|
'POST',
|
||||||
|
'/watchonly/api/v1/tx',
|
||||||
|
wallet.adminkey,
|
||||||
|
{tx_hex: this.payment.signedTxHex}
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Failed to broadcast!',
|
||||||
|
caption: `${error}`,
|
||||||
|
timeout: 10000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
//################### UTXOs ###################
|
//################### UTXOs ###################
|
||||||
scanAllAddresses: async function () {
|
scanAllAddresses: async function () {
|
||||||
await this.refreshAddresses()
|
await this.refreshAddresses()
|
||||||
|
|
|
||||||
|
|
@ -262,6 +262,8 @@ const tableData = {
|
||||||
psbtBase64: '',
|
psbtBase64: '',
|
||||||
psbtBase64Signed: '',
|
psbtBase64Signed: '',
|
||||||
signedTx: null,
|
signedTx: null,
|
||||||
|
signedTxHex: null,
|
||||||
|
sentTxId: null,
|
||||||
utxoSelectionModes: [
|
utxoSelectionModes: [
|
||||||
'Manual',
|
'Manual',
|
||||||
'Random',
|
'Random',
|
||||||
|
|
|
||||||
|
|
@ -182,14 +182,13 @@
|
||||||
:disabled="scan.scanning == true"
|
:disabled="scan.scanning == true"
|
||||||
>Scan Blockchain</q-btn
|
>Scan Blockchain</q-btn
|
||||||
>
|
>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<q-spinner
|
<q-spinner
|
||||||
v-if="scan.scanning == true"
|
v-if="scan.scanning == true"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="2.55em"
|
size="2.55em"
|
||||||
></q-spinner>
|
></q-spinner>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 q-pr-md">
|
<div class="col-3 q-pr-md">
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|
@ -997,8 +996,8 @@
|
||||||
class="text-subtitle2"
|
class="text-subtitle2"
|
||||||
color="green"
|
color="green"
|
||||||
>
|
>
|
||||||
{{payment.changeAmount ? satBtc(payment.changeAmount):
|
{{payment.changeAmount ?
|
||||||
'no change'}}
|
satBtc(payment.changeAmount): 'no change'}}
|
||||||
</q-badge>
|
</q-badge>
|
||||||
<q-badge
|
<q-badge
|
||||||
v-if="payment.changeAmount > 0 && payment.changeAmount < DUST_LIMIT"
|
v-if="payment.changeAmount > 0 && payment.changeAmount < DUST_LIMIT"
|
||||||
|
|
@ -1065,7 +1064,7 @@
|
||||||
>Disconnect</q-btn
|
>Disconnect</q-btn
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-6">
|
||||||
<q-toggle
|
<q-toggle
|
||||||
label="Advanced Config"
|
label="Advanced Config"
|
||||||
color="secodary float-left"
|
color="secodary float-left"
|
||||||
|
|
@ -1073,7 +1072,7 @@
|
||||||
v-model="serial.showAdvancedConfig"
|
v-model="serial.showAdvancedConfig"
|
||||||
></q-toggle>
|
></q-toggle>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 ">
|
<div class="col-3">
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="serial.selectedPort"
|
v-if="serial.selectedPort"
|
||||||
@click="sendPsbtToSerialPort()"
|
@click="sendPsbtToSerialPort()"
|
||||||
|
|
@ -1113,16 +1112,50 @@
|
||||||
<div class="row items-center no-wrap q-mb-sm">
|
<div class="row items-center no-wrap q-mb-sm">
|
||||||
<div class="col-3 q-pr-lg">Fee</div>
|
<div class="col-3 q-pr-lg">Fee</div>
|
||||||
<div class="col-9">
|
<div class="col-9">
|
||||||
<q-badge color="orange">{{satBtc(payment.signedTx.fee)}} </q-badge></div>
|
<q-badge color="orange"
|
||||||
|
>{{satBtc(payment.signedTx.fee)}}
|
||||||
|
</q-badge>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<q-separator class="q-mb-lg"></q-separator>
|
<q-separator class="q-mb-lg"></q-separator>
|
||||||
<div v-for="out in payment.signedTx.outputs" class="row items-center no-wrap q-mb-sm">
|
<div
|
||||||
<div class="col-3 q-pr-lg"> <q-badge color="orange">{{satBtc(out.amount)}}</div>
|
v-for="out in payment.signedTx.outputs"
|
||||||
|
class="row items-center no-wrap q-mb-sm"
|
||||||
|
>
|
||||||
|
<div class="col-3 q-pr-lg">
|
||||||
|
<q-badge color="orange"
|
||||||
|
>{{satBtc(out.amount)}}</q-badge
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-9">
|
<div class="col-9">
|
||||||
<q-badge outline color="blue">{{out.address}}</q-badge></div>
|
<q-badge outline color="blue"
|
||||||
|
>{{out.address}}</q-badge
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="payment.psbtBase64Signed"
|
||||||
|
class="row items-center no-wrap q-mb-md"
|
||||||
|
>
|
||||||
|
<div class="col-3 q-pr-lg">
|
||||||
|
<q-btn
|
||||||
|
v-if="payment.signedTx"
|
||||||
|
@click="broadcastTransaction()"
|
||||||
|
unelevated
|
||||||
|
color="secondary"
|
||||||
|
>Send Payment</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="col-9">
|
||||||
|
<q-input
|
||||||
|
v-if="payment.sentTxId"
|
||||||
|
v-model="payment.sentTxId"
|
||||||
|
filled
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
@ -1301,7 +1334,6 @@
|
||||||
|
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
.btn-full {
|
.btn-full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
import httpx
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from fastapi import Query, Request
|
from fastapi import Query, Request
|
||||||
|
|
@ -33,6 +34,7 @@ from .crud import (
|
||||||
update_config,
|
update_config,
|
||||||
)
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
|
BroadcastTransaction,
|
||||||
SignedTransaction,
|
SignedTransaction,
|
||||||
CreateWallet,
|
CreateWallet,
|
||||||
CreatePsbt,
|
CreatePsbt,
|
||||||
|
|
@ -304,6 +306,27 @@ async def api_psbt_extract_tx(
|
||||||
return res.dict()
|
return res.dict()
|
||||||
|
|
||||||
|
|
||||||
|
@watchonly_ext.post("/api/v1/tx")
|
||||||
|
async def api_psbt_extract_tx(
|
||||||
|
data: BroadcastTransaction, w: WalletTypeInfo = Depends(require_admin_key)
|
||||||
|
):
|
||||||
|
print("### data", data)
|
||||||
|
try:
|
||||||
|
config = await get_config(w.wallet.user)
|
||||||
|
if not config:
|
||||||
|
raise ValueError("Cannot broadcast transaction. Mempool endpoint not defined!")
|
||||||
|
x = bytes.fromhex(data.tx_hex)
|
||||||
|
print('### x', x)
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.post(
|
||||||
|
config.mempool_endpoint + "/api/tx",
|
||||||
|
data=x
|
||||||
|
)
|
||||||
|
tx_id = r.json()
|
||||||
|
return tx_id
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
|
||||||
|
|
||||||
#############################CONFIG##########################
|
#############################CONFIG##########################
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue