@arkade-os/sdk 0.4.19 → 0.4.21
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/dist/cjs/contracts/contractWatcher.js +33 -3
- package/dist/cjs/contracts/handlers/default.js +10 -3
- package/dist/cjs/contracts/handlers/helpers.js +47 -5
- package/dist/cjs/contracts/handlers/vhtlc.js +4 -2
- package/dist/cjs/identity/descriptor.js +98 -0
- package/dist/cjs/identity/descriptorProvider.js +2 -0
- package/dist/cjs/identity/index.js +15 -1
- package/dist/cjs/identity/seedIdentity.js +91 -6
- package/dist/cjs/identity/serialize.js +166 -0
- package/dist/cjs/identity/staticDescriptorProvider.js +65 -0
- package/dist/cjs/index.js +6 -3
- package/dist/cjs/providers/ark.js +71 -46
- package/dist/cjs/providers/electrum.js +663 -0
- package/dist/cjs/providers/indexer.js +60 -43
- package/dist/cjs/providers/utils.js +62 -12
- package/dist/cjs/wallet/ramps.js +1 -1
- package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +10 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +137 -91
- package/dist/cjs/wallet/vtxo-manager.js +56 -8
- package/dist/cjs/wallet/wallet.js +130 -156
- package/dist/cjs/worker/messageBus.js +200 -56
- package/dist/esm/contracts/contractWatcher.js +33 -3
- package/dist/esm/contracts/handlers/default.js +10 -3
- package/dist/esm/contracts/handlers/helpers.js +47 -5
- package/dist/esm/contracts/handlers/vhtlc.js +4 -2
- package/dist/esm/identity/descriptor.js +92 -0
- package/dist/esm/identity/descriptorProvider.js +1 -0
- package/dist/esm/identity/index.js +6 -1
- package/dist/esm/identity/seedIdentity.js +89 -6
- package/dist/esm/identity/serialize.js +159 -0
- package/dist/esm/identity/staticDescriptorProvider.js +61 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/providers/ark.js +72 -47
- package/dist/esm/providers/electrum.js +658 -0
- package/dist/esm/providers/indexer.js +61 -44
- package/dist/esm/providers/utils.js +61 -12
- package/dist/esm/wallet/ramps.js +1 -1
- package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +10 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +137 -91
- package/dist/esm/wallet/vtxo-manager.js +56 -8
- package/dist/esm/wallet/wallet.js +130 -156
- package/dist/esm/worker/messageBus.js +201 -57
- package/dist/types/contracts/contractWatcher.d.ts +3 -0
- package/dist/types/contracts/handlers/default.d.ts +1 -1
- package/dist/types/contracts/handlers/helpers.d.ts +1 -1
- package/dist/types/contracts/types.d.ts +11 -3
- package/dist/types/identity/descriptor.d.ts +35 -0
- package/dist/types/identity/descriptorProvider.d.ts +28 -0
- package/dist/types/identity/index.d.ts +7 -1
- package/dist/types/identity/seedIdentity.d.ts +41 -4
- package/dist/types/identity/serialize.d.ts +84 -0
- package/dist/types/identity/staticDescriptorProvider.d.ts +18 -0
- package/dist/types/index.d.ts +4 -2
- package/dist/types/providers/electrum.d.ts +212 -0
- package/dist/types/providers/utils.d.ts +10 -5
- package/dist/types/wallet/serviceWorker/wallet-message-handler.d.ts +11 -2
- package/dist/types/wallet/serviceWorker/wallet.d.ts +27 -10
- package/dist/types/wallet/vtxo-manager.d.ts +2 -0
- package/dist/types/wallet/wallet.d.ts +7 -6
- package/dist/types/worker/messageBus.d.ts +68 -8
- package/package.json +3 -2
|
@@ -4,4 +4,9 @@ export function isBatchSignable(identity) {
|
|
|
4
4
|
typeof identity.signMultiple === "function");
|
|
5
5
|
}
|
|
6
6
|
export * from './singleKey.js';
|
|
7
|
-
export
|
|
7
|
+
export { SeedIdentity, MnemonicIdentity, ReadonlyDescriptorIdentity, } from './seedIdentity.js';
|
|
8
|
+
export * from './serialize.js';
|
|
9
|
+
// Descriptor utilities
|
|
10
|
+
export { isDescriptor, normalizeToDescriptor, extractPubKey, parseHDDescriptor, } from './descriptor.js';
|
|
11
|
+
// Static descriptor provider (wrapper for legacy Identity)
|
|
12
|
+
export { StaticDescriptorProvider } from './staticDescriptorProvider.js';
|
|
@@ -2,10 +2,24 @@ import { validateMnemonic, mnemonicToSeedSync } from "@scure/bip39";
|
|
|
2
2
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
3
3
|
import { pubECDSA, pubSchnorr } from "@scure/btc-signer/utils.js";
|
|
4
4
|
import { SigHash } from "@scure/btc-signer";
|
|
5
|
+
import { hex } from "@scure/base";
|
|
5
6
|
import { TreeSignerSession } from '../tree/signingSession.js';
|
|
6
7
|
import { schnorr, signAsync } from "@noble/secp256k1";
|
|
7
8
|
import { HDKey, expand, networks, scriptExpressions, } from "@bitcoinerlab/descriptors-scure";
|
|
8
9
|
const ALL_SIGHASH = Object.values(SigHash).filter((x) => typeof x === "number");
|
|
10
|
+
/**
|
|
11
|
+
* Secret-bearing state for seed-backed identities, held off the public
|
|
12
|
+
* instance surface. Accessed only by the SDK-internal serializer helpers
|
|
13
|
+
* below; application code cannot read these via ordinary field access.
|
|
14
|
+
*
|
|
15
|
+
* Using a module-private WeakMap (rather than `private` / `protected`)
|
|
16
|
+
* matters because TypeScript visibility is a compile-time boundary only:
|
|
17
|
+
* JavaScript consumers could still read public fields. A WeakMap removes
|
|
18
|
+
* that enumeration path entirely.
|
|
19
|
+
*/
|
|
20
|
+
const seedBytes = new WeakMap();
|
|
21
|
+
const mnemonicMeta = new WeakMap();
|
|
22
|
+
// ── Helpers ──────────────────────────────────────────────────────
|
|
9
23
|
/**
|
|
10
24
|
* Detects the network from a descriptor string by checking for tpub (testnet)
|
|
11
25
|
* vs xpub (mainnet) key prefix.
|
|
@@ -37,14 +51,14 @@ function buildDescriptor(seed, isMainnet) {
|
|
|
37
51
|
*
|
|
38
52
|
* This is the recommended identity type for most applications. It uses
|
|
39
53
|
* standard BIP86 (Taproot) derivation by default and stores an output
|
|
40
|
-
* descriptor for interoperability with other wallets.
|
|
41
|
-
* format is HD-ready, allowing future support for multiple addresses
|
|
42
|
-
* and change derivation.
|
|
54
|
+
* descriptor for interoperability with other wallets.
|
|
43
55
|
*
|
|
44
56
|
* Prefer this (or @see MnemonicIdentity) over `SingleKey` for new
|
|
45
57
|
* integrations — `SingleKey` exists for backward compatibility with
|
|
46
58
|
* raw nsec-style keys.
|
|
47
59
|
*
|
|
60
|
+
* For descriptor-based signing, wrap with {@link StaticDescriptorProvider}.
|
|
61
|
+
*
|
|
48
62
|
* @example
|
|
49
63
|
* ```typescript
|
|
50
64
|
* const seed = mnemonicToSeedSync(mnemonic);
|
|
@@ -64,7 +78,11 @@ export class SeedIdentity {
|
|
|
64
78
|
if (seed.length !== 64) {
|
|
65
79
|
throw new Error("Seed must be 64 bytes");
|
|
66
80
|
}
|
|
67
|
-
|
|
81
|
+
// Defensive copy: `derivedKey` and `descriptor` are computed eagerly
|
|
82
|
+
// from the bytes we're about to stash, so a later mutation of the
|
|
83
|
+
// caller's buffer must not drift the serialized `seed` out of sync
|
|
84
|
+
// with the live identity state.
|
|
85
|
+
seedBytes.set(this, new Uint8Array(seed));
|
|
68
86
|
this.descriptor = descriptor;
|
|
69
87
|
const network = detectNetwork(descriptor);
|
|
70
88
|
// Parse and validate the descriptor using the library
|
|
@@ -169,8 +187,9 @@ export class SeedIdentity {
|
|
|
169
187
|
* ```
|
|
170
188
|
*/
|
|
171
189
|
export class MnemonicIdentity extends SeedIdentity {
|
|
172
|
-
constructor(seed, descriptor) {
|
|
190
|
+
constructor(seed, descriptor, mnemonic, passphrase) {
|
|
173
191
|
super(seed, descriptor);
|
|
192
|
+
mnemonicMeta.set(this, { mnemonic, passphrase });
|
|
174
193
|
}
|
|
175
194
|
/**
|
|
176
195
|
* Creates a MnemonicIdentity from a BIP39 mnemonic phrase.
|
|
@@ -190,7 +209,7 @@ export class MnemonicIdentity extends SeedIdentity {
|
|
|
190
209
|
const descriptor = hasDescriptor(opts)
|
|
191
210
|
? opts.descriptor
|
|
192
211
|
: buildDescriptor(seed, opts.isMainnet ?? true);
|
|
193
|
-
return new MnemonicIdentity(seed, descriptor);
|
|
212
|
+
return new MnemonicIdentity(seed, descriptor, phrase, passphrase);
|
|
194
213
|
}
|
|
195
214
|
}
|
|
196
215
|
/**
|
|
@@ -246,3 +265,67 @@ export class ReadonlyDescriptorIdentity {
|
|
|
246
265
|
return this.compressedPubKey;
|
|
247
266
|
}
|
|
248
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Serialize a seed-backed signing identity into a
|
|
270
|
+
* {@link SerializedSigningIdentity} envelope without exposing the
|
|
271
|
+
* underlying secret material on the public instance surface.
|
|
272
|
+
*
|
|
273
|
+
* Called by {@link serializeSigningIdentity}; application code should
|
|
274
|
+
* prefer that public dispatcher instead of calling this directly. This
|
|
275
|
+
* helper is deliberately kept out of the `src/identity` barrel so it is
|
|
276
|
+
* not part of the package's public export surface.
|
|
277
|
+
*
|
|
278
|
+
* Secret-surface trade-off: the resulting envelope carries master-seed
|
|
279
|
+
* material — the BIP39 mnemonic (+ optional passphrase) for
|
|
280
|
+
* `MnemonicIdentity` or the raw 64-byte seed for `SeedIdentity`. A party
|
|
281
|
+
* that reads this envelope can derive any key under the HD tree, not
|
|
282
|
+
* just the key currently in use. The pre-change `SingleKey` flow only
|
|
283
|
+
* shipped one derived private key and therefore had a smaller blast
|
|
284
|
+
* radius. This is an intentional design trade to preserve class and
|
|
285
|
+
* descriptor identity across the page / service-worker boundary; the
|
|
286
|
+
* page already holds the same material so that it can re-initialize a
|
|
287
|
+
* killed worker. Transport is same-origin `postMessage` only. See the
|
|
288
|
+
* threat-model note in `src/worker/browser/README.md`.
|
|
289
|
+
*
|
|
290
|
+
* @internal
|
|
291
|
+
*/
|
|
292
|
+
export function serializeSeedOwnedSigningIdentity(identity) {
|
|
293
|
+
if (identity instanceof MnemonicIdentity) {
|
|
294
|
+
const meta = mnemonicMeta.get(identity);
|
|
295
|
+
if (!meta) {
|
|
296
|
+
throw new Error("MnemonicIdentity is missing internal secret state; was it constructed via MnemonicIdentity.fromMnemonic()?");
|
|
297
|
+
}
|
|
298
|
+
const envelope = {
|
|
299
|
+
type: "mnemonic",
|
|
300
|
+
mnemonic: meta.mnemonic,
|
|
301
|
+
descriptor: identity.descriptor,
|
|
302
|
+
};
|
|
303
|
+
if (meta.passphrase !== undefined) {
|
|
304
|
+
envelope.passphrase = meta.passphrase;
|
|
305
|
+
}
|
|
306
|
+
return envelope;
|
|
307
|
+
}
|
|
308
|
+
const seed = seedBytes.get(identity);
|
|
309
|
+
if (!seed) {
|
|
310
|
+
throw new Error("SeedIdentity is missing internal secret state; was it constructed via SeedIdentity.fromSeed() or the class constructor?");
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
type: "seed",
|
|
314
|
+
seed: hex.encode(seed),
|
|
315
|
+
descriptor: identity.descriptor,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Downgrade a seed-backed or descriptor-backed identity into a readonly
|
|
320
|
+
* descriptor envelope. Always produces a descriptor-only shape — secret
|
|
321
|
+
* material never crosses this path, even if the input is a signing
|
|
322
|
+
* identity.
|
|
323
|
+
*
|
|
324
|
+
* Deliberately kept out of the `src/identity` barrel; consumers should go
|
|
325
|
+
* through {@link serializeReadonlyIdentity}.
|
|
326
|
+
*
|
|
327
|
+
* @internal
|
|
328
|
+
*/
|
|
329
|
+
export function serializeSeedOwnedReadonlyIdentity(identity) {
|
|
330
|
+
return { type: "readonly-descriptor", descriptor: identity.descriptor };
|
|
331
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { hex } from "@scure/base";
|
|
2
|
+
import { SingleKey, ReadonlySingleKey } from './singleKey.js';
|
|
3
|
+
import { SeedIdentity, MnemonicIdentity, ReadonlyDescriptorIdentity, serializeSeedOwnedSigningIdentity, serializeSeedOwnedReadonlyIdentity, } from './seedIdentity.js';
|
|
4
|
+
/** Type guard — true for signing envelopes, false for readonly envelopes. */
|
|
5
|
+
export function isSigningSerialized(s) {
|
|
6
|
+
return (s.type === "single-key" || s.type === "seed" || s.type === "mnemonic");
|
|
7
|
+
}
|
|
8
|
+
function hasToHex(identity) {
|
|
9
|
+
return typeof identity.toHex === "function";
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Serialize a signing identity into a structured-clone safe envelope for
|
|
13
|
+
* transport across the service-worker boundary.
|
|
14
|
+
*
|
|
15
|
+
* Supports SDK-owned signing identities directly. For custom identities, a
|
|
16
|
+
* duck-typed `toHex()` fallback preserves compatibility with existing
|
|
17
|
+
* `SingleKey`-like implementations.
|
|
18
|
+
*/
|
|
19
|
+
export function serializeSigningIdentity(identity) {
|
|
20
|
+
// Seed-backed identities (including MnemonicIdentity, which extends
|
|
21
|
+
// SeedIdentity) delegate to the colocated helper so secret material
|
|
22
|
+
// stays behind the WeakMap-backed internal state in seedIdentity.ts.
|
|
23
|
+
if (identity instanceof SeedIdentity) {
|
|
24
|
+
return serializeSeedOwnedSigningIdentity(identity);
|
|
25
|
+
}
|
|
26
|
+
if (identity instanceof SingleKey) {
|
|
27
|
+
return { type: "single-key", privateKey: identity.toHex() };
|
|
28
|
+
}
|
|
29
|
+
if (hasToHex(identity)) {
|
|
30
|
+
return { type: "single-key", privateKey: identity.toHex() };
|
|
31
|
+
}
|
|
32
|
+
throw new Error("Unsupported signing identity: cannot serialize for service-worker transport");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Serialize a readonly identity into a structured-clone safe envelope.
|
|
36
|
+
*
|
|
37
|
+
* Works for any `ReadonlyIdentity` via `compressedPublicKey()`. When called
|
|
38
|
+
* with a signing identity, produces a readonly envelope (never ships signing
|
|
39
|
+
* material) — callers that need to preserve signing capability across the
|
|
40
|
+
* boundary must use {@link serializeSigningIdentity}.
|
|
41
|
+
*/
|
|
42
|
+
export async function serializeReadonlyIdentity(identity) {
|
|
43
|
+
if (identity instanceof SeedIdentity ||
|
|
44
|
+
identity instanceof ReadonlyDescriptorIdentity) {
|
|
45
|
+
return serializeSeedOwnedReadonlyIdentity(identity);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
type: "readonly-single-key",
|
|
49
|
+
publicKey: hex.encode(await identity.compressedPublicKey()),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Rehydrate a serialized identity envelope back into an identity instance.
|
|
54
|
+
* The return type is the union of signing and readonly; use
|
|
55
|
+
* {@link isSigningSerialized} on the envelope before hydration if the caller
|
|
56
|
+
* needs to know which side it ends up on.
|
|
57
|
+
*/
|
|
58
|
+
export function hydrateIdentity(s) {
|
|
59
|
+
switch (s.type) {
|
|
60
|
+
case "single-key":
|
|
61
|
+
return SingleKey.fromHex(s.privateKey);
|
|
62
|
+
case "readonly-single-key":
|
|
63
|
+
return ReadonlySingleKey.fromPublicKey(hex.decode(s.publicKey));
|
|
64
|
+
case "seed":
|
|
65
|
+
return SeedIdentity.fromSeed(hex.decode(s.seed), {
|
|
66
|
+
descriptor: s.descriptor,
|
|
67
|
+
});
|
|
68
|
+
case "mnemonic":
|
|
69
|
+
return MnemonicIdentity.fromMnemonic(s.mnemonic, {
|
|
70
|
+
descriptor: s.descriptor,
|
|
71
|
+
passphrase: s.passphrase,
|
|
72
|
+
});
|
|
73
|
+
case "readonly-descriptor":
|
|
74
|
+
return ReadonlyDescriptorIdentity.fromDescriptor(s.descriptor);
|
|
75
|
+
default:
|
|
76
|
+
// Belt-and-suspenders: `normalizeSerializedIdentity` already
|
|
77
|
+
// rejects unknown `type` values at the wire boundary. Without
|
|
78
|
+
// this throw, an unknown type would fall through and return
|
|
79
|
+
// undefined, which callers would then cast to Identity and
|
|
80
|
+
// crash downstream with an opaque error.
|
|
81
|
+
throw new Error(`Unknown serialized identity type: ${String(s.type)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
let warnedLegacyShape = false;
|
|
85
|
+
/**
|
|
86
|
+
* Accept either a modern {@link SerializedIdentity} envelope or a legacy
|
|
87
|
+
* `{ privateKey }` / `{ publicKey }` shape and normalize to a
|
|
88
|
+
* {@link SerializedIdentity}. Emits a one-time deprecation warning when a
|
|
89
|
+
* legacy shape is seen.
|
|
90
|
+
*
|
|
91
|
+
* Intended for the worker-side boundary; new page builds always emit tagged
|
|
92
|
+
* envelopes via {@link serializeSigningIdentity} /
|
|
93
|
+
* {@link serializeReadonlyIdentity}.
|
|
94
|
+
*/
|
|
95
|
+
export function normalizeSerializedIdentity(shape) {
|
|
96
|
+
if ("type" in shape) {
|
|
97
|
+
assertValidSerializedIdentity(shape);
|
|
98
|
+
return shape;
|
|
99
|
+
}
|
|
100
|
+
if (!warnedLegacyShape) {
|
|
101
|
+
warnedLegacyShape = true;
|
|
102
|
+
console.warn("[ts-sdk] Received legacy serialized identity shape " +
|
|
103
|
+
"(privateKey/publicKey). Upgrade the page build to the latest " +
|
|
104
|
+
"@arkade-os/sdk — this compatibility path will be removed in " +
|
|
105
|
+
"the next major.");
|
|
106
|
+
}
|
|
107
|
+
if ("privateKey" in shape && typeof shape.privateKey === "string") {
|
|
108
|
+
return { type: "single-key", privateKey: shape.privateKey };
|
|
109
|
+
}
|
|
110
|
+
if ("publicKey" in shape && typeof shape.publicKey === "string") {
|
|
111
|
+
return { type: "readonly-single-key", publicKey: shape.publicKey };
|
|
112
|
+
}
|
|
113
|
+
throw new Error("Unrecognized serialized identity shape");
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Runtime-validate that a tagged envelope carries the fields its variant
|
|
117
|
+
* requires. The SDK's own serializer produces well-formed envelopes; this
|
|
118
|
+
* guard exists so a malformed message (older SDK version mismatch,
|
|
119
|
+
* hand-built config, etc.) fails loudly at the wire boundary rather than
|
|
120
|
+
* with an opaque `"Cannot read properties of undefined"` deep inside a
|
|
121
|
+
* hydrator.
|
|
122
|
+
*/
|
|
123
|
+
function assertValidSerializedIdentity(s) {
|
|
124
|
+
const kind = s.type;
|
|
125
|
+
const bad = (field, expected) => {
|
|
126
|
+
throw new Error(`Malformed serialized identity ({ type: ${JSON.stringify(kind)} }): ` +
|
|
127
|
+
`missing or invalid "${field}" (expected ${expected})`);
|
|
128
|
+
};
|
|
129
|
+
const asStr = (key) => {
|
|
130
|
+
const v = s[key];
|
|
131
|
+
return typeof v === "string" ? v : bad(key, "string");
|
|
132
|
+
};
|
|
133
|
+
switch (kind) {
|
|
134
|
+
case "single-key":
|
|
135
|
+
asStr("privateKey");
|
|
136
|
+
return;
|
|
137
|
+
case "readonly-single-key":
|
|
138
|
+
asStr("publicKey");
|
|
139
|
+
return;
|
|
140
|
+
case "seed":
|
|
141
|
+
asStr("seed");
|
|
142
|
+
asStr("descriptor");
|
|
143
|
+
return;
|
|
144
|
+
case "mnemonic": {
|
|
145
|
+
asStr("mnemonic");
|
|
146
|
+
asStr("descriptor");
|
|
147
|
+
const passphrase = s.passphrase;
|
|
148
|
+
if (passphrase !== undefined && typeof passphrase !== "string") {
|
|
149
|
+
bad("passphrase", "string | undefined");
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
case "readonly-descriptor":
|
|
154
|
+
asStr("descriptor");
|
|
155
|
+
return;
|
|
156
|
+
default:
|
|
157
|
+
throw new Error(`Unknown serialized identity type: ${String(kind)}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { hex } from "@scure/base";
|
|
2
|
+
import { isBatchSignable } from './index.js';
|
|
3
|
+
import { normalizeToDescriptor, extractPubKey } from './descriptor.js';
|
|
4
|
+
/**
|
|
5
|
+
* Wraps a legacy Identity (single-key) as a DescriptorProvider.
|
|
6
|
+
* The descriptor is always a simple tr(pubkey) format.
|
|
7
|
+
*/
|
|
8
|
+
export class StaticDescriptorProvider {
|
|
9
|
+
constructor(identity, pubKeyHex) {
|
|
10
|
+
this.identity = identity;
|
|
11
|
+
this.pubKeyHex = pubKeyHex;
|
|
12
|
+
this.descriptor = `tr(${pubKeyHex})`;
|
|
13
|
+
}
|
|
14
|
+
static async create(identity) {
|
|
15
|
+
const pubKey = await identity.xOnlyPublicKey();
|
|
16
|
+
return new StaticDescriptorProvider(identity, hex.encode(pubKey));
|
|
17
|
+
}
|
|
18
|
+
getSigningDescriptor() {
|
|
19
|
+
return this.descriptor;
|
|
20
|
+
}
|
|
21
|
+
isOurs(descriptor) {
|
|
22
|
+
try {
|
|
23
|
+
const normalized = normalizeToDescriptor(descriptor);
|
|
24
|
+
const pubKey = extractPubKey(normalized);
|
|
25
|
+
return pubKey.toLowerCase() === this.pubKeyHex.toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async signWithDescriptor(requests) {
|
|
32
|
+
for (const request of requests) {
|
|
33
|
+
if (!this.isOurs(request.descriptor)) {
|
|
34
|
+
throw new Error(`Descriptor ${request.descriptor} does not belong to this provider`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Use batch signing when the identity supports it (fewer confirmation popups)
|
|
38
|
+
if (isBatchSignable(this.identity)) {
|
|
39
|
+
const signed = await this.identity.signMultiple(requests.map((r) => ({
|
|
40
|
+
tx: r.tx,
|
|
41
|
+
inputIndexes: r.inputIndexes,
|
|
42
|
+
})));
|
|
43
|
+
if (signed.length !== requests.length) {
|
|
44
|
+
throw new Error(`signMultiple returned ${signed.length} transactions, expected ${requests.length}`);
|
|
45
|
+
}
|
|
46
|
+
return signed;
|
|
47
|
+
}
|
|
48
|
+
const results = [];
|
|
49
|
+
for (const request of requests) {
|
|
50
|
+
const signed = await this.identity.sign(request.tx, request.inputIndexes);
|
|
51
|
+
results.push(signed);
|
|
52
|
+
}
|
|
53
|
+
return results;
|
|
54
|
+
}
|
|
55
|
+
async signMessageWithDescriptor(descriptor, message, type = "schnorr") {
|
|
56
|
+
if (!this.isOurs(descriptor)) {
|
|
57
|
+
throw new Error(`Descriptor ${descriptor} does not belong to this provider`);
|
|
58
|
+
}
|
|
59
|
+
return this.identity.signMessage(message, type);
|
|
60
|
+
}
|
|
61
|
+
}
|
package/dist/esm/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { ServiceWorkerWallet, ServiceWorkerReadonlyWallet, DEFAULT_MESSAGE_TIMEO
|
|
|
18
18
|
import { OnchainWallet } from './wallet/onchain.js';
|
|
19
19
|
import { setupServiceWorker } from './worker/browser/utils.js';
|
|
20
20
|
import { ESPLORA_URL, EsploraProvider, } from './providers/onchain.js';
|
|
21
|
+
import { ElectrumOnchainProvider, WsElectrumChainSource, } from './providers/electrum.js';
|
|
21
22
|
import { RestArkProvider, SettlementEventType, } from './providers/ark.js';
|
|
22
23
|
import { RestDelegatorProvider, } from './providers/delegator.js';
|
|
23
24
|
import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, decodeTapscript, MultisigTapscript, } from './script/tapscript.js';
|
|
@@ -46,7 +47,7 @@ export {
|
|
|
46
47
|
// Wallets
|
|
47
48
|
Wallet, ReadonlyWallet, SingleKey, ReadonlySingleKey, SeedIdentity, MnemonicIdentity, ReadonlyDescriptorIdentity, isBatchSignable, OnchainWallet, Ramps, VtxoManager, DelegatorManagerImpl, RestDelegatorProvider,
|
|
48
49
|
// Providers
|
|
49
|
-
ESPLORA_URL, EsploraProvider, RestArkProvider, RestIndexerProvider,
|
|
50
|
+
ESPLORA_URL, EsploraProvider, ElectrumOnchainProvider, WsElectrumChainSource, RestArkProvider, RestIndexerProvider,
|
|
50
51
|
// Script-related
|
|
51
52
|
ArkAddress, DefaultVtxo, DelegateVtxo, VtxoScript, VHTLC,
|
|
52
53
|
// Enums
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hex } from "@scure/base";
|
|
2
|
-
import { eventSourceIterator } from './utils.js';
|
|
2
|
+
import { eventSourceIterator, isEventSourceError } from './utils.js';
|
|
3
3
|
import { maybeArkError } from './errors.js';
|
|
4
4
|
import { Intent } from '../intent/index.js';
|
|
5
5
|
export var SettlementEventType;
|
|
@@ -232,18 +232,19 @@ export class RestArkProvider {
|
|
|
232
232
|
// leak the underlying SSE connection. `return()` is overridden below
|
|
233
233
|
// so that closing the generator also closes the connection even when
|
|
234
234
|
// the body is currently suspended at an await point.
|
|
235
|
-
let
|
|
235
|
+
let iterator = null;
|
|
236
|
+
const closeIterator = () => iterator?.close();
|
|
236
237
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
237
238
|
const self = this;
|
|
238
239
|
const gen = (async function* () {
|
|
239
|
-
const abortHandler =
|
|
240
|
+
const abortHandler = closeIterator;
|
|
240
241
|
signal?.addEventListener("abort", abortHandler);
|
|
241
242
|
try {
|
|
242
243
|
while (!signal?.aborted) {
|
|
243
|
-
|
|
244
|
-
|
|
244
|
+
const currentIterator = eventSourceIterator(new EventSource(url + queryParams));
|
|
245
|
+
iterator = currentIterator;
|
|
245
246
|
try {
|
|
246
|
-
for await (const event of
|
|
247
|
+
for await (const event of currentIterator) {
|
|
247
248
|
if (signal?.aborted)
|
|
248
249
|
break;
|
|
249
250
|
try {
|
|
@@ -260,8 +261,9 @@ export class RestArkProvider {
|
|
|
260
261
|
}
|
|
261
262
|
}
|
|
262
263
|
catch (error) {
|
|
263
|
-
if (
|
|
264
|
-
error
|
|
264
|
+
if (signal?.aborted ||
|
|
265
|
+
(error instanceof Error &&
|
|
266
|
+
error.name === "AbortError")) {
|
|
265
267
|
break;
|
|
266
268
|
}
|
|
267
269
|
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
@@ -269,71 +271,94 @@ export class RestArkProvider {
|
|
|
269
271
|
console.debug("Timeout error ignored");
|
|
270
272
|
continue;
|
|
271
273
|
}
|
|
274
|
+
if (isEventSourceError(error)) {
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
272
277
|
console.error("Event stream error:", error);
|
|
273
278
|
throw error;
|
|
274
279
|
}
|
|
275
280
|
finally {
|
|
276
|
-
|
|
281
|
+
currentIterator.close();
|
|
282
|
+
iterator = null;
|
|
277
283
|
}
|
|
278
284
|
}
|
|
279
285
|
}
|
|
280
286
|
finally {
|
|
281
287
|
signal?.removeEventListener("abort", abortHandler);
|
|
282
|
-
|
|
288
|
+
closeIterator();
|
|
283
289
|
}
|
|
284
290
|
})();
|
|
285
291
|
const origReturn = gen.return.bind(gen);
|
|
286
292
|
gen.return = (value) => {
|
|
287
|
-
|
|
293
|
+
closeIterator();
|
|
288
294
|
return origReturn(value);
|
|
289
295
|
};
|
|
290
296
|
return gen;
|
|
291
297
|
}
|
|
292
|
-
|
|
298
|
+
getTransactionsStream(signal) {
|
|
293
299
|
const url = `${this.serverUrl}/v1/txs`;
|
|
294
|
-
|
|
300
|
+
let iterator = null;
|
|
301
|
+
const closeIterator = () => iterator?.close();
|
|
302
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
303
|
+
const self = this;
|
|
304
|
+
const gen = (async function* () {
|
|
305
|
+
const abortHandler = closeIterator;
|
|
306
|
+
signal?.addEventListener("abort", abortHandler);
|
|
295
307
|
try {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
308
|
+
while (!signal?.aborted) {
|
|
309
|
+
try {
|
|
310
|
+
const currentIterator = eventSourceIterator(new EventSource(url));
|
|
311
|
+
iterator = currentIterator;
|
|
312
|
+
for await (const event of currentIterator) {
|
|
313
|
+
if (signal?.aborted)
|
|
314
|
+
break;
|
|
315
|
+
try {
|
|
316
|
+
const data = JSON.parse(event.data);
|
|
317
|
+
const txNotification = self.parseTransactionNotification(data);
|
|
318
|
+
if (txNotification) {
|
|
319
|
+
yield txNotification;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
console.error("Failed to parse transaction notification:", err);
|
|
324
|
+
throw err;
|
|
311
325
|
}
|
|
312
326
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
if (signal?.aborted ||
|
|
330
|
+
(error instanceof Error &&
|
|
331
|
+
error.name === "AbortError")) {
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
335
|
+
if (isFetchTimeoutError(error)) {
|
|
336
|
+
console.debug("Timeout error ignored");
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (isEventSourceError(error)) {
|
|
340
|
+
throw error;
|
|
316
341
|
}
|
|
342
|
+
console.error("Transaction stream error:", error);
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
closeIterator();
|
|
347
|
+
iterator = null;
|
|
317
348
|
}
|
|
318
|
-
}
|
|
319
|
-
finally {
|
|
320
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
321
|
-
eventSource.close();
|
|
322
349
|
}
|
|
323
350
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
329
|
-
if (isFetchTimeoutError(error)) {
|
|
330
|
-
console.debug("Timeout error ignored");
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
console.error("Transaction stream error:", error);
|
|
334
|
-
throw error;
|
|
351
|
+
finally {
|
|
352
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
353
|
+
closeIterator();
|
|
335
354
|
}
|
|
336
|
-
}
|
|
355
|
+
})();
|
|
356
|
+
const origReturn = gen.return.bind(gen);
|
|
357
|
+
gen.return = (value) => {
|
|
358
|
+
closeIterator();
|
|
359
|
+
return origReturn(value);
|
|
360
|
+
};
|
|
361
|
+
return gen;
|
|
337
362
|
}
|
|
338
363
|
async getPendingTxs(intent) {
|
|
339
364
|
const url = `${this.serverUrl}/v1/tx/pending`;
|