@arkade-os/sdk 0.4.22 → 0.4.24

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