@arkade-os/sdk 0.4.21 → 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.
- package/README.md +95 -12
- package/dist/cjs/contracts/arkcontract.js +2 -1
- package/dist/cjs/contracts/contractWatcher.js +16 -18
- package/dist/cjs/identity/descriptor.js +75 -4
- package/dist/cjs/identity/hdCapableIdentity.js +2 -0
- package/dist/cjs/identity/seedIdentity.js +225 -103
- package/dist/cjs/identity/serialize.js +5 -0
- package/dist/cjs/identity/staticDescriptorProvider.js +1 -1
- package/dist/cjs/index.js +12 -3
- package/dist/cjs/providers/electrum.js +285 -79
- package/dist/cjs/providers/expoIndexer.js +1 -1
- package/dist/cjs/providers/indexer.js +2 -2
- package/dist/cjs/providers/onchain.js +9 -3
- package/dist/cjs/repositories/migrations/walletRepositoryImpl.js +6 -2
- package/dist/cjs/repositories/realm/walletRepository.js +2 -2
- package/dist/cjs/repositories/serialization.js +34 -1
- package/dist/cjs/repositories/sqlite/walletRepository.js +4 -2
- package/dist/cjs/script/address.js +2 -1
- package/dist/cjs/utils/transactionHistory.js +4 -4
- package/dist/cjs/wallet/asset-manager.js +18 -18
- package/dist/cjs/wallet/asset.js +10 -8
- package/dist/cjs/wallet/delegator.js +29 -20
- package/dist/cjs/wallet/hdDescriptorProvider.js +159 -0
- package/dist/cjs/wallet/index.js +5 -1
- package/dist/cjs/wallet/onchain.js +2 -1
- package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +4 -2
- package/dist/cjs/wallet/serviceWorker/wallet.js +5 -4
- package/dist/cjs/wallet/validation.js +2 -3
- package/dist/cjs/wallet/vtxo-manager.js +7 -5
- package/dist/cjs/wallet/wallet.js +13 -14
- package/dist/esm/contracts/arkcontract.js +2 -1
- package/dist/esm/contracts/contractWatcher.js +14 -16
- package/dist/esm/identity/descriptor.js +74 -5
- package/dist/esm/identity/hdCapableIdentity.js +1 -0
- package/dist/esm/identity/seedIdentity.js +225 -103
- package/dist/esm/identity/serialize.js +5 -0
- package/dist/esm/identity/staticDescriptorProvider.js +1 -1
- package/dist/esm/index.js +7 -4
- package/dist/esm/providers/electrum.js +284 -78
- package/dist/esm/providers/expoIndexer.js +1 -1
- package/dist/esm/providers/indexer.js +2 -2
- package/dist/esm/providers/onchain.js +9 -3
- package/dist/esm/repositories/migrations/walletRepositoryImpl.js +6 -2
- package/dist/esm/repositories/realm/walletRepository.js +3 -3
- package/dist/esm/repositories/serialization.js +27 -0
- package/dist/esm/repositories/sqlite/walletRepository.js +5 -3
- package/dist/esm/script/address.js +2 -1
- package/dist/esm/utils/transactionHistory.js +4 -4
- package/dist/esm/wallet/asset-manager.js +18 -18
- package/dist/esm/wallet/asset.js +10 -8
- package/dist/esm/wallet/delegator.js +29 -20
- package/dist/esm/wallet/hdDescriptorProvider.js +155 -0
- package/dist/esm/wallet/index.js +4 -0
- package/dist/esm/wallet/onchain.js +2 -1
- package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +4 -2
- package/dist/esm/wallet/serviceWorker/wallet.js +5 -4
- package/dist/esm/wallet/validation.js +2 -3
- package/dist/esm/wallet/vtxo-manager.js +7 -5
- package/dist/esm/wallet/wallet.js +12 -14
- package/dist/types/contracts/arkcontract.d.ts +1 -1
- package/dist/types/contracts/types.d.ts +5 -5
- package/dist/types/identity/descriptor.d.ts +26 -0
- package/dist/types/identity/descriptorProvider.d.ts +11 -4
- package/dist/types/identity/hdCapableIdentity.d.ts +44 -0
- package/dist/types/identity/index.d.ts +1 -0
- package/dist/types/identity/seedIdentity.d.ts +113 -29
- package/dist/types/identity/serialize.d.ts +12 -0
- package/dist/types/identity/staticDescriptorProvider.d.ts +1 -1
- package/dist/types/index.d.ts +6 -3
- package/dist/types/providers/electrum.d.ts +115 -15
- package/dist/types/providers/onchain.d.ts +6 -0
- package/dist/types/repositories/serialization.d.ts +26 -2
- package/dist/types/script/address.d.ts +1 -1
- package/dist/types/wallet/delegator.d.ts +8 -3
- package/dist/types/wallet/hdDescriptorProvider.d.ts +93 -0
- package/dist/types/wallet/index.d.ts +19 -10
- package/dist/types/wallet/onchain.d.ts +1 -1
- package/dist/types/wallet/serviceWorker/wallet.d.ts +1 -1
- package/dist/types/wallet/wallet.d.ts +4 -1
- package/package.json +4 -4
|
@@ -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
|
-
*
|
|
25
|
-
*
|
|
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
|
|
54
|
-
*
|
|
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
|
-
*
|
|
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
|
|
55
|
+
* // Testnet (BIP86 wildcard descriptor m/86'/1'/0'/0/*)
|
|
67
56
|
* const identity = SeedIdentity.fromSeed(seed, { isMainnet: false });
|
|
68
57
|
*
|
|
69
|
-
* // Mainnet (BIP86
|
|
58
|
+
* // Mainnet (BIP86 wildcard descriptor m/86'/0'/0'/0/*)
|
|
70
59
|
* const identity = SeedIdentity.fromSeed(seed, { isMainnet: true });
|
|
71
60
|
*
|
|
72
|
-
* //
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
151
|
+
* @param opts - Network selection or descriptor template.
|
|
118
152
|
*/
|
|
119
153
|
static fromSeed(seed, opts = {}) {
|
|
120
|
-
|
|
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(
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
158
|
-
if (signatureType === "ecdsa")
|
|
159
|
-
return signAsync(message,
|
|
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(
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
220
|
-
*
|
|
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
|
|
225
|
-
*
|
|
226
|
-
*
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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
|
|
353
|
+
* Creates a ReadonlyDescriptorIdentity from an account-descriptor
|
|
354
|
+
* *template* (must end with the BIP-32 wildcard suffix `/*)`).
|
|
255
355
|
*
|
|
256
|
-
* @param descriptor - Taproot
|
|
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
|
-
|
|
363
|
+
// Validated non-null in the constructor.
|
|
364
|
+
return this.indexZero.pubkey;
|
|
263
365
|
}
|
|
264
366
|
async compressedPublicKey() {
|
|
265
|
-
|
|
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 {
|
|
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
|
-
|
|
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 './contracts/handlers/helpers.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
|