@leather.io/bitcoin 0.19.29 → 0.19.31

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.
Files changed (47) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/CHANGELOG.md +41 -0
  3. package/dist/index.d.ts +206 -157
  4. package/dist/index.js +341 -205
  5. package/dist/index.js.map +1 -1
  6. package/package.json +11 -11
  7. package/src/bip322/bip322-utils.ts +1 -1
  8. package/src/bip322/sign-message-bip322-bitcoinjs.ts +8 -3
  9. package/src/bip322/sign-message-bip322.spec.ts +13 -13
  10. package/src/coin-selection/calculate-max-spend.spec.ts +19 -17
  11. package/src/coin-selection/calculate-max-spend.ts +26 -16
  12. package/src/coin-selection/coin-selection.spec.ts +29 -26
  13. package/src/coin-selection/coin-selection.ts +1 -1
  14. package/src/coin-selection/coin-selection.utils.spec.ts +2 -1
  15. package/src/coin-selection/coin-selection.utils.ts +5 -8
  16. package/src/fees/bitcoin-fees.spec.ts +7 -10
  17. package/src/index.ts +21 -9
  18. package/src/mocks/mocks.ts +39 -0
  19. package/src/{p2tr-address-gen.spec.ts → payments/p2tr-address-gen.spec.ts} +1 -1
  20. package/src/{p2tr-address-gen.ts → payments/p2tr-address-gen.ts} +2 -2
  21. package/src/{p2wpkh-address-gen.ts → payments/p2wpkh-address-gen.ts} +2 -2
  22. package/src/psbt/psbt-details.ts +3 -3
  23. package/src/psbt/psbt-inputs.ts +9 -6
  24. package/src/psbt/psbt-outputs.ts +10 -7
  25. package/src/psbt/psbt-totals.ts +3 -3
  26. package/src/{bitcoin-signer.ts → signer/bitcoin-signer.ts} +6 -5
  27. package/src/transactions/generate-unsigned-transaction.spec.ts +1 -1
  28. package/src/transactions/generate-unsigned-transaction.ts +3 -3
  29. package/src/{bitcoin.network.ts → utils/bitcoin.network.ts} +2 -0
  30. package/src/{bitcoin.utils.spec.ts → utils/bitcoin.utils.spec.ts} +19 -14
  31. package/src/{bitcoin.utils.ts → utils/bitcoin.utils.ts} +19 -13
  32. package/src/{lookup-derivation-by-address.spec.ts → utils/lookup-derivation-by-address.spec.ts} +11 -6
  33. package/src/{lookup-derivation-by-address.ts → utils/lookup-derivation-by-address.ts} +4 -3
  34. package/src/validation/address-validation.spec.ts +396 -0
  35. package/src/validation/address-validation.ts +28 -0
  36. package/src/validation/amount-validation.spec.ts +39 -0
  37. package/src/validation/amount-validation.ts +31 -0
  38. package/src/validation/bitcoin-address.ts +23 -0
  39. package/src/{bitcoin-error.ts → validation/bitcoin-error.ts} +4 -2
  40. package/src/validation/transaction-validation.spec.ts +60 -0
  41. package/src/validation/transaction-validation.ts +46 -0
  42. /package/src/{btc-size-fee-estimator.spec.ts → fees/btc-size-fee-estimator.spec.ts} +0 -0
  43. /package/src/{btc-size-fee-estimator.ts → fees/btc-size-fee-estimator.ts} +0 -0
  44. /package/src/{p2wpkh-address-gen.spec.ts → payments/p2wpkh-address-gen.spec.ts} +0 -0
  45. /package/src/{p2wsh-p2sh-address-gen.spec.ts → payments/p2wsh-p2sh-address-gen.spec.ts} +0 -0
  46. /package/src/{p2wsh-p2sh-address-gen.ts → payments/p2wsh-p2sh-address-gen.ts} +0 -0
  47. /package/src/{bitcoin-signer.spec.ts → signer/bitcoin-signer.spec.ts} +0 -0
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import { ECPairFactory } from "ecpair";
7
7
  import { encode } from "varuint-bitcoin";
8
8
  import { isString } from "@leather.io/utils";
9
9
 
10
- // src/bitcoin.utils.ts
10
+ // src/utils/bitcoin.utils.ts
11
11
  import { hexToBytes } from "@noble/hashes/utils";
12
12
  import { HDKey } from "@scure/bip32";
13
13
  import { mnemonicToSeedSync } from "@scure/bip39";
@@ -19,7 +19,11 @@ import {
19
19
  } from "@leather.io/crypto";
20
20
  import { defaultWalletKeyId, isDefined, whenNetwork } from "@leather.io/utils";
21
21
 
22
- // src/bitcoin.network.ts
22
+ // src/payments/p2tr-address-gen.ts
23
+ import * as btc from "@scure/btc-signer";
24
+ import { DerivationPathDepth } from "@leather.io/crypto";
25
+
26
+ // src/utils/bitcoin.network.ts
23
27
  import * as bitcoinJs from "bitcoinjs-lib";
24
28
  var bitcoinMainnet = {
25
29
  bech32: "bc",
@@ -60,9 +64,7 @@ function getBitcoinJsLibNetworkConfigByMode(network) {
60
64
  return bitcoinJsLibNetworks[network];
61
65
  }
62
66
 
63
- // src/p2tr-address-gen.ts
64
- import * as btc from "@scure/btc-signer";
65
- import { DerivationPathDepth } from "@leather.io/crypto";
67
+ // src/payments/p2tr-address-gen.ts
66
68
  function makeTaprootAccountDerivationPath(network, accountIndex) {
67
69
  return `m/86'/${getBitcoinCoinTypeIndexByNetwork(network)}'/${accountIndex}'`;
68
70
  }
@@ -108,7 +110,7 @@ function deriveTaprootReceiveAddressIndexZero({
108
110
  };
109
111
  }
110
112
 
111
- // src/p2wpkh-address-gen.ts
113
+ // src/payments/p2wpkh-address-gen.ts
112
114
  import * as btc2 from "@scure/btc-signer";
113
115
  import { DerivationPathDepth as DerivationPathDepth2 } from "@leather.io/crypto";
114
116
  function makeNativeSegwitAccountDerivationPath(network, accountIndex) {
@@ -146,7 +148,7 @@ function deriveNativeSegwitReceiveAddressIndexZero({
146
148
  };
147
149
  }
148
150
 
149
- // src/bitcoin.utils.ts
151
+ // src/utils/bitcoin.utils.ts
150
152
  function initBitcoinAccount(derivationPath, policy) {
151
153
  const xpub = extractExtendedPublicKeyFromPolicy(policy);
152
154
  const network = inferNetworkFromPath(derivationPath);
@@ -485,7 +487,7 @@ import validate, { AddressType, getAddressInfo } from "bitcoin-address-validatio
485
487
  import { BTC_P2WPKH_DUST_AMOUNT } from "@leather.io/constants";
486
488
  import { sumNumbers } from "@leather.io/utils";
487
489
 
488
- // src/btc-size-fee-estimator.ts
490
+ // src/fees/btc-size-fee-estimator.ts
489
491
  import BigNumber from "bignumber.js";
490
492
  import { assertUnreachable } from "@leather.io/utils";
491
493
  var BtcSizeFeeEstimator = class {
@@ -757,11 +759,7 @@ function getSizeInfo(payload) {
757
759
  });
758
760
  return sizeInfo;
759
761
  }
760
- function getSpendableAmount({
761
- utxos,
762
- feeRate,
763
- recipients
764
- }) {
762
+ function getSpendableAmount({ utxos, feeRate, recipients }) {
765
763
  const balance = utxos.map((utxo) => Number(utxo.value)).reduce((prevVal, curVal) => prevVal + curVal, 0);
766
764
  const size = getSizeInfo({
767
765
  inputLength: utxos.length,
@@ -796,33 +794,34 @@ function filterUneconomicalUtxos({
796
794
  }
797
795
 
798
796
  // src/coin-selection/calculate-max-spend.ts
799
- function calculateMaxBitcoinSpend({
800
- address: address2,
797
+ function calculateMaxSpend({
798
+ recipient,
801
799
  utxos,
802
800
  feeRate,
803
- fetchedFeeRates
801
+ feeRates
804
802
  }) {
805
- if (!utxos.length || !fetchedFeeRates)
803
+ if (!utxos.length || !feeRates)
806
804
  return {
807
805
  spendAllFee: 0,
808
806
  amount: createMoney(0, "BTC"),
809
- spendableBitcoin: new BigNumber3(0)
807
+ spendableBtc: new BigNumber3(0)
810
808
  };
811
- const currentFeeRate = feeRate ?? fetchedFeeRates.halfHourFee.toNumber();
809
+ const currentFeeRate = feeRate ?? feeRates.halfHourFee.toNumber();
812
810
  const filteredUtxos = filterUneconomicalUtxos({
813
811
  utxos,
814
812
  feeRate: currentFeeRate,
815
- recipients: [{ address: address2, amount: createMoney(0, "BTC") }]
813
+ recipients: [{ address: recipient, amount: createMoney(0, "BTC") }]
816
814
  });
817
815
  const { spendableAmount, fee } = getSpendableAmount({
818
816
  utxos: filteredUtxos,
819
817
  feeRate: currentFeeRate,
820
- recipients: [{ address: address2, amount: createMoney(0, "BTC") }]
818
+ recipients: [{ address: recipient, amount: createMoney(0, "BTC") }],
819
+ isSendMax: true
821
820
  });
822
821
  return {
823
822
  spendAllFee: fee,
824
823
  amount: createMoney(spendableAmount, "BTC"),
825
- spendableBitcoin: satToBtc(spendableAmount)
824
+ spendableBtc: satToBtc(spendableAmount)
826
825
  };
827
826
  }
828
827
 
@@ -832,7 +831,7 @@ import { validate as validate2 } from "bitcoin-address-validation";
832
831
  import { BTC_P2WPKH_DUST_AMOUNT as BTC_P2WPKH_DUST_AMOUNT2 } from "@leather.io/constants";
833
832
  import { createMoney as createMoney2, sumMoney } from "@leather.io/utils";
834
833
 
835
- // src/bitcoin-error.ts
834
+ // src/validation/bitcoin-error.ts
836
835
  var BitcoinError = class extends Error {
837
836
  message;
838
837
  constructor(message) {
@@ -964,133 +963,74 @@ function getBitcoinFees({ feeRates, isSendingMax, recipients, utxos }) {
964
963
  };
965
964
  }
966
965
 
967
- // src/transactions/generate-unsigned-transaction.ts
968
- import { hexToBytes as hexToBytes3 } from "@noble/hashes/utils";
969
- import * as btc4 from "@scure/btc-signer";
970
- function generateBitcoinUnsignedTransactionNativeSegwit({
971
- feeRate,
972
- isSendingMax,
973
- payerAddress,
974
- payerPublicKey,
975
- bip32Derivation,
976
- network,
977
- recipients,
978
- utxos
979
- }) {
980
- const determineUtxosArgs = { feeRate, recipients, utxos };
981
- const { inputs, outputs, fee } = isSendingMax ? determineUtxosForSpendAll(determineUtxosArgs) : determineUtxosForSpend(determineUtxosArgs);
982
- if (!inputs.length) throw new BitcoinError("NoInputsToSign");
983
- if (!outputs.length) throw new BitcoinError("NoOutputsToSign");
984
- const tx = new btc4.Transaction();
985
- const p2wpkh3 = btc4.p2wpkh(hexToBytes3(payerPublicKey), network);
986
- for (const input of inputs) {
987
- tx.addInput({
988
- txid: input.txid,
989
- index: input.vout,
990
- sequence: 0,
991
- bip32Derivation,
992
- witnessUtxo: {
993
- // script = 0014 + pubKeyHash
994
- script: p2wpkh3.script,
995
- amount: BigInt(input.value)
996
- }
997
- });
966
+ // src/validation/address-validation.ts
967
+ import { Network, validate as validate3 } from "bitcoin-address-validation";
968
+ import { isEmptyString, isUndefined } from "@leather.io/utils";
969
+ function getBitcoinAddressNetworkType(network) {
970
+ if (network === "signet") return Network.testnet;
971
+ return network;
972
+ }
973
+ function isValidBitcoinAddress(address2) {
974
+ if (isUndefined(address2) || isEmptyString(address2)) {
975
+ return false;
998
976
  }
999
- outputs.forEach((output) => {
1000
- if (!output.address) {
1001
- tx.addOutputAddress(payerAddress, BigInt(output.value), network);
1002
- return;
1003
- }
1004
- tx.addOutputAddress(output.address, BigInt(output.value), network);
1005
- });
1006
- return { tx, hex: tx.hex, psbt: tx.toPSBT(), inputs, fee };
977
+ return validate3(address2);
1007
978
  }
1008
-
1009
- // src/bitcoin-signer.ts
1010
- import { HARDENED_OFFSET } from "@scure/bip32";
1011
- import * as btc5 from "@scure/btc-signer";
1012
- import {
1013
- DerivationPathDepth as DerivationPathDepth4,
1014
- appendAddressIndexToPath,
1015
- decomposeDescriptor,
1016
- deriveKeychainFromXpub,
1017
- keyOriginToDerivationPath
1018
- } from "@leather.io/crypto";
1019
- import { hexToNumber, toHexString } from "@leather.io/utils";
1020
- function initializeBitcoinAccountKeychainFromDescriptor(descriptor) {
1021
- const { fingerprint, keyOrigin } = decomposeDescriptor(descriptor);
1022
- return {
1023
- descriptor,
1024
- xpub: extractExtendedPublicKeyFromPolicy(descriptor),
1025
- keyOrigin,
1026
- masterKeyFingerprint: fingerprint,
1027
- keychain: deriveKeychainFromXpub(extractExtendedPublicKeyFromPolicy(descriptor))
1028
- };
1029
- }
1030
- function deriveBitcoinPayerFromAccount(descriptor, network) {
1031
- const { fingerprint, keyOrigin } = decomposeDescriptor(descriptor);
1032
- const accountKeychain = deriveKeychainFromXpub(extractExtendedPublicKeyFromPolicy(descriptor));
1033
- const paymentType = inferPaymentTypeFromPath(keyOrigin);
1034
- if (accountKeychain.depth !== DerivationPathDepth4.Account)
1035
- throw new Error("Keychain passed is not an account");
1036
- return ({ receive = 0, addressIndex }) => {
1037
- const childKeychain = accountKeychain.deriveChild(receive).deriveChild(addressIndex);
1038
- const derivePayerFromAccount = whenSupportedPaymentType(paymentType)({
1039
- p2tr: getTaprootPaymentFromAddressIndex,
1040
- p2wpkh: getNativeSegwitPaymentFromAddressIndex
1041
- });
1042
- const payment = derivePayerFromAccount(childKeychain, network);
1043
- return {
1044
- keyOrigin: appendAddressIndexToPath(keyOrigin, 0),
1045
- masterKeyFingerprint: fingerprint,
1046
- paymentType,
1047
- network,
1048
- payment,
1049
- get address() {
1050
- if (!payment.address) throw new Error("Payment address could not be derived");
1051
- return payment.address;
1052
- },
1053
- get publicKey() {
1054
- if (!childKeychain.publicKey) throw new Error("Public key could not be derived");
1055
- return childKeychain.publicKey;
1056
- }
1057
- };
1058
- };
1059
- }
1060
- function payerToBip32Derivation(args) {
1061
- return [
1062
- args.publicKey,
1063
- {
1064
- fingerprint: hexToNumber(args.masterKeyFingerprint),
1065
- path: btc5.bip32Path(keyOriginToDerivationPath(args.keyOrigin))
1066
- }
1067
- ];
1068
- }
1069
- function payerToTapBip32Derivation(args) {
1070
- return [
1071
- // TODO: @kyranjamie to default to schnoor when TR so conversion isn't
1072
- // necessary here?
1073
- ecdsaPublicKeyToSchnorr(args.publicKey),
1074
- {
1075
- hashes: [],
1076
- der: {
1077
- fingerprint: hexToNumber(args.masterKeyFingerprint),
1078
- path: btc5.bip32Path(keyOriginToDerivationPath(args.keyOrigin))
1079
- }
1080
- }
1081
- ];
979
+ function isValidBitcoinNetworkAddress(address2, network) {
980
+ if (!isValidBitcoinAddress(address2) || !network) {
981
+ return false;
982
+ }
983
+ return validate3(address2, getBitcoinAddressNetworkType(network));
1082
984
  }
1083
- function serializeKeyOrigin({ fingerprint, path }) {
1084
- const values = path.map((num) => num >= HARDENED_OFFSET ? num - HARDENED_OFFSET + "'" : num);
1085
- return `${toHexString(fingerprint)}/${values.join("/")}`;
985
+
986
+ // src/validation/bitcoin-address.ts
987
+ function isBitcoinAddress(value) {
988
+ try {
989
+ isValidBitcoinAddress(value);
990
+ return true;
991
+ } catch {
992
+ return false;
993
+ }
1086
994
  }
1087
- function extractRequiredKeyOrigins(derivation) {
1088
- return derivation.map(
1089
- ([_pubkey, path]) => serializeKeyOrigin("hashes" in path ? path.der : path)
1090
- );
995
+ function createBitcoinAddress(value) {
996
+ if (!isBitcoinAddress(value)) {
997
+ throw new BitcoinError("InvalidAddress");
998
+ }
999
+ return value;
1091
1000
  }
1092
1001
 
1093
- // src/p2wsh-p2sh-address-gen.ts
1002
+ // src/mocks/mocks.ts
1003
+ var TEST_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS = createBitcoinAddress(
1004
+ "bc1q530dz4h80kwlzywlhx2qn0k6vdtftd93c499yq"
1005
+ );
1006
+ var TEST_ACCOUNT_1_TAPROOT_ADDRESS = createBitcoinAddress(
1007
+ "bc1putuzj9lyfcm8fef9jpy85nmh33cxuq9u6wyuk536t9kemdk37yjqmkc0pg"
1008
+ );
1009
+ var TEST_ACCOUNT_2_TAPROOT_ADDRESS = createBitcoinAddress(
1010
+ "bc1pmk2sacpfyy4v5phl8tq6eggu4e8laztep7fsgkkx0nc6m9vydjesaw0g2r"
1011
+ );
1012
+ var TEST_TESNET_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS = createBitcoinAddress(
1013
+ "tb1q4qgnjewwun2llgken94zqjrx5kpqqycaz5522d"
1014
+ );
1015
+ var TEST_TESTNET_ACCOUNT_2_BTC_ADDRESS = createBitcoinAddress(
1016
+ "tb1qr8me8t9gu9g6fu926ry5v44yp0wyljrespjtnz"
1017
+ );
1018
+ var TEST_TESTNET_ACCOUNT_2_TAPROOT_ADDRESS = createBitcoinAddress(
1019
+ "tb1pve00jmp43whpqj2wpcxtc7m8wqhz0azq689y4r7h8tmj8ltaj87qj2nj6w"
1020
+ );
1021
+ var recipientAddress = createBitcoinAddress("tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m");
1022
+ var legacyAddress = createBitcoinAddress("15PyZveQd28E2SHZu2ugkWZBp6iER41vXj");
1023
+ var segwitAddress = createBitcoinAddress("33SVjoCHJovrXxjDKLFSXo1h3t5KgkPzfH");
1024
+ var taprootAddress = createBitcoinAddress(
1025
+ "tb1parwmj7533de3k2fw2kntyqacspvhm67qnjcmpqnnpfvzu05l69nsczdywd"
1026
+ );
1027
+ var invalidAddress = "whoop-de-da-boop-da-de-not-a-bitcoin-address";
1028
+ var inValidCharactersAddress = createBitcoinAddress(
1029
+ "tb1&*%wmj7533de3k2fw2kntyqacspvhm67qnjcmpqnnpfvzu05l69nsczdywd"
1030
+ );
1031
+ var inValidLengthAddress = createBitcoinAddress("tb1parwmj7533de3k2fw2kntyqacspvhm67wd");
1032
+
1033
+ // src/payments/p2wsh-p2sh-address-gen.ts
1094
1034
  import { ripemd160 } from "@noble/hashes/ripemd160";
1095
1035
  import { sha256 as sha2562 } from "@noble/hashes/sha256";
1096
1036
  import { base58check } from "@scure/base";
@@ -1132,54 +1072,6 @@ function publicKeyToPayToScriptHashAddress(publicKey, network) {
1132
1072
  return makePayToScriptHashAddress(addrBytes, network);
1133
1073
  }
1134
1074
 
1135
- // src/lookup-derivation-by-address.ts
1136
- import { HARDENED_OFFSET as HARDENED_OFFSET2, HDKey as HDKey3 } from "@scure/bip32";
1137
- import { createCounter } from "@leather.io/utils";
1138
- function lookupDerivationByAddress(args) {
1139
- const { taprootXpub, nativeSegwitXpub, iterationLimit } = args;
1140
- const taprootKeychain = HDKey3.fromExtendedKey(taprootXpub);
1141
- const nativeSegwitKeychain = HDKey3.fromExtendedKey(nativeSegwitXpub);
1142
- return (address2) => {
1143
- const network = inferNetworkFromAddress(address2);
1144
- const paymentType = inferPaymentTypeFromAddress(address2);
1145
- const accountIndex = whenSupportedPaymentType(paymentType)({
1146
- p2tr: taprootKeychain.index - HARDENED_OFFSET2,
1147
- p2wpkh: nativeSegwitKeychain.index - HARDENED_OFFSET2
1148
- });
1149
- function getTaprootAddressAtIndex(index) {
1150
- return getTaprootAddress({ index, keychain: taprootKeychain, network });
1151
- }
1152
- function getNativeSegwitAddressAtIndex(index) {
1153
- return getNativeSegwitAddress({ index, keychain: nativeSegwitKeychain, network });
1154
- }
1155
- const paymentFn = whenSupportedPaymentType(paymentType)({
1156
- p2tr: getTaprootAddressAtIndex,
1157
- p2wpkh: getNativeSegwitAddressAtIndex
1158
- });
1159
- const derivationPathFn = whenSupportedPaymentType(paymentType)({
1160
- p2tr: makeTaprootAddressIndexDerivationPath,
1161
- p2wpkh: makeNativeSegwitAddressIndexDerivationPath
1162
- });
1163
- const count = createCounter();
1164
- const t0 = performance.now();
1165
- while (count.getValue() <= iterationLimit) {
1166
- const currentIndex = count.getValue();
1167
- const addressToCheck = paymentFn(currentIndex);
1168
- if (addressToCheck !== address2) {
1169
- count.increment();
1170
- continue;
1171
- }
1172
- const t1 = performance.now();
1173
- return {
1174
- status: "success",
1175
- duration: t1 - t0,
1176
- path: derivationPathFn(network, accountIndex, currentIndex)
1177
- };
1178
- }
1179
- return { status: "failure" };
1180
- };
1181
- }
1182
-
1183
1075
  // src/psbt/psbt-totals.ts
1184
1076
  import { createMoney as createMoney3, sumNumbers as sumNumbers2 } from "@leather.io/utils";
1185
1077
  function calculateAddressInputsTotal(addresses, inputs) {
@@ -1219,7 +1111,7 @@ function getPsbtTotals({ psbtAddresses, parsedInputs, parsedOutputs }) {
1219
1111
 
1220
1112
  // src/psbt/psbt-inputs.ts
1221
1113
  import { bytesToHex } from "@noble/hashes/utils";
1222
- import { isDefined as isDefined2, isUndefined } from "@leather.io/utils";
1114
+ import { isDefined as isDefined2, isUndefined as isUndefined2 } from "@leather.io/utils";
1223
1115
  function getParsedInputs({
1224
1116
  inputs,
1225
1117
  indexesToSign,
@@ -1227,15 +1119,16 @@ function getParsedInputs({
1227
1119
  psbtAddresses
1228
1120
  }) {
1229
1121
  const bitcoinNetwork = getBtcSignerLibNetworkConfigByMode(networkMode);
1230
- const signAll = isUndefined(indexesToSign);
1122
+ const signAll = isUndefined2(indexesToSign);
1231
1123
  const psbtInputs = inputs.map((input, i) => {
1232
1124
  const inputAddress = isDefined2(input.index) ? getBitcoinInputAddress(input, bitcoinNetwork) : "";
1233
- const isCurrentAddress = psbtAddresses.includes(inputAddress);
1125
+ const bitcoinAddress = createBitcoinAddress(inputAddress);
1126
+ const isCurrentAddress = psbtAddresses.includes(bitcoinAddress);
1234
1127
  const canChange = isCurrentAddress && !(!input.sighashType || input.sighashType === 0 || input.sighashType === 1);
1235
1128
  const toSignAll = isCurrentAddress && signAll;
1236
1129
  const toSignIndex = isCurrentAddress && !signAll && indexesToSign.includes(i);
1237
1130
  return {
1238
- address: inputAddress,
1131
+ address: bitcoinAddress,
1239
1132
  index: input.index,
1240
1133
  bip32Derivation: input.bip32Derivation,
1241
1134
  tapBip32Derivation: input.tapBip32Derivation,
@@ -1251,7 +1144,7 @@ function getParsedInputs({
1251
1144
  }
1252
1145
 
1253
1146
  // src/psbt/psbt-outputs.ts
1254
- import { isDefined as isDefined3, isUndefined as isUndefined2 } from "@leather.io/utils";
1147
+ import { isDefined as isDefined3, isUndefined as isUndefined3 } from "@leather.io/utils";
1255
1148
  function getParsedOutputs({
1256
1149
  isPsbtMutable,
1257
1150
  outputs,
@@ -1260,10 +1153,12 @@ function getParsedOutputs({
1260
1153
  }) {
1261
1154
  const bitcoinNetwork = getBtcSignerLibNetworkConfigByMode(networkMode);
1262
1155
  return outputs.map((output) => {
1263
- if (isUndefined2(output.script)) {
1156
+ if (isUndefined3(output.script)) {
1264
1157
  return;
1265
1158
  }
1266
- const outputAddress = getAddressFromOutScript(output.script, bitcoinNetwork);
1159
+ const outputAddress = createBitcoinAddress(
1160
+ getAddressFromOutScript(output.script, bitcoinNetwork)
1161
+ );
1267
1162
  const isCurrentAddress = psbtAddresses.includes(outputAddress);
1268
1163
  return {
1269
1164
  address: outputAddress,
@@ -1278,16 +1173,16 @@ function getParsedOutputs({
1278
1173
  import { createMoney as createMoney4, subtractMoney } from "@leather.io/utils";
1279
1174
 
1280
1175
  // src/psbt/utils.ts
1281
- import { hexToBytes as hexToBytes4 } from "@noble/hashes/utils";
1282
- import * as btc6 from "@scure/btc-signer";
1176
+ import { hexToBytes as hexToBytes3 } from "@noble/hashes/utils";
1177
+ import * as btc4 from "@scure/btc-signer";
1283
1178
  import { RawPSBTV0, RawPSBTV2 } from "@scure/btc-signer/psbt";
1284
1179
  import { isString as isString2 } from "@leather.io/utils";
1285
1180
  function getPsbtAsTransaction(psbt) {
1286
- const bytes = isString2(psbt) ? hexToBytes4(psbt) : psbt;
1287
- return btc6.Transaction.fromPSBT(bytes);
1181
+ const bytes = isString2(psbt) ? hexToBytes3(psbt) : psbt;
1182
+ return btc4.Transaction.fromPSBT(bytes);
1288
1183
  }
1289
1184
  function getRawPsbt(psbt) {
1290
- const bytes = isString2(psbt) ? hexToBytes4(psbt) : psbt;
1185
+ const bytes = isString2(psbt) ? hexToBytes3(psbt) : psbt;
1291
1186
  try {
1292
1187
  return RawPSBTV0.decode(bytes);
1293
1188
  } catch (e1) {
@@ -1342,14 +1237,240 @@ function getPsbtDetails({
1342
1237
  psbtOutputs: parsedOutputs
1343
1238
  };
1344
1239
  }
1240
+
1241
+ // src/signer/bitcoin-signer.ts
1242
+ import { HARDENED_OFFSET } from "@scure/bip32";
1243
+ import * as btc5 from "@scure/btc-signer";
1244
+ import {
1245
+ DerivationPathDepth as DerivationPathDepth4,
1246
+ appendAddressIndexToPath,
1247
+ decomposeDescriptor,
1248
+ deriveKeychainFromXpub,
1249
+ keyOriginToDerivationPath
1250
+ } from "@leather.io/crypto";
1251
+ import { hexToNumber, toHexString } from "@leather.io/utils";
1252
+ function initializeBitcoinAccountKeychainFromDescriptor(descriptor) {
1253
+ const { fingerprint, keyOrigin } = decomposeDescriptor(descriptor);
1254
+ return {
1255
+ descriptor,
1256
+ xpub: extractExtendedPublicKeyFromPolicy(descriptor),
1257
+ keyOrigin,
1258
+ masterKeyFingerprint: fingerprint,
1259
+ keychain: deriveKeychainFromXpub(extractExtendedPublicKeyFromPolicy(descriptor))
1260
+ };
1261
+ }
1262
+ function deriveBitcoinPayerFromAccount(descriptor, network) {
1263
+ const { fingerprint, keyOrigin } = decomposeDescriptor(descriptor);
1264
+ const accountKeychain = deriveKeychainFromXpub(extractExtendedPublicKeyFromPolicy(descriptor));
1265
+ const paymentType = inferPaymentTypeFromPath(keyOrigin);
1266
+ if (accountKeychain.depth !== DerivationPathDepth4.Account)
1267
+ throw new Error("Keychain passed is not an account");
1268
+ return ({ receive = 0, addressIndex }) => {
1269
+ const childKeychain = accountKeychain.deriveChild(receive).deriveChild(addressIndex);
1270
+ const derivePayerFromAccount = whenSupportedPaymentType(paymentType)({
1271
+ p2tr: getTaprootPaymentFromAddressIndex,
1272
+ p2wpkh: getNativeSegwitPaymentFromAddressIndex
1273
+ });
1274
+ const payment = derivePayerFromAccount(childKeychain, network);
1275
+ return {
1276
+ keyOrigin: appendAddressIndexToPath(keyOrigin, 0),
1277
+ masterKeyFingerprint: fingerprint,
1278
+ paymentType,
1279
+ network,
1280
+ payment,
1281
+ get address() {
1282
+ if (!payment.address) throw new Error("Payment address could not be derived");
1283
+ return payment.address;
1284
+ },
1285
+ get publicKey() {
1286
+ if (!childKeychain.publicKey) throw new Error("Public key could not be derived");
1287
+ return childKeychain.publicKey;
1288
+ }
1289
+ };
1290
+ };
1291
+ }
1292
+ function payerToBip32Derivation(args) {
1293
+ return [
1294
+ args.publicKey,
1295
+ {
1296
+ fingerprint: hexToNumber(args.masterKeyFingerprint),
1297
+ path: btc5.bip32Path(keyOriginToDerivationPath(args.keyOrigin))
1298
+ }
1299
+ ];
1300
+ }
1301
+ function payerToTapBip32Derivation(args) {
1302
+ return [
1303
+ // TODO: @kyranjamie to default to schnoor when TR so conversion isn't
1304
+ // necessary here?
1305
+ ecdsaPublicKeyToSchnorr(args.publicKey),
1306
+ {
1307
+ hashes: [],
1308
+ der: {
1309
+ fingerprint: hexToNumber(args.masterKeyFingerprint),
1310
+ path: btc5.bip32Path(keyOriginToDerivationPath(args.keyOrigin))
1311
+ }
1312
+ }
1313
+ ];
1314
+ }
1315
+ function serializeKeyOrigin({ fingerprint, path }) {
1316
+ const values = path.map((num) => num >= HARDENED_OFFSET ? num - HARDENED_OFFSET + "'" : num);
1317
+ return `${toHexString(fingerprint)}/${values.join("/")}`;
1318
+ }
1319
+ function extractRequiredKeyOrigins(derivation) {
1320
+ return derivation.map(
1321
+ ([_pubkey, path]) => serializeKeyOrigin("hashes" in path ? path.der : path)
1322
+ );
1323
+ }
1324
+
1325
+ // src/transactions/generate-unsigned-transaction.ts
1326
+ import { hexToBytes as hexToBytes4 } from "@noble/hashes/utils";
1327
+ import * as btc6 from "@scure/btc-signer";
1328
+ function generateBitcoinUnsignedTransactionNativeSegwit({
1329
+ feeRate,
1330
+ isSendingMax,
1331
+ payerAddress,
1332
+ payerPublicKey,
1333
+ bip32Derivation,
1334
+ network,
1335
+ recipients,
1336
+ utxos
1337
+ }) {
1338
+ const determineUtxosArgs = { feeRate, recipients, utxos };
1339
+ const { inputs, outputs, fee } = isSendingMax ? determineUtxosForSpendAll(determineUtxosArgs) : determineUtxosForSpend(determineUtxosArgs);
1340
+ if (!inputs.length) throw new BitcoinError("NoInputsToSign");
1341
+ if (!outputs.length) throw new BitcoinError("NoOutputsToSign");
1342
+ const tx = new btc6.Transaction();
1343
+ const p2wpkh3 = btc6.p2wpkh(hexToBytes4(payerPublicKey), network);
1344
+ for (const input of inputs) {
1345
+ tx.addInput({
1346
+ txid: input.txid,
1347
+ index: input.vout,
1348
+ sequence: 0,
1349
+ bip32Derivation,
1350
+ witnessUtxo: {
1351
+ // script = 0014 + pubKeyHash
1352
+ script: p2wpkh3.script,
1353
+ amount: BigInt(input.value)
1354
+ }
1355
+ });
1356
+ }
1357
+ outputs.forEach((output) => {
1358
+ if (!output.address) {
1359
+ tx.addOutputAddress(payerAddress, BigInt(output.value), network);
1360
+ return;
1361
+ }
1362
+ tx.addOutputAddress(output.address, BigInt(output.value), network);
1363
+ });
1364
+ return { tx, hex: tx.hex, psbt: tx.toPSBT(), inputs, fee };
1365
+ }
1366
+
1367
+ // src/validation/amount-validation.ts
1368
+ import BigNumber5 from "bignumber.js";
1369
+ import { btcToSat } from "@leather.io/utils";
1370
+ var minSpendAmountInSats = 546;
1371
+ function isBtcBalanceSufficient({
1372
+ amount: { amount },
1373
+ spendableBtc
1374
+ }) {
1375
+ if (!spendableBtc) return false;
1376
+ const desiredSpend = new BigNumber5(amount);
1377
+ if (desiredSpend.isGreaterThan(spendableBtc)) return false;
1378
+ return true;
1379
+ }
1380
+ function isBtcMinimumSpend({ amount: { amount } }) {
1381
+ if (!amount) return false;
1382
+ const desiredSpend = btcToSat(amount);
1383
+ if (desiredSpend.isLessThan(minSpendAmountInSats)) return false;
1384
+ return true;
1385
+ }
1386
+
1387
+ // src/validation/transaction-validation.ts
1388
+ function isValidBitcoinTransaction({
1389
+ amount,
1390
+ payer,
1391
+ recipient,
1392
+ network,
1393
+ utxos,
1394
+ feeRate,
1395
+ feeRates
1396
+ }) {
1397
+ if (!isValidBitcoinAddress(payer) || !isValidBitcoinAddress(recipient)) {
1398
+ throw new BitcoinError("InvalidAddress");
1399
+ }
1400
+ if (!isValidBitcoinNetworkAddress(payer, network) || !isValidBitcoinNetworkAddress(recipient, network)) {
1401
+ throw new BitcoinError("InvalidNetworkAddress");
1402
+ }
1403
+ if (!isBtcMinimumSpend({ amount })) {
1404
+ throw new BitcoinError("InsufficientAmount");
1405
+ }
1406
+ const { spendableBtc } = calculateMaxSpend({ recipient, utxos, feeRate, feeRates });
1407
+ if (!isBtcBalanceSufficient({ amount, spendableBtc })) {
1408
+ throw new BitcoinError("InsufficientFunds");
1409
+ }
1410
+ }
1411
+
1412
+ // src/utils/lookup-derivation-by-address.ts
1413
+ import { HARDENED_OFFSET as HARDENED_OFFSET2, HDKey as HDKey3 } from "@scure/bip32";
1414
+ import { createCounter } from "@leather.io/utils";
1415
+ function lookupDerivationByAddress(args) {
1416
+ const { taprootXpub, nativeSegwitXpub, iterationLimit } = args;
1417
+ const taprootKeychain = HDKey3.fromExtendedKey(taprootXpub);
1418
+ const nativeSegwitKeychain = HDKey3.fromExtendedKey(nativeSegwitXpub);
1419
+ return (address2) => {
1420
+ const network = inferNetworkFromAddress(address2);
1421
+ const paymentType = inferPaymentTypeFromAddress(address2);
1422
+ const accountIndex = whenSupportedPaymentType(paymentType)({
1423
+ p2tr: taprootKeychain.index - HARDENED_OFFSET2,
1424
+ p2wpkh: nativeSegwitKeychain.index - HARDENED_OFFSET2
1425
+ });
1426
+ function getTaprootAddressAtIndex(index) {
1427
+ return getTaprootAddress({ index, keychain: taprootKeychain, network });
1428
+ }
1429
+ function getNativeSegwitAddressAtIndex(index) {
1430
+ return getNativeSegwitAddress({ index, keychain: nativeSegwitKeychain, network });
1431
+ }
1432
+ const paymentFn = whenSupportedPaymentType(paymentType)({
1433
+ p2tr: getTaprootAddressAtIndex,
1434
+ p2wpkh: getNativeSegwitAddressAtIndex
1435
+ });
1436
+ const derivationPathFn = whenSupportedPaymentType(paymentType)({
1437
+ p2tr: makeTaprootAddressIndexDerivationPath,
1438
+ p2wpkh: makeNativeSegwitAddressIndexDerivationPath
1439
+ });
1440
+ const count = createCounter();
1441
+ const t0 = performance.now();
1442
+ while (count.getValue() <= iterationLimit) {
1443
+ const currentIndex = count.getValue();
1444
+ const addressToCheck = paymentFn(currentIndex);
1445
+ if (addressToCheck !== address2) {
1446
+ count.increment();
1447
+ continue;
1448
+ }
1449
+ const t1 = performance.now();
1450
+ return {
1451
+ status: "success",
1452
+ duration: t1 - t0,
1453
+ path: derivationPathFn(network, accountIndex, currentIndex)
1454
+ };
1455
+ }
1456
+ return { status: "failure" };
1457
+ };
1458
+ }
1345
1459
  export {
1346
1460
  BitcoinError,
1461
+ TEST_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS,
1462
+ TEST_ACCOUNT_1_TAPROOT_ADDRESS,
1463
+ TEST_ACCOUNT_2_TAPROOT_ADDRESS,
1464
+ TEST_TESNET_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS,
1465
+ TEST_TESTNET_ACCOUNT_2_BTC_ADDRESS,
1466
+ TEST_TESTNET_ACCOUNT_2_TAPROOT_ADDRESS,
1347
1467
  bip322TransactionToSignValues,
1348
1468
  bitcoinNetworkModeToCoreNetworkMode,
1349
1469
  bitcoinNetworkToCoreNetworkMap,
1350
1470
  btcSignerLibPaymentTypeToPaymentTypeMap,
1351
- calculateMaxBitcoinSpend,
1471
+ calculateMaxSpend,
1352
1472
  coinTypeMap,
1473
+ createBitcoinAddress,
1353
1474
  createNativeSegwitBitcoinJsSigner,
1354
1475
  createTaprootBitcoinJsSigner,
1355
1476
  createToSpendTx,
@@ -1376,6 +1497,7 @@ export {
1376
1497
  filterUneconomicalUtxos,
1377
1498
  generateBitcoinUnsignedTransactionNativeSegwit,
1378
1499
  getAddressFromOutScript,
1500
+ getBitcoinAddressNetworkType,
1379
1501
  getBitcoinCoinTypeIndexByNetwork,
1380
1502
  getBitcoinFees,
1381
1503
  getBitcoinInputAddress,
@@ -1406,14 +1528,24 @@ export {
1406
1528
  getTaprootPaymentFromAddressIndex,
1407
1529
  getUtxoTotal,
1408
1530
  hashBip322Message,
1531
+ inValidCharactersAddress,
1532
+ inValidLengthAddress,
1409
1533
  inferNetworkFromAddress,
1410
1534
  inferNetworkFromPath,
1411
1535
  inferPaymentTypeFromAddress,
1412
1536
  inferPaymentTypeFromPath,
1413
1537
  initBitcoinAccount,
1414
1538
  initializeBitcoinAccountKeychainFromDescriptor,
1539
+ invalidAddress,
1540
+ isBitcoinAddress,
1541
+ isBtcBalanceSufficient,
1542
+ isBtcMinimumSpend,
1415
1543
  isBtcSignerLibPaymentType,
1416
1544
  isSupportedMessageSigningPaymentType,
1545
+ isValidBitcoinAddress,
1546
+ isValidBitcoinNetworkAddress,
1547
+ isValidBitcoinTransaction,
1548
+ legacyAddress,
1417
1549
  lookUpLedgerKeysByPath,
1418
1550
  lookupDerivationByAddress,
1419
1551
  makeNativeSegwitAccountDerivationPath,
@@ -1423,6 +1555,7 @@ export {
1423
1555
  makePayToScriptHashKeyHash,
1424
1556
  makeTaprootAccountDerivationPath,
1425
1557
  makeTaprootAddressIndexDerivationPath,
1558
+ minSpendAmountInSats,
1426
1559
  mnemonicToRootNode,
1427
1560
  parseKnownPaymentType,
1428
1561
  payToScriptHashTestnetPrefix,
@@ -1430,8 +1563,11 @@ export {
1430
1563
  payerToTapBip32Derivation,
1431
1564
  paymentTypeMap,
1432
1565
  publicKeyToPayToScriptHashAddress,
1566
+ recipientAddress,
1567
+ segwitAddress,
1433
1568
  serializeKeyOrigin,
1434
1569
  signBip322MessageSimple,
1570
+ taprootAddress,
1435
1571
  toXOnly,
1436
1572
  tweakSigner,
1437
1573
  whenBitcoinNetwork,