@anywalletkit/wallet-btc 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -588,6 +588,7 @@ var DUST_LIMIT_P2TR = dustLimit("tr");
588
588
  var kindOfType = (type) => type === "p2tr" ? "tr" : "wpkh";
589
589
 
590
590
  // src/describe.ts
591
+ var import_core7 = require("@anywalletkit/core");
591
592
  var DEFAULT_MAX_FEE_RATE = 500;
592
593
  var RBF_SEQUENCE = 4294967293;
593
594
  var FINAL_SEQUENCE = 4294967294;
@@ -622,7 +623,7 @@ function describeTransaction(tx, inputValues, ctx) {
622
623
  const fee = inputSum - outputSum;
623
624
  const amount = outputs.filter((o) => o.role !== "change").reduce((s, o) => s + o.value, 0n);
624
625
  const vsize = vsizeOf(tx);
625
- const feeRate = vsize > 0 ? Math.round(Number(fee) / vsize * 100) / 100 : 0;
626
+ const feeRate = vsize > 0 ? Math.round((0, import_core7.bigintToNumber)(fee) / vsize * 100) / 100 : 0;
626
627
  if (feeRate > (ctx.maxFeeRate ?? DEFAULT_MAX_FEE_RATE)) warnings.add("high-fee-rate");
627
628
  if (amount > 0n && fee * 10n > amount) warnings.add("fee-over-10pct-of-amount");
628
629
  if (!outputs.some((o) => o.role === "change")) warnings.add("no-change");
@@ -668,7 +669,7 @@ var coinType = (network) => network === "bitcoin" ? 0 : 1;
668
669
  var purposeFor = (type) => type === "p2tr" ? 86 : 84;
669
670
 
670
671
  // src/read-only-account.ts
671
- var import_core7 = require("@anywalletkit/core");
672
+ var import_core8 = require("@anywalletkit/core");
672
673
  var toBig = (v) => typeof v === "bigint" ? v : BigInt(Math.round(Number(v)));
673
674
  var WalletAccountReadOnlyBtc = class {
674
675
  /** The address. */
@@ -700,7 +701,7 @@ var WalletAccountReadOnlyBtc = class {
700
701
  this._client = createClient(config);
701
702
  }
702
703
  requireClient(what) {
703
- if (!this._client) throw new import_core7.NetworkError(`The wallet must be connected to a client to ${what}.`);
704
+ if (!this._client) throw new import_core8.NetworkError(`The wallet must be connected to a client to ${what}.`);
704
705
  return this._client;
705
706
  }
706
707
  /** Fee-rate ceiling in sat/vB: `BtcConfig.maxFeeRate` or {@link DEFAULT_MAX_FEE_RATE}. */
@@ -742,7 +743,7 @@ var WalletAccountReadOnlyBtc = class {
742
743
  const inputWeight = this.inputWeight();
743
744
  const feeRate = await this.resolveFeeRate(spend);
744
745
  const utxos = await this.requireClient("plan a spend").listUnspent(this.address);
745
- if (utxos.length === 0) throw new import_core7.InsufficientFundsError(amount, 0n, "BTC");
746
+ if (utxos.length === 0) throw new import_core8.InsufficientFundsError(amount, 0n, "BTC");
746
747
  const own = kindOfType(this.addressType);
747
748
  const selection = selectCoins({
748
749
  utxos,
@@ -817,7 +818,7 @@ var WalletAccountReadOnlyBtc = class {
817
818
  };
818
819
 
819
820
  // src/account.ts
820
- var import_core8 = require("@anywalletkit/core");
821
+ var import_core9 = require("@anywalletkit/core");
821
822
  var import_btc_signer4 = require("@scure/btc-signer");
822
823
  var import_secp256k12 = require("@noble/curves/secp256k1.js");
823
824
  var import_base3 = require("@scure/base");
@@ -841,15 +842,15 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
841
842
  const type = config.addressType ?? "p2wpkh";
842
843
  super(addressFromPublicKey(publicKey, networkParams(config.network ?? "bitcoin"), type), config);
843
844
  this.addressType = type;
844
- this.signer = (0, import_core8.requireCurve)(signer, "secp256k1");
845
+ this.signer = (0, import_core9.requireCurve)(signer, "secp256k1");
845
846
  const needed = type === "p2tr" ? "schnorr" : "ecdsa";
846
- if (!this.signer.schemes.includes(needed)) throw new import_core8.SignerError(`A ${type} account needs a signer with the '${needed}' scheme (has: ${this.signer.schemes.join(", ")}).`);
847
+ if (!this.signer.schemes.includes(needed)) throw new import_core9.SignerError(`A ${type} account needs a signer with the '${needed}' scheme (has: ${this.signer.schemes.join(", ")}).`);
847
848
  this.publicKey = publicKey;
848
849
  this.path = path;
849
850
  }
850
851
  /** Back-compat: wrap a raw derived key in a {@link LocalSigner} (the account takes ownership; it cannot derive). */
851
852
  static fromPrivateKey(privateKey, publicKey, path, config = {}) {
852
- return new _WalletAccountBtc(import_core8.LocalSigner.fromPrivateKey("secp256k1", privateKey, publicKey, path), publicKey, path, config);
853
+ return new _WalletAccountBtc(import_core9.LocalSigner.fromPrivateKey("secp256k1", privateKey, publicKey, path), publicKey, path, config);
853
854
  }
854
855
  /** Account index for the standard m/{84|86}'/{0|1}'/0'/0/i path. */
855
856
  get index() {
@@ -857,7 +858,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
857
858
  return m ? Number(m[1]) : void 0;
858
859
  }
859
860
  live() {
860
- if (this._disposed) throw new import_core8.SignerError("This account has been disposed.");
861
+ if (this._disposed) throw new import_core9.SignerError("This account has been disposed.");
861
862
  }
862
863
  /** BIP-137 p2wpkh signature, base64 (Electrum / Bitcoin Core encoding). Not available for taproot accounts. */
863
864
  async sign(message) {
@@ -932,7 +933,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
932
933
  const delta = required - fee;
933
934
  if (change === 0n) {
934
935
  const have = plan.utxos.reduce((s, u) => s + u.value, 0n);
935
- throw new import_core8.InsufficientFundsError(plan.amount + required, have, "BTC");
936
+ throw new import_core9.InsufficientFundsError(plan.amount + required, have, "BTC");
936
937
  }
937
938
  change = change - delta;
938
939
  if (change < this.dustLimit) {
@@ -1012,19 +1013,23 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
1012
1013
  if (typeof tx === "string") return (await this.describeRaw(tx)).desc;
1013
1014
  return (await this.buildSignedTransaction(tx)).desc;
1014
1015
  }
1015
- /** Sign without broadcasting. Returns the raw transaction hex. Raw hex input is returned unchanged. */
1016
- async signTransaction(tx) {
1016
+ /** Sign without broadcasting. Returns the raw transaction hex. Raw hex input gets the checks `sendTransaction` runs (prevouts through the client) and is returned unchanged, never re-signed. */
1017
+ async signTransaction(tx, options = {}) {
1017
1018
  this.live();
1018
- if (typeof tx === "string") return tx;
1019
+ if (typeof tx === "string") {
1020
+ const { desc } = await this.describeRaw(tx);
1021
+ this.checkMax(desc.fee, (0, import_core9.feeLimit)(this._config.transactionMaxFee, options), "transaction");
1022
+ return tx;
1023
+ }
1019
1024
  const { tx: built, fee } = await this.buildSignedTransaction(tx);
1020
- this.checkMax(fee, this._config.transactionMaxFee, "transaction");
1025
+ this.checkMax(fee, (0, import_core9.feeLimit)(this._config.transactionMaxFee, options), "transaction");
1021
1026
  return built.hex;
1022
1027
  }
1023
1028
  async quoteSendTransaction(tx) {
1024
1029
  if (typeof tx === "string") return { fee: (await this.describeRaw(tx)).desc.fee };
1025
1030
  return super.quoteSendTransaction(tx);
1026
1031
  }
1027
- async sendTransaction(tx) {
1032
+ async sendTransaction(tx, options = {}) {
1028
1033
  this.live();
1029
1034
  const client = this.requireClient("send transactions");
1030
1035
  let rawHex, hash, fee;
@@ -1039,17 +1044,17 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
1039
1044
  hash = built.tx.id;
1040
1045
  fee = built.fee;
1041
1046
  }
1042
- this.checkMax(fee, this._config.transactionMaxFee, "transaction");
1047
+ this.checkMax(fee, (0, import_core9.feeLimit)(this._config.transactionMaxFee, options), "transaction");
1043
1048
  const txid = await client.broadcast(rawHex);
1044
1049
  return { hash: txid || hash, fee };
1045
1050
  }
1046
1051
  /** Native BTC only (`token` is rejected). Subject to `transferMaxFee`. */
1047
- async transfer(o) {
1052
+ async transfer(o, writeOptions = {}) {
1048
1053
  if (o.token) throw new Error("Tokens are not supported on the bitcoin blockchain.");
1049
1054
  this.live();
1050
1055
  const client = this.requireClient("transfer");
1051
1056
  const built = await this.buildSignedTransaction({ to: o.recipient, value: o.amount });
1052
- this.checkMax(built.fee, this._config.transferMaxFee, "transfer");
1057
+ this.checkMax(built.fee, (0, import_core9.feeLimit)(this._config.transferMaxFee, writeOptions), "transfer");
1053
1058
  const txid = await client.broadcast(built.tx.hex);
1054
1059
  return { hash: txid || built.tx.id, fee: built.fee };
1055
1060
  }
@@ -1065,8 +1070,8 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
1065
1070
  };
1066
1071
 
1067
1072
  // src/manager.ts
1068
- var import_core9 = require("@anywalletkit/core");
1069
- var WalletManagerBtc = class extends import_core9.WalletManager {
1073
+ var import_core10 = require("@anywalletkit/core");
1074
+ var WalletManagerBtc = class extends import_core10.WalletManager {
1070
1075
  constructor(seedOrSigner, config = {}) {
1071
1076
  super(seedOrSigner, config);
1072
1077
  }
@@ -1086,30 +1091,26 @@ var WalletManagerBtc = class extends import_core9.WalletManager {
1086
1091
  if (cached) return cached;
1087
1092
  const external = this.externalSigner();
1088
1093
  if (external) return this.fromSigner(external, full);
1089
- const signer = import_core9.LocalSigner.leafFromSeed(this.seedBytes(), "secp256k1", full);
1090
- const account = new WalletAccountBtc(signer, signer.publicKey, full, this._config);
1091
- this._accounts.set(full, account);
1092
- return account;
1094
+ return this.cachedAccount(full, () => {
1095
+ const signer = import_core10.LocalSigner.leafFromSeed(this.seedBytes(), "secp256k1", full);
1096
+ return new WalletAccountBtc(signer, signer.publicKey, full, this._config);
1097
+ });
1093
1098
  }
1094
1099
  /** Resolve `path` against an injected signer: the signer itself, or a child when it can derive there. */
1095
1100
  async fromSigner(signer, path) {
1096
1101
  this.requireSigner("secp256k1");
1097
- let leaf = signer;
1102
+ let rel;
1098
1103
  let key = "external";
1099
- if ((0, import_core9.isSeedSigner)(signer)) {
1104
+ if ((0, import_core10.isSeedSigner)(signer)) {
1100
1105
  if (path === signer.path) key = path;
1101
1106
  else if (path.startsWith(`${signer.path}/`)) {
1102
- leaf = await (signer.deriveLeaf ?? signer.derive).call(signer, path.slice(signer.path.length + 1));
1107
+ rel = path.slice(signer.path.length + 1);
1103
1108
  key = path;
1104
- } else throw new import_core9.SignerError(`The injected signer sits at ${signer.path}; it cannot provide ${path}.`);
1109
+ } else throw new import_core10.SignerError(`The injected signer sits at ${signer.path}; it cannot provide ${path}.`);
1105
1110
  } else if (path !== `${this.basePath}/0'/0/0` && path !== "external") {
1106
- throw new import_core9.SignerError(`An external signer has one account; ${path} is not derivable from it.`);
1111
+ throw new import_core10.SignerError(`An external signer has one account; ${path} is not derivable from it.`);
1107
1112
  }
1108
- const cached = this._accounts.get(key);
1109
- if (cached) return cached;
1110
- const account = new WalletAccountBtc(leaf, await leaf.getPublicKey(), key, this._config);
1111
- this._accounts.set(key, account);
1112
- return account;
1113
+ return this.signerAccount(key, signer, rel, (leaf, publicKey) => new WalletAccountBtc(leaf, publicKey, key, this._config));
1113
1114
  }
1114
1115
  /** A read-only account for any address, with this manager's config. */
1115
1116
  getReadOnlyAccount(address) {
@@ -1251,7 +1252,7 @@ function createBtcHistory(options = {}) {
1251
1252
  const pageNo = cursor?.p ?? 1;
1252
1253
  const data = await failover((c) => c.getAddressPage(address, pageNo, pageSize));
1253
1254
  let items = (data.transactions ?? []).map((tx) => mapBlockbookTx(chain, address, tx));
1254
- items.sort((a, b) => (b.block === void 0 ? 1 : a.block === void 0 ? -1 : Number(b.block - a.block)) || b.time - a.time);
1255
+ items.sort((a, b) => (b.block === void 0 ? 1 : a.block === void 0 ? -1 : b.block > a.block ? 1 : b.block < a.block ? -1 : 0) || b.time - a.time);
1255
1256
  const totalPages = data.totalPages ?? 1;
1256
1257
  let more = pageNo < totalPages;
1257
1258
  if (query.since !== void 0) {