Skip to content

Commit 813379f

Browse files
authored
Merge pull request #16 from bitcoinerlab/descriptorsv3
Coinselect v2.0.0 with Tapscript support, descriptors v3 and bigint values
2 parents dd408c8 + 4f478ed commit 813379f

20 files changed

+63299
-49582
lines changed

README.md

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ For detailed API documentation, visit [https://bitcoinerlab.com/modules/coinsele
1515

1616
- Prevents the creation of outputs below the dust threshold, which Bitcoin nodes typically do not relay.
1717

18+
## v2.0.0 (Breaking Changes)
19+
20+
- All satoshi-denominated values are now `bigint` (`utxos[].value`, `targets[].value`, change value, `fee`, and `dustThreshold`).
21+
- `feeRate` and `dustRelayFeeRate` remain `number` values (sat/vB).
22+
- The project aligns with `@bitcoinerlab/descriptors` v3 APIs and byte-array usage (`Uint8Array` instead of `Buffer` in public-facing flows).
23+
- Taproot script-path workflows are supported through `tr(KEY,TREE)` descriptors with `taprootSpendPath: 'script'` and optional `tapLeaf`.
24+
1825
## Usage
1926

2027
To get started, first install the necessary dependencies:
@@ -44,17 +51,17 @@ const { utxos, targets, fee, vsize } = coinselect({
4451
utxos: [
4552
{
4653
output: new Output({ descriptor: 'addr(bc1qzne9qykh9j55qt8ccqamusp099spdfr49tje60)' }),
47-
value: 2000
54+
value: 2000n
4855
},
4956
{
5057
output: new Output({ descriptor: 'addr(12higDjoCCNXSA95xZMWUdPvXNmkAduhWv)' }),
51-
value: 4000
58+
value: 4000n
5259
}
5360
],
5461
targets: [
5562
{
5663
output: new Output({ descriptor: 'addr(bc1qxtuy67s0rnz7uq2cyejqx5lj8p25mh0fz2pltm)' }),
57-
value: 3000
64+
value: 3000n
5865
}
5966
],
6067
remainder: new Output({ descriptor: 'addr(bc1qwfh5mj2kms4rrf8amr66f7d5ckmpdqdzlpr082)' }),
@@ -69,21 +76,21 @@ This code produces the following result:
6976
"utxos": [
7077
{
7178
"output": {}, // The same OutputInstance as above: utxos[0].output
72-
"value": 4000
79+
"value": 4000n
7380
}
7481
],
7582
"targets": [
7683
{
7784
"output": {}, // The same OutputInstance as above: targets[0].output
78-
"value": 3000
85+
"value": 3000n
7986
},
8087
{
8188
"output": {}, // A new OutputInstance corresponding to the change
8289
// address passed in remainder
83-
"value": 705 // The final change value that you will receive
90+
"value": 705n // The final change value that you will receive
8491
}
8592
],
86-
"fee": 295, // The theoretical fee to pay (approximation before tx signing)
93+
"fee": 295n, // The theoretical fee to pay (approximation before tx signing)
8794
"vsize": 220 // The theoretical virtual size (in bytes) of the tx
8895
// (approximation before tx signing)
8996
}
@@ -106,6 +113,27 @@ const numBytes = vsize(
106113
);
107114
```
108115

116+
For tapscript/script-path spending, use explicit `tr(KEY,TREE)` descriptors:
117+
118+
```typescript
119+
import { vsize } from '@bitcoinerlab/coinselect';
120+
121+
const INTERNAL_KEY = 'a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd';
122+
const LEAF_KEY_A = '669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0';
123+
const LEAF_KEY_B = 'c6e26fdf91debe78458853f1ba08d8de71b7672a099e1be5b6204dab83c046e5';
124+
125+
const tapscriptUtxo = new Output({
126+
descriptor: `tr(${INTERNAL_KEY},{pk(${LEAF_KEY_A}),pk(${LEAF_KEY_B})})`,
127+
taprootSpendPath: 'script',
128+
tapLeaf: `pk(${LEAF_KEY_A})`
129+
});
130+
131+
const estimatedVsize = vsize(
132+
[tapscriptUtxo],
133+
[new Output({ descriptor: `tr(${INTERNAL_KEY})` })]
134+
);
135+
```
136+
109137
Having gone through the basic usage of the library, let's now focus on some essential concepts that enhance your understanding and effective utilization of the library. These include understanding *Dust*, managing *Fee Rates and Virtual Size*, and grasping the principles of *Immutability*.
110138

111139
#### Dust
@@ -127,7 +155,7 @@ For a more detailed explanation, refer to [the API documentation](https://bitcoi
127155

128156
#### Fee Rates and Virtual Size
129157

130-
The rate (`fee / vsize`) returned by `coinselect` may be higher than the specified `feeRate`. This discrepancy is due to rounding effects (target values must be integers in satoshis) and the possibility of not creating change if it falls below the dust threshold, as illustrated in the first code snippet.
158+
The rate (`fee / vsize`) returned by `coinselect` may be higher than the specified `feeRate`. This discrepancy is due to rounding effects (target values are integer satoshis represented as `bigint`) and the possibility of not creating change if it falls below the dust threshold, as illustrated in the first code snippet.
131159

132160
After signing, the final `vsize` might be lower than the initial estimate provided by `coinselect()`/`vsize()`. This is because `vsize` is calculated assuming DER-encoded signatures of 72 bytes, though they can occasionally be 71 bytes. Consequently, the final `feeRate` might exceed the pre-signing estimate. In summary, `feeRateAfterSigning >= (fee / vsize) >= feeRate`.
133161

@@ -166,11 +194,11 @@ const { utxos, targets, fee, vsize } = maxFunds({
166194
utxos: [
167195
{
168196
output: new Output({ descriptor: 'addr(bc1qzne9qykh9j55qt8ccqamusp099spdfr49tje60)' }),
169-
value: 2000
197+
value: 2000n
170198
},
171199
{
172200
output: new Output({ descriptor: 'addr(12higDjoCCNXSA95xZMWUdPvXNmkAduhWv)' }),
173-
value: 4000
201+
value: 4000n
174202
}
175203
],
176204
targets: [
@@ -259,7 +287,7 @@ const { parseKeyExpression, Output } = DescriptorsFactory(secp256k1);
259287
const Key1: string = "[73c5da0a/44'/0'/0']xpubDC5FSnBiZDMmhiuCmWAYsLwgLYrrT9rAqvTySfuCCrgsWz8wxMXUS9Tb9iVMvcRbvFcAHGkMD5Kx8koh4GquNGNTfohfk7pgjhaPCdXpoba/0/0"
260288
const Key2: string = ...; // Some other Key Expression...
261289

262-
const pubKey1: Buffer = parseKeyExpression({ keyExpression: Key1 }).pubkey;
290+
const pubKey1: Uint8Array = parseKeyExpression({ keyExpression: Key1 }).pubkey;
263291

264292
const output: OutputInstance = new Output({
265293
descriptor: `wsh(andor(pk(${Key1}),older(10),pkh(${Key2})))`,

0 commit comments

Comments
 (0)