@arkade-os/sdk 0.4.22 → 0.4.23

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 (74) hide show
  1. package/README.md +95 -12
  2. package/dist/cjs/contracts/arkcontract.js +2 -1
  3. package/dist/cjs/identity/descriptor.js +75 -4
  4. package/dist/cjs/identity/hdCapableIdentity.js +2 -0
  5. package/dist/cjs/identity/seedIdentity.js +225 -103
  6. package/dist/cjs/identity/serialize.js +5 -0
  7. package/dist/cjs/identity/staticDescriptorProvider.js +1 -1
  8. package/dist/cjs/index.js +12 -3
  9. package/dist/cjs/providers/electrum.js +285 -79
  10. package/dist/cjs/providers/expoIndexer.js +1 -1
  11. package/dist/cjs/providers/indexer.js +2 -2
  12. package/dist/cjs/providers/onchain.js +9 -3
  13. package/dist/cjs/repositories/migrations/walletRepositoryImpl.js +6 -2
  14. package/dist/cjs/repositories/realm/walletRepository.js +2 -2
  15. package/dist/cjs/repositories/serialization.js +34 -1
  16. package/dist/cjs/repositories/sqlite/walletRepository.js +4 -2
  17. package/dist/cjs/script/address.js +2 -1
  18. package/dist/cjs/utils/transactionHistory.js +4 -4
  19. package/dist/cjs/wallet/asset-manager.js +18 -18
  20. package/dist/cjs/wallet/asset.js +10 -8
  21. package/dist/cjs/wallet/delegator.js +2 -2
  22. package/dist/cjs/wallet/hdDescriptorProvider.js +159 -0
  23. package/dist/cjs/wallet/index.js +5 -1
  24. package/dist/cjs/wallet/onchain.js +2 -1
  25. package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +1 -1
  26. package/dist/cjs/wallet/serviceWorker/wallet.js +5 -4
  27. package/dist/cjs/wallet/validation.js +2 -3
  28. package/dist/cjs/wallet/wallet.js +13 -14
  29. package/dist/esm/contracts/arkcontract.js +2 -1
  30. package/dist/esm/identity/descriptor.js +74 -5
  31. package/dist/esm/identity/hdCapableIdentity.js +1 -0
  32. package/dist/esm/identity/seedIdentity.js +225 -103
  33. package/dist/esm/identity/serialize.js +5 -0
  34. package/dist/esm/identity/staticDescriptorProvider.js +1 -1
  35. package/dist/esm/index.js +7 -4
  36. package/dist/esm/providers/electrum.js +284 -78
  37. package/dist/esm/providers/expoIndexer.js +1 -1
  38. package/dist/esm/providers/indexer.js +2 -2
  39. package/dist/esm/providers/onchain.js +9 -3
  40. package/dist/esm/repositories/migrations/walletRepositoryImpl.js +6 -2
  41. package/dist/esm/repositories/realm/walletRepository.js +3 -3
  42. package/dist/esm/repositories/serialization.js +27 -0
  43. package/dist/esm/repositories/sqlite/walletRepository.js +5 -3
  44. package/dist/esm/script/address.js +2 -1
  45. package/dist/esm/utils/transactionHistory.js +4 -4
  46. package/dist/esm/wallet/asset-manager.js +18 -18
  47. package/dist/esm/wallet/asset.js +10 -8
  48. package/dist/esm/wallet/delegator.js +2 -2
  49. package/dist/esm/wallet/hdDescriptorProvider.js +155 -0
  50. package/dist/esm/wallet/index.js +4 -0
  51. package/dist/esm/wallet/onchain.js +2 -1
  52. package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +1 -1
  53. package/dist/esm/wallet/serviceWorker/wallet.js +5 -4
  54. package/dist/esm/wallet/validation.js +2 -3
  55. package/dist/esm/wallet/wallet.js +12 -14
  56. package/dist/types/contracts/arkcontract.d.ts +1 -1
  57. package/dist/types/identity/descriptor.d.ts +26 -0
  58. package/dist/types/identity/descriptorProvider.d.ts +11 -4
  59. package/dist/types/identity/hdCapableIdentity.d.ts +44 -0
  60. package/dist/types/identity/index.d.ts +1 -0
  61. package/dist/types/identity/seedIdentity.d.ts +113 -29
  62. package/dist/types/identity/serialize.d.ts +12 -0
  63. package/dist/types/identity/staticDescriptorProvider.d.ts +1 -1
  64. package/dist/types/index.d.ts +6 -3
  65. package/dist/types/providers/electrum.d.ts +115 -15
  66. package/dist/types/providers/onchain.d.ts +6 -0
  67. package/dist/types/repositories/serialization.d.ts +26 -2
  68. package/dist/types/script/address.d.ts +1 -1
  69. package/dist/types/wallet/hdDescriptorProvider.d.ts +93 -0
  70. package/dist/types/wallet/index.d.ts +19 -10
  71. package/dist/types/wallet/onchain.d.ts +1 -1
  72. package/dist/types/wallet/serviceWorker/wallet.d.ts +1 -1
  73. package/dist/types/wallet/wallet.d.ts +4 -1
  74. package/package.json +1 -1
@@ -11,6 +11,7 @@ const base_1 = require("@scure/base");
11
11
  const signingSession_1 = require("../tree/signingSession");
12
12
  const secp256k1_1 = require("@noble/secp256k1");
13
13
  const descriptors_scure_1 = require("@bitcoinerlab/descriptors-scure");
14
+ const descriptor_1 = require("./descriptor");
14
15
  const ALL_SIGHASH = Object.values(btc_signer_1.SigHash).filter((x) => typeof x === "number");
15
16
  /**
16
17
  * Secret-bearing state for seed-backed identities, held off the public
@@ -24,85 +25,117 @@ const ALL_SIGHASH = Object.values(btc_signer_1.SigHash).filter((x) => typeof x =
24
25
  */
25
26
  const seedBytes = new WeakMap();
26
27
  const mnemonicMeta = new WeakMap();
27
- // ── Helpers ──────────────────────────────────────────────────────
28
28
  /**
29
- * Detects the network from a descriptor string by checking for tpub (testnet)
30
- * vs xpub (mainnet) key prefix.
31
- * @internal
32
- */
33
- function detectNetwork(descriptor) {
34
- return descriptor.includes("tpub") ? descriptors_scure_1.networks.testnet : descriptors_scure_1.networks.bitcoin;
35
- }
36
- function hasDescriptor(opts = {}) {
37
- return "descriptor" in opts && typeof opts.descriptor === "string";
38
- }
39
- /**
40
- * Builds a BIP86 Taproot output descriptor from a seed and network flag.
41
- * @internal
42
- */
43
- function buildDescriptor(seed, isMainnet) {
44
- const network = isMainnet ? descriptors_scure_1.networks.bitcoin : descriptors_scure_1.networks.testnet;
45
- const masterNode = descriptors_scure_1.HDKey.fromMasterSeed(seed, network.bip32);
46
- return descriptors_scure_1.scriptExpressions.trBIP32({
47
- masterNode,
48
- network,
49
- account: 0,
50
- change: 0,
51
- index: 0,
52
- });
53
- }
54
- /**
55
- * Seed-based identity derived from a raw seed and an output descriptor.
29
+ * Seed-based identity derived from a raw seed and an account descriptor
30
+ * *template*.
56
31
  *
57
32
  * This is the recommended identity type for most applications. It uses
58
- * standard BIP86 (Taproot) derivation by default and stores an output
59
- * descriptor for interoperability with other wallets.
33
+ * standard BIP86 (Taproot) derivation by default; callers that need a
34
+ * different path supply the wildcard template directly.
60
35
  *
61
36
  * Prefer this (or @see MnemonicIdentity) over `SingleKey` for new
62
37
  * integrations — `SingleKey` exists for backward compatibility with
63
38
  * raw nsec-style keys.
64
39
  *
65
- * For descriptor-based signing, wrap with {@link StaticDescriptorProvider}.
40
+ * The identity holds the wildcard *template* (e.g.
41
+ * `tr([fp/86'/0'/0']xpub/0/*)`) on its public {@link descriptor}
42
+ * field. HD rotation reads it directly; consumers that need a
43
+ * concrete descriptor at a specific index materialize it themselves
44
+ * (see `HDDescriptorProvider` in the wallet layer).
45
+ *
46
+ * Exposes seed-level primitives (signing, derivation, the template)
47
+ * but is deliberately NOT a `DescriptorProvider`. Wrap it explicitly
48
+ * to get one:
49
+ * - `HDDescriptorProvider` for rotating receive addresses.
50
+ * - {@link StaticDescriptorProvider} for legacy, single-key behaviour.
51
+ *
52
+ * The split prevents a SeedIdentity from being silently used as a
53
+ * concrete descriptor source, which would defeat HD rotation without
54
+ * any compile-time signal that something was wrong.
66
55
  *
67
56
  * @example
68
57
  * ```typescript
69
58
  * const seed = mnemonicToSeedSync(mnemonic);
70
59
  *
71
- * // Testnet (BIP86 path m/86'/1'/0'/0/0)
60
+ * // Testnet (BIP86 wildcard descriptor m/86'/1'/0'/0/*)
72
61
  * const identity = SeedIdentity.fromSeed(seed, { isMainnet: false });
73
62
  *
74
- * // Mainnet (BIP86 path m/86'/0'/0'/0/0)
63
+ * // Mainnet (BIP86 wildcard descriptor m/86'/0'/0'/0/*)
75
64
  * const identity = SeedIdentity.fromSeed(seed, { isMainnet: true });
76
65
  *
77
- * // Custom descriptor
66
+ * // Caller-supplied wildcard descriptor (must end in `/*)`).
78
67
  * const identity = SeedIdentity.fromSeed(seed, { descriptor });
79
68
  * ```
80
69
  */
81
70
  class SeedIdentity {
82
- constructor(seed, descriptor) {
71
+ /**
72
+ * Constructs a SeedIdentity from a 64-byte seed and either a
73
+ * caller-supplied wildcard descriptor (`{ descriptor }`) or the
74
+ * default BIP86 path at the requested network (`{ isMainnet }`).
75
+ * Prefer the {@link fromSeed} factory for symmetry with
76
+ * {@link MnemonicIdentity.fromMnemonic}.
77
+ *
78
+ * Throws on a non-wildcard descriptor, an xpub mismatch with the
79
+ * seed, or a missing derivation path.
80
+ */
81
+ constructor(seed, opts = {}) {
83
82
  if (seed.length !== 64) {
84
83
  throw new Error("Seed must be 64 bytes");
85
84
  }
85
+ // Resolve the descriptor: caller-supplied wins; otherwise build
86
+ // the BIP86 default at the requested network via the library.
87
+ let descriptor;
88
+ let network;
89
+ if ("descriptor" in opts && typeof opts.descriptor === "string") {
90
+ descriptor = opts.descriptor;
91
+ network = (0, descriptor_1.isMainnetDescriptor)(descriptor)
92
+ ? descriptors_scure_1.networks.bitcoin
93
+ : descriptors_scure_1.networks.testnet;
94
+ }
95
+ else {
96
+ network =
97
+ (opts.isMainnet ?? true)
98
+ ? descriptors_scure_1.networks.bitcoin
99
+ : descriptors_scure_1.networks.testnet;
100
+ descriptor = descriptors_scure_1.scriptExpressions.trBIP32({
101
+ masterNode: descriptors_scure_1.HDKey.fromMasterSeed(seed, network.bip32),
102
+ network,
103
+ account: 0,
104
+ change: 0,
105
+ index: "*",
106
+ });
107
+ }
108
+ // Parse the descriptor, substituting the wildcard at index 0.
109
+ // The library raises "index passed for non-ranged descriptor"
110
+ // if the input isn't a wildcard template, which we re-wrap so
111
+ // the caller sees what they actually got wrong.
112
+ let expansion;
113
+ try {
114
+ expansion = (0, descriptors_scure_1.expand)({ descriptor, network, index: 0 });
115
+ }
116
+ catch (e) {
117
+ throw new Error(`SeedIdentity requires a wildcard descriptor template (must end in "/*)"); ${e instanceof Error ? e.message : String(e)}`);
118
+ }
119
+ const keyInfo = expansion.expansionMap?.["@0"];
86
120
  // Defensive copy: `derivedKey` and `descriptor` are computed eagerly
87
121
  // from the bytes we're about to stash, so a later mutation of the
88
122
  // caller's buffer must not drift the serialized `seed` out of sync
89
123
  // with the live identity state.
90
124
  seedBytes.set(this, new Uint8Array(seed));
91
125
  this.descriptor = descriptor;
92
- const network = detectNetwork(descriptor);
93
- // Parse and validate the descriptor using the library
94
- const expansion = (0, descriptors_scure_1.expand)({ descriptor, network });
95
- const keyInfo = expansion.expansionMap?.["@0"];
96
126
  if (!keyInfo?.originPath) {
97
127
  throw new Error("Descriptor must include a key origin path");
98
128
  }
99
- // Verify the xpub in the descriptor matches our seed
129
+ // Verify the xpub in the descriptor matches our seed (validates
130
+ // that the descriptor was generated from this seed; we don't
131
+ // need to keep the xpub around afterwards — `isOurs` re-derives
132
+ // it from `this.descriptor` on demand).
100
133
  const masterNode = descriptors_scure_1.HDKey.fromMasterSeed(seed, network.bip32);
101
134
  const accountNode = masterNode.derive(`m${keyInfo.originPath}`);
102
135
  if (accountNode.publicExtendedKey !== keyInfo.bip32?.toBase58()) {
103
136
  throw new Error("xpub mismatch: derived key does not match descriptor");
104
137
  }
105
- // Derive the private key using the full path from the descriptor
138
+ // Derive the private key for index 0 using the full path
106
139
  if (!keyInfo.path) {
107
140
  throw new Error("Descriptor must specify a full derivation path");
108
141
  }
@@ -116,16 +149,14 @@ class SeedIdentity {
116
149
  * Creates a SeedIdentity from a raw 64-byte seed.
117
150
  *
118
151
  * Pass `{ isMainnet }` for default BIP86 derivation, or
119
- * `{ descriptor }` for a custom derivation path.
152
+ * `{ descriptor }` for a caller-supplied account-descriptor
153
+ * template (the option's value must end with `/*)`).
120
154
  *
121
155
  * @param seed - 64-byte seed (typically from mnemonicToSeedSync)
122
- * @param opts - Network selection or custom descriptor.
156
+ * @param opts - Network selection or descriptor template.
123
157
  */
124
158
  static fromSeed(seed, opts = {}) {
125
- const descriptor = hasDescriptor(opts)
126
- ? opts.descriptor
127
- : buildDescriptor(seed, opts.isMainnet ?? true);
128
- return new SeedIdentity(seed, descriptor);
159
+ return new SeedIdentity(seed, opts);
129
160
  }
130
161
  async xOnlyPublicKey() {
131
162
  return (0, utils_js_1.pubSchnorr)(this.derivedKey);
@@ -134,10 +165,82 @@ class SeedIdentity {
134
165
  return (0, utils_js_1.pubECDSA)(this.derivedKey, true);
135
166
  }
136
167
  async sign(tx, inputIndexes) {
168
+ return this.signTxWithKey(tx, this.derivedKey, inputIndexes);
169
+ }
170
+ async signMessage(message, signatureType = "schnorr") {
171
+ return this.signMessageWithKey(this.derivedKey, message, signatureType);
172
+ }
173
+ signerSession() {
174
+ return signingSession_1.TreeSignerSession.random();
175
+ }
176
+ /**
177
+ * Converts to a watch-only identity that cannot sign. Carries the
178
+ * template forward, so the readonly side stays HD-capable (can
179
+ * derive descriptors at any index without seed access).
180
+ */
181
+ async toReadonly() {
182
+ return ReadonlyDescriptorIdentity.fromDescriptor(this.descriptor);
183
+ }
184
+ /**
185
+ * Returns true when `descriptor` is derived from this identity's seed.
186
+ * HD descriptors match by account xpub; bare `tr(pubkey)` descriptors
187
+ * match by raw pubkey. See {@link descriptorIsOurs}.
188
+ */
189
+ isOurs(descriptor) {
190
+ return (0, descriptor_1.descriptorIsOurs)(descriptor, this.descriptor, (0, utils_js_1.pubSchnorr)(this.derivedKey));
191
+ }
192
+ /**
193
+ * Signs each request with the key derived from its descriptor.
194
+ * Each descriptor must share this identity's seed ({@link isOurs}).
195
+ */
196
+ async signWithDescriptor(requests) {
197
+ return requests.map((request) => {
198
+ if (!this.isOurs(request.descriptor)) {
199
+ throw new Error(`Descriptor ${request.descriptor} does not belong to this identity`);
200
+ }
201
+ const key = this.derivePrivateKeyForDescriptor(request.descriptor);
202
+ return this.signTxWithKey(request.tx, key, request.inputIndexes);
203
+ });
204
+ }
205
+ /**
206
+ * Signs a message with the key derived from `descriptor`.
207
+ */
208
+ async signMessageWithDescriptor(descriptor, message, signatureType = "schnorr") {
209
+ if (!this.isOurs(descriptor)) {
210
+ throw new Error(`Descriptor ${descriptor} does not belong to this identity`);
211
+ }
212
+ const key = this.derivePrivateKeyForDescriptor(descriptor);
213
+ return this.signMessageWithKey(key, message, signatureType);
214
+ }
215
+ // ── internal helpers ─────────────────────────────────────────────
216
+ derivePrivateKeyForDescriptor(descriptor) {
217
+ const network = (0, descriptor_1.isMainnetDescriptor)(descriptor)
218
+ ? descriptors_scure_1.networks.bitcoin
219
+ : descriptors_scure_1.networks.testnet;
220
+ const expansion = (0, descriptors_scure_1.expand)({ descriptor, network });
221
+ if (expansion.isRanged) {
222
+ throw new Error("Cannot sign with a wildcard descriptor; derive a concrete index first");
223
+ }
224
+ const keyInfo = expansion.expansionMap?.["@0"];
225
+ if (!keyInfo?.path) {
226
+ throw new Error("Descriptor must specify a full derivation path for signing");
227
+ }
228
+ const seed = seedBytes.get(this);
229
+ if (!seed) {
230
+ throw new Error("Seed bytes not available for descriptor signing");
231
+ }
232
+ const masterNode = descriptors_scure_1.HDKey.fromMasterSeed(seed, network.bip32);
233
+ const node = masterNode.derive(keyInfo.path);
234
+ if (!node.privateKey) {
235
+ throw new Error("Failed to derive private key for descriptor");
236
+ }
237
+ return node.privateKey;
238
+ }
239
+ signTxWithKey(tx, key, inputIndexes) {
137
240
  const txCpy = tx.clone();
138
241
  if (!inputIndexes) {
139
242
  try {
140
- if (!txCpy.sign(this.derivedKey, ALL_SIGHASH)) {
243
+ if (!txCpy.sign(key, ALL_SIGHASH)) {
141
244
  throw new Error("Failed to sign transaction");
142
245
  }
143
246
  }
@@ -150,29 +253,20 @@ class SeedIdentity {
150
253
  throw e;
151
254
  }
152
255
  }
153
- return txCpy;
154
256
  }
155
- for (const inputIndex of inputIndexes) {
156
- if (!txCpy.signIdx(this.derivedKey, inputIndex, ALL_SIGHASH)) {
157
- throw new Error(`Failed to sign input #${inputIndex}`);
257
+ else {
258
+ for (const idx of inputIndexes) {
259
+ if (!txCpy.signIdx(key, idx, ALL_SIGHASH)) {
260
+ throw new Error(`Failed to sign input #${idx}`);
261
+ }
158
262
  }
159
263
  }
160
264
  return txCpy;
161
265
  }
162
- async signMessage(message, signatureType = "schnorr") {
163
- if (signatureType === "ecdsa") {
164
- return (0, secp256k1_1.signAsync)(message, this.derivedKey, { prehash: false });
165
- }
166
- return secp256k1_1.schnorr.signAsync(message, this.derivedKey);
167
- }
168
- signerSession() {
169
- return signingSession_1.TreeSignerSession.random();
170
- }
171
- /**
172
- * Converts to a watch-only identity that cannot sign.
173
- */
174
- async toReadonly() {
175
- return ReadonlyDescriptorIdentity.fromDescriptor(this.descriptor);
266
+ signMessageWithKey(key, message, signatureType) {
267
+ if (signatureType === "ecdsa")
268
+ return (0, secp256k1_1.signAsync)(message, key, { prehash: false });
269
+ return secp256k1_1.schnorr.signAsync(message, key);
176
270
  }
177
271
  }
178
272
  exports.SeedIdentity = SeedIdentity;
@@ -193,83 +287,108 @@ exports.SeedIdentity = SeedIdentity;
193
287
  * ```
194
288
  */
195
289
  class MnemonicIdentity extends SeedIdentity {
196
- constructor(seed, descriptor, mnemonic, passphrase) {
197
- super(seed, descriptor);
198
- mnemonicMeta.set(this, { mnemonic, passphrase });
290
+ constructor(phrase, opts) {
291
+ const { passphrase } = opts;
292
+ super((0, bip39_1.mnemonicToSeedSync)(phrase, passphrase), opts);
293
+ mnemonicMeta.set(this, { mnemonic: phrase, passphrase });
199
294
  }
200
295
  /**
201
296
  * Creates a MnemonicIdentity from a BIP39 mnemonic phrase.
202
297
  *
203
298
  * Pass `{ isMainnet }` for default BIP86 derivation, or
204
- * `{ descriptor }` for a custom derivation path.
299
+ * `{ descriptor }` for a caller-supplied account-descriptor
300
+ * template (the option's value must end with `/*)`).
205
301
  *
206
302
  * @param phrase - BIP39 mnemonic phrase (12 or 24 words)
207
- * @param opts - Network selection or custom descriptor, plus optional passphrase
303
+ * @param opts - Network selection or descriptor template, plus optional passphrase
208
304
  */
209
305
  static fromMnemonic(phrase, opts = {}) {
210
306
  if (!(0, bip39_1.validateMnemonic)(phrase, english_js_1.wordlist)) {
211
307
  throw new Error("Invalid mnemonic");
212
308
  }
213
- const passphrase = opts.passphrase;
214
- const seed = (0, bip39_1.mnemonicToSeedSync)(phrase, passphrase);
215
- const descriptor = hasDescriptor(opts)
216
- ? opts.descriptor
217
- : buildDescriptor(seed, opts.isMainnet ?? true);
218
- return new MnemonicIdentity(seed, descriptor, phrase, passphrase);
309
+ return new MnemonicIdentity(phrase, opts);
219
310
  }
220
311
  }
221
312
  exports.MnemonicIdentity = MnemonicIdentity;
222
313
  /**
223
- * Watch-only identity from an output descriptor.
314
+ * Watch-only HD identity from a descriptor *template*.
224
315
  *
225
316
  * Can derive public keys but cannot sign transactions. Use this for
226
- * watch-only wallets or when sharing identity information without
227
- * exposing private keys.
317
+ * watch-only wallets given just an xpub-based template, the readonly
318
+ * side still rotates through HD indices.
319
+ *
320
+ * Constructed from a wildcard template (e.g.
321
+ * `tr([fp/86'/0'/0']xpub.../0/*)`); the {@link descriptor} field
322
+ * holds it for HD providers to consume.
228
323
  *
229
324
  * @example
230
325
  * ```typescript
231
- * const descriptor = "tr([fingerprint/86'/0'/0']xpub.../0/0)";
232
- * const readonly = ReadonlyDescriptorIdentity.fromDescriptor(descriptor);
233
- * const pubKey = await readonly.xOnlyPublicKey();
326
+ * const ro = ReadonlyDescriptorIdentity.fromDescriptor(
327
+ * "tr([fp/86'/0'/0']xpub.../0/*)"
328
+ * );
329
+ * ro.descriptor;
330
+ * // => "tr([fp/86'/0'/0']xpub.../0/*)" — the template
234
331
  * ```
235
332
  */
236
333
  class ReadonlyDescriptorIdentity {
237
334
  constructor(descriptor) {
238
- this.descriptor = descriptor;
239
- const network = detectNetwork(descriptor);
240
- const expansion = (0, descriptors_scure_1.expand)({ descriptor, network });
335
+ const network = (0, descriptor_1.isMainnetDescriptor)(descriptor)
336
+ ? descriptors_scure_1.networks.bitcoin
337
+ : descriptors_scure_1.networks.testnet;
338
+ // Library substitutes the wildcard at index 0 and raises
339
+ // "index passed for non-ranged descriptor" if `descriptor` isn't
340
+ // actually a wildcard template — re-wrap so the caller sees
341
+ // the higher-level invariant they violated.
342
+ let expansion;
343
+ try {
344
+ expansion = (0, descriptors_scure_1.expand)({ descriptor, network, index: 0 });
345
+ }
346
+ catch (e) {
347
+ throw new Error(`ReadonlyDescriptorIdentity requires a wildcard descriptor template (must end in "/*)"); ${e instanceof Error ? e.message : String(e)}`);
348
+ }
241
349
  const keyInfo = expansion.expansionMap?.["@0"];
242
350
  if (!keyInfo?.pubkey) {
243
351
  throw new Error("Failed to derive public key from descriptor");
244
352
  }
245
- // For taproot, the library returns 32-byte x-only pubkey
246
- this.xOnlyPubKey = keyInfo.pubkey;
247
- // Get 33-byte compressed key with correct parity from the bip32 node
248
- if (keyInfo.bip32 && keyInfo.keyPath) {
249
- // Strip leading "/" — the library's derivePath prepends "m/" itself
250
- const relPath = keyInfo.keyPath.replace(/^\//, "");
251
- this.compressedPubKey = keyInfo.bip32.derivePath(relPath).publicKey;
252
- }
253
- else if (keyInfo.bip32) {
254
- this.compressedPubKey = keyInfo.bip32.publicKey;
255
- }
256
- else {
353
+ if (!keyInfo.bip32) {
257
354
  throw new Error("Cannot determine compressed public key parity from descriptor");
258
355
  }
356
+ this.descriptor = descriptor;
357
+ this.indexZero = keyInfo;
259
358
  }
260
359
  /**
261
- * Creates a ReadonlyDescriptorIdentity from an output descriptor.
360
+ * Creates a ReadonlyDescriptorIdentity from an account-descriptor
361
+ * *template* (must end with the BIP-32 wildcard suffix `/*)`).
262
362
  *
263
- * @param descriptor - Taproot descriptor: tr([fingerprint/path']xpub.../child/path)
363
+ * @param descriptor - Wildcard-suffixed Taproot template
364
+ * (`tr([fp/path']xpub.../child/*)`).
264
365
  */
265
366
  static fromDescriptor(descriptor) {
266
367
  return new ReadonlyDescriptorIdentity(descriptor);
267
368
  }
268
369
  async xOnlyPublicKey() {
269
- return this.xOnlyPubKey;
370
+ // Validated non-null in the constructor.
371
+ return this.indexZero.pubkey;
270
372
  }
271
373
  async compressedPublicKey() {
272
- return this.compressedPubKey;
374
+ const { bip32, keyPath } = this.indexZero;
375
+ // bip32 validated non-null in the constructor; derivePath
376
+ // returns a fresh node so this is a read of the index-0
377
+ // compressed pubkey, not a mutation of the stored one.
378
+ if (keyPath) {
379
+ // Strip leading "/" — the library's derivePath prepends "m/" itself
380
+ return bip32.derivePath(keyPath.replace(/^\//, "")).publicKey;
381
+ }
382
+ return bip32.publicKey;
383
+ }
384
+ /**
385
+ * Returns true when `descriptor` derives from this identity's xpub.
386
+ * HD descriptors match by account xpub; bare `tr(pubkey)` descriptors
387
+ * fall back to comparing against the index-0 x-only pubkey. See
388
+ * {@link descriptorIsOurs}.
389
+ */
390
+ isOurs(descriptor) {
391
+ return (0, descriptor_1.descriptorIsOurs)(descriptor, this.descriptor, this.indexZero.pubkey);
273
392
  }
274
393
  }
275
394
  exports.ReadonlyDescriptorIdentity = ReadonlyDescriptorIdentity;
@@ -335,5 +454,8 @@ function serializeSeedOwnedSigningIdentity(identity) {
335
454
  * @internal
336
455
  */
337
456
  function serializeSeedOwnedReadonlyIdentity(identity) {
338
- return { type: "readonly-descriptor", descriptor: identity.descriptor };
457
+ return {
458
+ type: "readonly-descriptor",
459
+ descriptor: identity.descriptor,
460
+ };
339
461
  }
@@ -61,6 +61,11 @@ async function serializeReadonlyIdentity(identity) {
61
61
  * The return type is the union of signing and readonly; use
62
62
  * {@link isSigningSerialized} on the envelope before hydration if the caller
63
63
  * needs to know which side it ends up on.
64
+ *
65
+ * Envelopes store the wildcard template directly (see
66
+ * `serializeSeedOwnedSigningIdentity` / `serializeSeedOwnedReadonlyIdentity`),
67
+ * so the `descriptor` field is passed straight through to the
68
+ * template-only factories.
64
69
  */
65
70
  function hydrateIdentity(s) {
66
71
  switch (s.type) {
@@ -18,7 +18,7 @@ class StaticDescriptorProvider {
18
18
  const pubKey = await identity.xOnlyPublicKey();
19
19
  return new StaticDescriptorProvider(identity, base_1.hex.encode(pubKey));
20
20
  }
21
- getSigningDescriptor() {
21
+ async getNextSigningDescriptor() {
22
22
  return this.descriptor;
23
23
  }
24
24
  isOurs(descriptor) {
package/dist/cjs/index.js CHANGED
@@ -36,9 +36,9 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  };
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = exports.TapTreeCoder = exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.DEFAULT_MESSAGE_TIMEOUTS = exports.ServiceWorkerReadonlyWallet = exports.ServiceWorkerWallet = exports.ServiceWorkerTimeoutError = exports.MessageBusNotInitializedError = exports.MESSAGE_BUS_NOT_INITIALIZED = exports.DelegatorNotConfiguredError = exports.ReadonlyWalletError = exports.WalletNotInitializedError = exports.WalletMessageHandler = exports.MessageBus = exports.setupServiceWorker = exports.SettlementEventType = exports.ChainTxType = exports.IndexerTxType = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DelegateVtxo = exports.DefaultVtxo = exports.ArkAddress = exports.RestIndexerProvider = exports.RestArkProvider = exports.WsElectrumChainSource = exports.ElectrumOnchainProvider = exports.EsploraProvider = exports.ESPLORA_URL = exports.RestDelegatorProvider = exports.DelegatorManagerImpl = exports.VtxoManager = exports.Ramps = exports.OnchainWallet = exports.isBatchSignable = exports.ReadonlyDescriptorIdentity = exports.MnemonicIdentity = exports.SeedIdentity = exports.ReadonlySingleKey = exports.SingleKey = exports.ReadonlyWallet = exports.Wallet = exports.asset = void 0;
40
- exports.DelegateContractHandler = exports.DefaultContractHandler = exports.contractHandlers = exports.ContractWatcher = exports.ContractManager = exports.getSequence = exports.isExpired = exports.isSubdust = exports.isSpendable = exports.isRecoverable = exports.buildForfeitTx = exports.validateConnectorsTxGraph = exports.validateVtxoTxGraph = exports.Batch = exports.maybeArkError = exports.ArkError = exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.BIP322 = exports.Intent = exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.rollbackMigration = exports.getMigrationStatus = exports.requiresMigration = exports.migrateWalletRepository = exports.MIGRATION_KEY = exports.InMemoryContractRepository = exports.InMemoryWalletRepository = exports.IndexedDBContractRepository = exports.IndexedDBWalletRepository = exports.openDatabase = exports.closeDatabase = exports.networks = exports.ArkNote = exports.isValidArkAddress = exports.isVtxoExpiringSoon = exports.combineTapscriptSigs = exports.hasBoardingTxExpired = exports.waitForIncomingFunds = exports.verifyTapscriptSignatures = exports.buildOffchainTx = exports.ConditionWitness = exports.VtxoTaprootTree = exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.getArkPsbtFields = exports.setArkPsbtField = void 0;
41
- exports.isArkContract = exports.contractFromArkContractWithAddress = exports.contractFromArkContract = exports.decodeArkContract = exports.encodeArkContract = exports.VHTLCContractHandler = void 0;
39
+ exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.DEFAULT_MESSAGE_TIMEOUTS = exports.ServiceWorkerReadonlyWallet = exports.ServiceWorkerWallet = exports.ServiceWorkerTimeoutError = exports.MessageBusNotInitializedError = exports.MESSAGE_BUS_NOT_INITIALIZED = exports.DelegatorNotConfiguredError = exports.ReadonlyWalletError = exports.WalletNotInitializedError = exports.WalletMessageHandler = exports.MessageBus = exports.setupServiceWorker = exports.SettlementEventType = exports.ChainTxType = exports.IndexerTxType = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DelegateVtxo = exports.DefaultVtxo = exports.ArkAddress = exports.RestIndexerProvider = exports.RestArkProvider = exports.WsElectrumChainSource = exports.ElectrumOnchainProvider = exports.ELECTRUM_TCP_HOST = exports.ELECTRUM_WS_URL = exports.EsploraProvider = exports.ESPLORA_URL = exports.RestDelegatorProvider = exports.DelegatorManagerImpl = exports.HDDescriptorProvider = exports.VtxoManager = exports.Ramps = exports.OnchainWallet = exports.isBatchSignable = exports.ReadonlyDescriptorIdentity = exports.MnemonicIdentity = exports.SeedIdentity = exports.ReadonlySingleKey = exports.SingleKey = exports.ReadonlyWallet = exports.Wallet = exports.asset = void 0;
40
+ exports.isExpired = exports.isSubdust = exports.isSpendable = exports.isRecoverable = exports.buildForfeitTx = exports.validateConnectorsTxGraph = exports.validateVtxoTxGraph = exports.Batch = exports.maybeArkError = exports.ArkError = exports.sequenceToTimelock = exports.timelockToSequence = exports.TxWeightEstimator = exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.BIP322 = exports.Intent = exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.rollbackMigration = exports.getMigrationStatus = exports.requiresMigration = exports.migrateWalletRepository = exports.MIGRATION_KEY = exports.InMemoryContractRepository = exports.InMemoryWalletRepository = exports.IndexedDBContractRepository = exports.IndexedDBWalletRepository = exports.openDatabase = exports.closeDatabase = exports.networks = exports.ArkNote = exports.isValidArkAddress = exports.isVtxoExpiringSoon = exports.combineTapscriptSigs = exports.hasBoardingTxExpired = exports.waitForIncomingFunds = exports.verifyTapscriptSignatures = exports.buildOffchainTx = exports.ConditionWitness = exports.VtxoTaprootTree = exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.getArkPsbtFields = exports.setArkPsbtField = exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = exports.TapTreeCoder = void 0;
41
+ exports.isArkContract = exports.contractFromArkContractWithAddress = exports.contractFromArkContract = exports.decodeArkContract = exports.encodeArkContract = exports.VHTLCContractHandler = exports.DelegateContractHandler = exports.DefaultContractHandler = exports.contractHandlers = exports.ContractWatcher = exports.ContractManager = exports.getSequence = void 0;
42
42
  const transaction_1 = require("./utils/transaction");
43
43
  Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return transaction_1.Transaction; } });
44
44
  const singleKey_1 = require("./identity/singleKey");
@@ -80,6 +80,8 @@ const txTree_1 = require("./tree/txTree");
80
80
  Object.defineProperty(exports, "TxTree", { enumerable: true, get: function () { return txTree_1.TxTree; } });
81
81
  const ramps_1 = require("./wallet/ramps");
82
82
  Object.defineProperty(exports, "Ramps", { enumerable: true, get: function () { return ramps_1.Ramps; } });
83
+ const hdDescriptorProvider_1 = require("./wallet/hdDescriptorProvider");
84
+ Object.defineProperty(exports, "HDDescriptorProvider", { enumerable: true, get: function () { return hdDescriptorProvider_1.HDDescriptorProvider; } });
83
85
  const vtxo_manager_1 = require("./wallet/vtxo-manager");
84
86
  Object.defineProperty(exports, "isVtxoExpiringSoon", { enumerable: true, get: function () { return vtxo_manager_1.isVtxoExpiringSoon; } });
85
87
  Object.defineProperty(exports, "VtxoManager", { enumerable: true, get: function () { return vtxo_manager_1.VtxoManager; } });
@@ -95,6 +97,8 @@ const onchain_2 = require("./providers/onchain");
95
97
  Object.defineProperty(exports, "ESPLORA_URL", { enumerable: true, get: function () { return onchain_2.ESPLORA_URL; } });
96
98
  Object.defineProperty(exports, "EsploraProvider", { enumerable: true, get: function () { return onchain_2.EsploraProvider; } });
97
99
  const electrum_1 = require("./providers/electrum");
100
+ Object.defineProperty(exports, "ELECTRUM_TCP_HOST", { enumerable: true, get: function () { return electrum_1.ELECTRUM_TCP_HOST; } });
101
+ Object.defineProperty(exports, "ELECTRUM_WS_URL", { enumerable: true, get: function () { return electrum_1.ELECTRUM_WS_URL; } });
98
102
  Object.defineProperty(exports, "ElectrumOnchainProvider", { enumerable: true, get: function () { return electrum_1.ElectrumOnchainProvider; } });
99
103
  Object.defineProperty(exports, "WsElectrumChainSource", { enumerable: true, get: function () { return electrum_1.WsElectrumChainSource; } });
100
104
  const ark_1 = require("./providers/ark");
@@ -138,6 +142,8 @@ Object.defineProperty(exports, "IndexerTxType", { enumerable: true, get: functio
138
142
  Object.defineProperty(exports, "ChainTxType", { enumerable: true, get: function () { return indexer_1.ChainTxType; } });
139
143
  const anchor_1 = require("./utils/anchor");
140
144
  Object.defineProperty(exports, "P2A", { enumerable: true, get: function () { return anchor_1.P2A; } });
145
+ const txSizeEstimator_1 = require("./utils/txSizeEstimator");
146
+ Object.defineProperty(exports, "TxWeightEstimator", { enumerable: true, get: function () { return txSizeEstimator_1.TxWeightEstimator; } });
141
147
  const unroll_1 = require("./wallet/unroll");
142
148
  Object.defineProperty(exports, "Unroll", { enumerable: true, get: function () { return unroll_1.Unroll; } });
143
149
  const errors_1 = require("./providers/errors");
@@ -177,6 +183,9 @@ Object.defineProperty(exports, "decodeArkContract", { enumerable: true, get: fun
177
183
  Object.defineProperty(exports, "contractFromArkContract", { enumerable: true, get: function () { return contracts_1.contractFromArkContract; } });
178
184
  Object.defineProperty(exports, "contractFromArkContractWithAddress", { enumerable: true, get: function () { return contracts_1.contractFromArkContractWithAddress; } });
179
185
  Object.defineProperty(exports, "isArkContract", { enumerable: true, get: function () { return contracts_1.isArkContract; } });
186
+ const helpers_1 = require("./contracts/handlers/helpers");
187
+ Object.defineProperty(exports, "timelockToSequence", { enumerable: true, get: function () { return helpers_1.timelockToSequence; } });
188
+ Object.defineProperty(exports, "sequenceToTimelock", { enumerable: true, get: function () { return helpers_1.sequenceToTimelock; } });
180
189
  const manager_1 = require("./repositories/indexedDB/manager");
181
190
  Object.defineProperty(exports, "closeDatabase", { enumerable: true, get: function () { return manager_1.closeDatabase; } });
182
191
  Object.defineProperty(exports, "openDatabase", { enumerable: true, get: function () { return manager_1.openDatabase; } });