mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
Chore: Fix psbt creator api to work with liquid assets when using json (#1831)
* fix psbt creator api to work with liquid assets when using json * make cypress tests less flaky Co-authored-by: moneymanolis <moneymanolis@protonmail.com>
This commit is contained in:
parent
a82eddfe7f
commit
3ace290482
7 changed files with 184 additions and 21 deletions
|
|
@ -5,6 +5,7 @@ describe('Operating with an Elements multisig wallet', () => {
|
|||
cy.visit('/')
|
||||
cy.get('#node-switch-icon').click()
|
||||
cy.get('#elements_node-select-node-form > .item > div').click()
|
||||
cy.get('[value="save"]').click()
|
||||
|
||||
// Delete wallets if existing
|
||||
cy.deleteWallet("Elm Multi Segwit Wallet")
|
||||
|
|
|
|||
|
|
@ -184,7 +184,10 @@ describe('Test sending transactions', () => {
|
|||
it('Use an address belonging to the wallet', () => {
|
||||
cy.selectWallet("Ghost wallet")
|
||||
cy.get('#btn_send').click()
|
||||
cy.get('#recipients').find('#recipient_0').find('#address').type("bcrt1qvtdx75y4554ngrq6aff3xdqnvjhmct5wck95qs", { force: true })
|
||||
// Simulating pasting the address, this also reduces the amount of fetch API calls to just one
|
||||
cy.get('#recipient_0').find('#address').invoke('val', "bcrt1qvtdx75y4554ngrq6aff3xdqnvjhmct5wck95qs").trigger('input')
|
||||
cy.get('#recipient_0').find('#label').type('To my own wallet', { force: true })
|
||||
cy.get('#recipient_0').find('#amount').type(10, { force: true })
|
||||
// Checking that the background colour of the address is green as it belongs to the wallet
|
||||
cy.get('#recipients').find('#recipient_0').find('#address').should('have.css', 'background-color','rgb(48, 109, 48)')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ Cypress.Commands.add("deleteDevice", (name) => {
|
|||
cy.get('body').then(($body) => {
|
||||
if ($body.text().includes(name)) {
|
||||
cy.get('#toggle_devices_list').click()
|
||||
cy.contains(name).click()
|
||||
cy.contains(name).click( {force: true} )
|
||||
cy.get('#forget_device').click()
|
||||
cy.reload()
|
||||
// For hot wallets only
|
||||
|
|
@ -251,9 +251,9 @@ Cypress.Commands.add("mine2wallet", (chain) => {
|
|||
Cypress.Commands.add("createPsbt", (address, label="a_label", amount=0.01) => {
|
||||
cy.get('#btn_send').click()
|
||||
// it is not clear why .shadow(), or { includeShadowDom: true } is needed here to find the elements in the ShadowDOM, but not in the other cypresss tests
|
||||
cy.get('#recipient_0').find('#address', { includeShadowDom: true }).type(address)
|
||||
cy.get('#recipient_0').find('#label', { includeShadowDom: true }).type(label)
|
||||
cy.get('#recipient_0').find('#address', { includeShadowDom: true }).type(address, { force: true })
|
||||
cy.get('#recipient_0').find('#label', { includeShadowDom: true }).type(label, { force: true })
|
||||
//cy.get('#send_max_0').click()
|
||||
cy.get('#recipient_0').find('#amount', { includeShadowDom: true }).type(amount)
|
||||
cy.get('#recipient_0').find('#amount', { includeShadowDom: true }).type(amount, { force: true })
|
||||
cy.get('#create_psbt_btn').click()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import requests
|
|||
from cryptoadvance.specter.specter_error import SpecterError
|
||||
from cryptoadvance.specter.util.common import str2bool
|
||||
|
||||
from ..helpers import is_testnet
|
||||
from ..helpers import is_testnet, normalize_address
|
||||
from ..util.descriptor import AddChecksum, Descriptor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -86,12 +86,7 @@ class PsbtCreator:
|
|||
f"Unknown ui_option: {ui_option}. Valid ones are ui|text|json"
|
||||
)
|
||||
# normalizing
|
||||
self.addresses = [
|
||||
address.lower()
|
||||
if address.startswith(("BC1", "TB1", "BCRT1", "EL1", "ERT1", "EX1", "LQ1"))
|
||||
else address
|
||||
for address in self.addresses
|
||||
]
|
||||
self.addresses = [normalize_address(addr) for addr in self.addresses]
|
||||
# get kwargs
|
||||
if ui_option == "ui" or ui_option == "text":
|
||||
self.kwargs = PsbtCreator.kwargs_from_request_form(request_form)
|
||||
|
|
@ -186,7 +181,7 @@ class PsbtCreator:
|
|||
continue
|
||||
addresses.append(output.split(",")[0].strip())
|
||||
if recipients_amount_unit == "sat":
|
||||
amounts.append(float(output.split(",")[1].strip()) / 1e8)
|
||||
amounts.append(round(float(output.split(",")[1].strip()) / 1e8, 8))
|
||||
elif recipients_amount_unit == "btc":
|
||||
amounts.append(float(output.split(",")[1].strip()))
|
||||
else:
|
||||
|
|
@ -243,18 +238,28 @@ class PsbtCreator:
|
|||
try:
|
||||
amount = float(recipient["amount"])
|
||||
if recipient["unit"] == "sat":
|
||||
amounts.append(float(amount / 1e8))
|
||||
amounts.append(round(amount / 1e8, 8))
|
||||
elif recipient["unit"] == "btc":
|
||||
amounts.append(amount)
|
||||
else:
|
||||
raise SpecterError(
|
||||
f"Non-compliant json: Unknown unit {recipient['unit']}"
|
||||
)
|
||||
if specter.is_liquid:
|
||||
if len(recipient["unit"]) == 64:
|
||||
amounts.append(amount)
|
||||
else:
|
||||
raise SpecterError(
|
||||
f"Non-compliant json: Unknown unit {recipient['unit']}. Accepted: sat, btc or 64-char hex asset id."
|
||||
)
|
||||
else:
|
||||
raise SpecterError(
|
||||
f"Non-compliant json: Unknown unit {recipient['unit']}"
|
||||
)
|
||||
except ValueError as e:
|
||||
raise SpecterError(
|
||||
f"Could not parse amount {recipient.get('amount')} because {e}"
|
||||
)
|
||||
unit = recipient["unit"]
|
||||
if specter.is_liquid and unit in ["sat", "btc"]:
|
||||
unit = specter.default_asset
|
||||
amount_units.append(unit)
|
||||
|
||||
label = recipient.get("label", "")
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from embit.psbt import PSBT
|
|||
from embit.transaction import Transaction
|
||||
from embit.liquid.pset import PSET
|
||||
from embit.liquid.transaction import LTransaction
|
||||
from embit.liquid.networks import NETWORKS
|
||||
from .persistence import read_json_file, write_json_file
|
||||
from .util.bcur import bcur_decode
|
||||
import threading
|
||||
|
|
@ -67,6 +68,23 @@ def is_liquid(chain):
|
|||
return chain not in ["main", "regtest", "test", "signet", "None", "none", None, ""]
|
||||
|
||||
|
||||
def normalize_address(addr: str) -> str:
|
||||
"""
|
||||
Normalized address to canonical form.
|
||||
Upper bech32 -> lower bech32, but doesn't touch base58 addresses.
|
||||
"""
|
||||
# bitcoin hrps
|
||||
hrps = {net["bech32"] + "1" for net in NETWORKS.values()}
|
||||
# liquid hrps
|
||||
# Blech32 addresses are intended for confidential assets
|
||||
hrps = hrps.union(
|
||||
{net["blech32"] + "1" for net in NETWORKS.values() if "blech32" in net}
|
||||
)
|
||||
if addr.lower().startswith(tuple(hrps)):
|
||||
return addr.lower()
|
||||
return addr
|
||||
|
||||
|
||||
def locked(customlock=defaultlock):
|
||||
"""
|
||||
@locked(lock) decorator.
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@
|
|||
|
||||
|
||||
|
||||
markAdddressGreenIfMine(){
|
||||
markAddressGreenIfMine(){
|
||||
// mark own addresses green
|
||||
this.isAddressMyOwn().then((isMine) => {
|
||||
this.markRecipient(isMine);
|
||||
|
|
@ -248,8 +248,13 @@
|
|||
}
|
||||
|
||||
dispatchAddressOnInput(result) {
|
||||
this.markAdddressGreenIfMine();
|
||||
this.dispatchEvent(new CustomEvent('address-input'));
|
||||
let address = this.addressElement.value;
|
||||
// To reduce the fetch API calls if the address is typed
|
||||
// TODO: If we have a better JS address validation, this can be useful here, too
|
||||
if (address.length >= 14) {
|
||||
this.markAddressGreenIfMine();
|
||||
this.dispatchEvent(new CustomEvent('address-input'));
|
||||
}
|
||||
}
|
||||
|
||||
dispatchRemove(result) {
|
||||
|
|
@ -314,7 +319,7 @@
|
|||
// address
|
||||
set address(newValue){
|
||||
this.addressElement.value = newValue;
|
||||
this.markAdddressGreenIfMine();
|
||||
this.markAddressGreenIfMine();
|
||||
}
|
||||
get address(){
|
||||
return this.addressElement.value;
|
||||
|
|
|
|||
|
|
@ -168,3 +168,134 @@ def test_PsbtCreator_json(caplog):
|
|||
}
|
||||
|
||||
psbt_creator.create_psbt(wallet_mock)
|
||||
|
||||
|
||||
def test_PsbtCreator_ui_liquid(caplog):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
specter_mock = MagicMock()
|
||||
# non liquid and default asset btc (for unit-calculation)
|
||||
specter_mock.is_liquid = True
|
||||
specter_mock.default_asset = "1" * 64
|
||||
|
||||
wallet_mock = MagicMock()
|
||||
specter_mock.chain = "liquid-regtest"
|
||||
# Let's mock the request.form which behaves like a dict but also needs getlist()
|
||||
request_form_data = {
|
||||
"rbf_tx_id": "",
|
||||
"amount_unit_text": "btc",
|
||||
"subtract_from": "0",
|
||||
"fee_option": "dynamic",
|
||||
"fee_rate": "",
|
||||
"fee_rate_dynamic": "64",
|
||||
"rbf": "on",
|
||||
"action": "createpsbt",
|
||||
"recipient_dicts": '[{"unit":"btc","amount":0.1,"recipient_id":0,"address":"el1qqgxqu7f05rm7e245nvdje28wa6fl3s0cv5lga3yahfqddgr4u8zg75nxwjmrrswvue8x2glk0rhvngz30du8gs5m3lgtfvjrx","label":"someLabel","btc_amount":"0.1"},'
|
||||
'{"unit":"sat","amount":111211,"recipient_id":1,"address":"EL1QQWHNYT8NSPG0PFXDZJ98ER2K66VE07D297VQ50TRGXZ2P28RJJSS69W3ZY3AUYLF60SKRL0G72V8ZTCCXZYR5WM2AQNAMNQMZ","label":"someOtherLabel","btc_amount":"0.00111211"},'
|
||||
'{"unit":"0b411a802e536a4217f46a47644c9e207db975f81e697753db2a1915d17dda79","amount":0.003,"recipient_id":2,"address":"AzpxZsPXCk3ct5wx9QfynivkEjHwzKELtHqWtwZ7RZKPncPtYBYn6tktDxCeMtNghMUBMw9MtD7kDcjC","label":"asset test","btc_amount":"0.003"}]',
|
||||
}
|
||||
|
||||
psbt_creator: PsbtCreator = PsbtCreator(
|
||||
specter_mock, wallet_mock, "ui", request_form=request_form_data
|
||||
)
|
||||
|
||||
assert psbt_creator.addresses == [
|
||||
"el1qqgxqu7f05rm7e245nvdje28wa6fl3s0cv5lga3yahfqddgr4u8zg75nxwjmrrswvue8x2glk0rhvngz30du8gs5m3lgtfvjrx",
|
||||
"el1qqwhnyt8nspg0pfxdzj98er2k66ve07d297vq50trgxz2p28rjjss69w3zy3auylf60skrl0g72v8ztccxzyr5wm2aqnamnqmz",
|
||||
"AzpxZsPXCk3ct5wx9QfynivkEjHwzKELtHqWtwZ7RZKPncPtYBYn6tktDxCeMtNghMUBMw9MtD7kDcjC",
|
||||
]
|
||||
assert psbt_creator.amounts == [0.1, 0.00111211, 0.003]
|
||||
assert psbt_creator.labels == ["someLabel", "someOtherLabel", "asset test"]
|
||||
assert psbt_creator.amount_units == [
|
||||
specter_mock.default_asset,
|
||||
specter_mock.default_asset,
|
||||
"0b411a802e536a4217f46a47644c9e207db975f81e697753db2a1915d17dda79",
|
||||
]
|
||||
assert psbt_creator.kwargs == {
|
||||
"fee_rate": 64.0,
|
||||
"rbf": True,
|
||||
"rbf_edit_mode": False,
|
||||
"readonly": False,
|
||||
"selected_coins": [],
|
||||
"subtract": False,
|
||||
"subtract_from": 0,
|
||||
"assets": [
|
||||
specter_mock.default_asset,
|
||||
specter_mock.default_asset,
|
||||
"0b411a802e536a4217f46a47644c9e207db975f81e697753db2a1915d17dda79",
|
||||
],
|
||||
}
|
||||
|
||||
psbt_creator.create_psbt(wallet_mock)
|
||||
|
||||
|
||||
def test_PsbtCreator_json(caplog):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
specter_mock = MagicMock()
|
||||
# set to liquid and provide default 64-char hex asset (111....)
|
||||
specter_mock.is_liquid = True
|
||||
specter_mock.default_asset = "1" * 64
|
||||
|
||||
wallet_mock = MagicMock()
|
||||
specter_mock.chain = "liquid-regtest"
|
||||
# Let's mock the request.form which behaves like a dict but also needs getlist()
|
||||
request_json = """
|
||||
{
|
||||
"recipients" : [
|
||||
{
|
||||
"address": "el1qqgxqu7f05rm7e245nvdje28wa6fl3s0cv5lga3yahfqddgr4u8zg75nxwjmrrswvue8x2glk0rhvngz30du8gs5m3lgtfvjrx",
|
||||
"amount": 0.1,
|
||||
"unit": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"label": "someLabel"
|
||||
},
|
||||
{
|
||||
"address": "EL1QQWHNYT8NSPG0PFXDZJ98ER2K66VE07D297VQ50TRGXZ2P28RJJSS69W3ZY3AUYLF60SKRL0G72V8ZTCCXZYR5WM2AQNAMNQMZ",
|
||||
"amount": 111211,
|
||||
"unit": "sat",
|
||||
"label": "someOtherLabel"
|
||||
},
|
||||
{
|
||||
"address": "AzpxZsPXCk3ct5wx9QfynivkEjHwzKELtHqWtwZ7RZKPncPtYBYn6tktDxCeMtNghMUBMw9MtD7kDcjC",
|
||||
"amount": 0.1,
|
||||
"unit": "0b411a802e536a4217f46a47644c9e207db975f81e697753db2a1915d17dda79",
|
||||
"label": "asset test"
|
||||
}
|
||||
],
|
||||
"rbf_tx_id": "",
|
||||
"subtract_from": "0",
|
||||
"fee_rate": "64",
|
||||
"rbf": true
|
||||
}
|
||||
"""
|
||||
|
||||
psbt_creator: PsbtCreator = PsbtCreator(
|
||||
specter_mock, wallet_mock, "json", request_json=request_json
|
||||
)
|
||||
|
||||
assert psbt_creator.addresses == [
|
||||
"el1qqgxqu7f05rm7e245nvdje28wa6fl3s0cv5lga3yahfqddgr4u8zg75nxwjmrrswvue8x2glk0rhvngz30du8gs5m3lgtfvjrx",
|
||||
"el1qqwhnyt8nspg0pfxdzj98er2k66ve07d297vq50trgxz2p28rjjss69w3zy3auylf60skrl0g72v8ztccxzyr5wm2aqnamnqmz",
|
||||
"AzpxZsPXCk3ct5wx9QfynivkEjHwzKELtHqWtwZ7RZKPncPtYBYn6tktDxCeMtNghMUBMw9MtD7kDcjC",
|
||||
]
|
||||
assert psbt_creator.amounts == [0.1, 0.00111211, 0.1]
|
||||
assert psbt_creator.labels == ["someLabel", "someOtherLabel", "asset test"]
|
||||
assert psbt_creator.amount_units == [
|
||||
specter_mock.default_asset,
|
||||
specter_mock.default_asset,
|
||||
"0b411a802e536a4217f46a47644c9e207db975f81e697753db2a1915d17dda79",
|
||||
]
|
||||
assert psbt_creator.kwargs == {
|
||||
"fee_rate": 64.0,
|
||||
"rbf": True,
|
||||
"rbf_edit_mode": False,
|
||||
"readonly": False,
|
||||
"selected_coins": [],
|
||||
"subtract": False,
|
||||
"subtract_from": 0,
|
||||
"assets": [
|
||||
specter_mock.default_asset,
|
||||
specter_mock.default_asset,
|
||||
"0b411a802e536a4217f46a47644c9e207db975f81e697753db2a1915d17dda79",
|
||||
],
|
||||
}
|
||||
|
||||
psbt_creator.create_psbt(wallet_mock)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue