mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-31 04:33:05 -07:00
global: private xpub support part 2
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { createElement } from "../dom.js";
|
||||
import { createField } from "../form/index.js";
|
||||
import { redaction } from "../redaction/index.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} AddWalletFormSubmit
|
||||
* @property {HTMLInputElement} name
|
||||
* @property {HTMLInputElement} source
|
||||
* @property {HTMLButtonElement} submit
|
||||
* @property {HTMLElement} status
|
||||
* @property {HTMLFormElement} form
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} AddWalletFormOptions
|
||||
* @property {() => void} onCancel
|
||||
* @property {(submit: AddWalletFormSubmit) => void | Promise<void>} onSubmit
|
||||
*/
|
||||
|
||||
function createSourceInput() {
|
||||
const input = document.createElement("input");
|
||||
|
||||
input.name = "source";
|
||||
input.type = redaction.isHidden() ? "password" : "text";
|
||||
input.setAttribute("data-wallets-private-input", "");
|
||||
input.autocomplete = "off";
|
||||
input.placeholder = "xpub or descriptor...";
|
||||
input.required = true;
|
||||
input.spellcheck = false;
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AddWalletFormOptions} options
|
||||
*/
|
||||
export function createAddForm(options) {
|
||||
const form = createElement("form", "wallets__dialog-form");
|
||||
const title = document.createElement("h2");
|
||||
const name = document.createElement("input");
|
||||
const source = createSourceInput();
|
||||
const actions = createElement("div", "wallets__dialog-actions");
|
||||
const cancel = document.createElement("button");
|
||||
const submit = document.createElement("button");
|
||||
const status = createElement("p", "wallets__status");
|
||||
const fields = [
|
||||
createField("name", name),
|
||||
createField("xpub or descriptor", source),
|
||||
];
|
||||
|
||||
title.append("Watch wallet");
|
||||
name.name = "name";
|
||||
name.autocomplete = "off";
|
||||
name.placeholder = "Wallet name";
|
||||
name.required = true;
|
||||
cancel.type = "button";
|
||||
cancel.append("Cancel");
|
||||
submit.type = "submit";
|
||||
submit.append("Add");
|
||||
status.setAttribute("role", "status");
|
||||
actions.append(cancel, submit);
|
||||
form.append(
|
||||
title,
|
||||
...fields,
|
||||
actions,
|
||||
status,
|
||||
);
|
||||
cancel.addEventListener("click", options.onCancel);
|
||||
form.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
void options.onSubmit({
|
||||
name,
|
||||
source,
|
||||
submit,
|
||||
status,
|
||||
form,
|
||||
});
|
||||
});
|
||||
|
||||
return form;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { fetchWalletAddresses } from "../lookup/index.js";
|
||||
import {
|
||||
generateAddressesFromKey,
|
||||
isOutputDescriptor,
|
||||
} from "../derive/index.js";
|
||||
import { parseOutputDescriptor } from "../derive/descriptor.js";
|
||||
import { addressScripts } from "../derive/script.js";
|
||||
|
||||
const RECEIVE_PATH = /** @type {const} */ ([0]);
|
||||
|
||||
/**
|
||||
* @typedef {import("../derive/address.js").AddressScript} AddressScript
|
||||
* @typedef {import("../scan/branch.js").AddressClient} AddressClient
|
||||
* @typedef {import("../lookup/index.js").WalletAddress} WalletAddress
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {WalletAddress} address
|
||||
*/
|
||||
function hasHistory(address) {
|
||||
return address.received > 0 || address.sent > 0 || address.txCount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AddressClient} client
|
||||
* @param {string} source
|
||||
* @returns {Promise<AddressScript>}
|
||||
*/
|
||||
export async function inferAddressScript(client, source) {
|
||||
if (isOutputDescriptor(source)) {
|
||||
return parseOutputDescriptor(source).script;
|
||||
}
|
||||
|
||||
for (const { id } of addressScripts) {
|
||||
const generated = await generateAddressesFromKey(source, {
|
||||
start: 0,
|
||||
count: 1,
|
||||
script: id,
|
||||
path: RECEIVE_PATH,
|
||||
});
|
||||
const [address] = await fetchWalletAddresses(client, generated);
|
||||
|
||||
if (address && hasHistory(address)) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
return addressScripts[0].id;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { isOutputDescriptor } from "../derive/index.js";
|
||||
|
||||
const EXTENDED_PUBLIC_KEY_PATTERN =
|
||||
/\b(?:xpub|ypub|zpub|tpub|upub|vpub)[1-9A-HJ-NP-Za-km-z]{20,}\b/;
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
export function readWalletSourceText(text) {
|
||||
const value = text.trim();
|
||||
|
||||
if (isOutputDescriptor(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const match = value.match(EXTENDED_PUBLIC_KEY_PATTERN);
|
||||
|
||||
if (match) {
|
||||
return match[0];
|
||||
}
|
||||
|
||||
throw new Error("Expected an xpub or descriptor");
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
main.wallets {
|
||||
.wallets__dialog-form {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.wallets__dialog-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: end;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user