@1sat/wallet-toolbox 0.0.7 → 0.0.9

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 (57) hide show
  1. package/dist/api/OneSatApi.d.ts +100 -0
  2. package/dist/api/OneSatApi.js +156 -0
  3. package/dist/api/balance/index.d.ts +38 -0
  4. package/dist/api/balance/index.js +82 -0
  5. package/dist/api/broadcast/index.d.ts +22 -0
  6. package/dist/api/broadcast/index.js +45 -0
  7. package/dist/api/constants.d.ts +21 -0
  8. package/dist/api/constants.js +29 -0
  9. package/dist/api/index.d.ts +36 -0
  10. package/dist/api/index.js +38 -0
  11. package/dist/api/inscriptions/index.d.ts +25 -0
  12. package/dist/api/inscriptions/index.js +50 -0
  13. package/dist/api/locks/index.d.ts +44 -0
  14. package/dist/api/locks/index.js +233 -0
  15. package/dist/api/ordinals/index.d.ts +87 -0
  16. package/dist/api/ordinals/index.js +446 -0
  17. package/dist/api/payments/index.d.ts +37 -0
  18. package/dist/api/payments/index.js +130 -0
  19. package/dist/api/signing/index.d.ts +32 -0
  20. package/dist/api/signing/index.js +41 -0
  21. package/dist/api/tokens/index.d.ts +88 -0
  22. package/dist/api/tokens/index.js +400 -0
  23. package/dist/cwi/chrome.d.ts +11 -0
  24. package/dist/cwi/chrome.js +39 -0
  25. package/dist/cwi/event.d.ts +11 -0
  26. package/dist/cwi/event.js +38 -0
  27. package/dist/cwi/factory.d.ts +14 -0
  28. package/dist/cwi/factory.js +44 -0
  29. package/dist/cwi/index.d.ts +11 -0
  30. package/dist/cwi/index.js +11 -0
  31. package/dist/cwi/types.d.ts +39 -0
  32. package/dist/cwi/types.js +39 -0
  33. package/dist/index.d.ts +2 -1
  34. package/dist/index.js +5 -1
  35. package/dist/indexers/Bsv21Indexer.js +1 -1
  36. package/dist/indexers/CosignIndexer.js +2 -1
  37. package/dist/indexers/InscriptionIndexer.js +4 -5
  38. package/dist/indexers/LockIndexer.js +2 -1
  39. package/dist/indexers/MapIndexer.js +1 -1
  40. package/dist/indexers/OrdLockIndexer.js +2 -1
  41. package/dist/indexers/OriginIndexer.js +1 -1
  42. package/dist/indexers/index.d.ts +1 -1
  43. package/dist/indexers/types.d.ts +18 -0
  44. package/dist/services/OneSatServices.d.ts +19 -10
  45. package/dist/services/OneSatServices.js +201 -39
  46. package/dist/services/client/ChaintracksClient.d.ts +55 -13
  47. package/dist/services/client/ChaintracksClient.js +123 -28
  48. package/dist/services/client/OrdfsClient.d.ts +2 -2
  49. package/dist/services/client/OrdfsClient.js +4 -3
  50. package/dist/services/client/TxoClient.js +9 -0
  51. package/dist/sync/AddressManager.d.ts +85 -0
  52. package/dist/sync/AddressManager.js +107 -0
  53. package/dist/sync/SyncManager.d.ts +207 -0
  54. package/dist/sync/SyncManager.js +507 -0
  55. package/dist/sync/index.d.ts +4 -0
  56. package/dist/sync/index.js +2 -0
  57. package/package.json +5 -4
@@ -1,14 +1,14 @@
1
1
  import type { ChainTracker } from "@bsv/sdk";
2
- import type { BlockHeader, ClientOptions } from "../types";
2
+ import type { BaseBlockHeader, BlockHeader, Chain } from "@bsv/wallet-toolbox";
3
+ import type { ClientOptions } from "../types";
3
4
  import { BaseClient } from "./BaseClient";
4
5
  /**
5
6
  * Client for /1sat/chaintracks/* routes.
6
- * Provides block header data and implements ChainTracker interface.
7
+ * Implements ChaintracksClientApi interface from @bsv/wallet-toolbox.
7
8
  *
8
9
  * Routes:
9
10
  * - GET /tip - Get chain tip
10
11
  * - GET /tip/stream - SSE stream of new blocks
11
- * - GET /height - Get current height
12
12
  * - GET /network - Get network type
13
13
  * - GET /headers?height=N&count=M - Get raw header bytes
14
14
  * - GET /header/height/:height - Get header by height
@@ -17,6 +17,9 @@ import { BaseClient } from "./BaseClient";
17
17
  export declare class ChaintracksClient extends BaseClient implements ChainTracker {
18
18
  private eventSource;
19
19
  private subscribers;
20
+ private cachedTip;
21
+ private cachedTipTime;
22
+ private static readonly TIP_CACHE_TTL_MS;
20
23
  constructor(baseUrl: string, options?: ClientOptions);
21
24
  /**
22
25
  * Get current blockchain height (ChainTracker interface)
@@ -27,25 +30,64 @@ export declare class ChaintracksClient extends BaseClient implements ChainTracke
27
30
  */
28
31
  isValidRootForHeight(root: string, height: number): Promise<boolean>;
29
32
  /**
30
- * Get the network type (main or test)
33
+ * Get the blockchain network (main or test)
31
34
  */
32
- getNetwork(): Promise<string>;
35
+ getChain(): Promise<Chain>;
33
36
  /**
34
- * Get the current chain tip
37
+ * Get service info - synthetic for remote client
35
38
  */
36
- getTip(): Promise<BlockHeader>;
39
+ getInfo(): Promise<{
40
+ chain: Chain;
41
+ heightBulk: number;
42
+ heightLive: number;
43
+ storage: string;
44
+ bulkIngestors: string[];
45
+ liveIngestors: string[];
46
+ packages: {
47
+ name: string;
48
+ version: string;
49
+ }[];
50
+ }>;
37
51
  /**
38
- * Get block header by height
52
+ * Get current chain height
39
53
  */
40
- getHeaderByHeight(height: number): Promise<BlockHeader>;
54
+ getPresentHeight(): Promise<number>;
41
55
  /**
42
- * Get block header by hash
56
+ * Get headers as serialized 80-byte hex string
57
+ */
58
+ getHeaders(height: number, count: number): Promise<string>;
59
+ /**
60
+ * Get the current chain tip header (cached for 30 seconds)
61
+ */
62
+ findChainTipHeader(): Promise<BlockHeader>;
63
+ /**
64
+ * Get the current chain tip hash
43
65
  */
44
- getHeaderByHash(hash: string): Promise<BlockHeader>;
66
+ findChainTipHash(): Promise<string>;
45
67
  /**
46
- * Get multiple headers as parsed BlockHeader objects
68
+ * Get block header by height
69
+ */
70
+ findHeaderForHeight(height: number): Promise<BlockHeader | undefined>;
71
+ /**
72
+ * Get block header by hash
47
73
  */
48
- getHeaders(height: number, count: number): Promise<BlockHeader[]>;
74
+ findHeaderForBlockHash(hash: string): Promise<BlockHeader | undefined>;
75
+ /** No-op: Remote server tracks the chain */
76
+ addHeader(_header: BaseBlockHeader): Promise<void>;
77
+ /** No-op: Client is always ready */
78
+ startListening(): Promise<void>;
79
+ /** No-op: Resolves immediately */
80
+ listening(): Promise<void>;
81
+ /** Always true for remote client */
82
+ isListening(): Promise<boolean>;
83
+ /** Always true for remote client */
84
+ isSynchronized(): Promise<boolean>;
85
+ /** Not implemented for remote client */
86
+ subscribeReorgs(_listener: (depth: number, oldTip: BlockHeader, newTip: BlockHeader, deactivatedHeaders?: BlockHeader[]) => void): Promise<string>;
87
+ /** Unsubscribe from events */
88
+ unsubscribe(_subscriptionId: string): Promise<boolean>;
89
+ /** Subscribe to new header events */
90
+ subscribeHeaders(listener: (header: BlockHeader) => void): Promise<string>;
49
91
  /**
50
92
  * Get raw header bytes for one or more headers
51
93
  */
@@ -36,22 +36,29 @@ async function doubleSha256(data) {
36
36
  */
37
37
  async function parseHeader(data, height) {
38
38
  const version = readUint32LE(data, 0);
39
- const prevHash = toHexLE(data.slice(4, 36));
39
+ const previousHash = toHexLE(data.slice(4, 36));
40
40
  const merkleRoot = toHexLE(data.slice(36, 68));
41
41
  const time = readUint32LE(data, 68);
42
42
  const bits = readUint32LE(data, 72);
43
43
  const nonce = readUint32LE(data, 76);
44
44
  const hash = toHexLE(await doubleSha256(data));
45
- return { height, hash, version, prevHash, merkleRoot, time, bits, nonce };
45
+ return { height, hash, version, previousHash, merkleRoot, time, bits, nonce };
46
+ }
47
+ /**
48
+ * Convert bytes to hex string (big-endian / natural order)
49
+ */
50
+ function toHex(data) {
51
+ return Array.from(data)
52
+ .map((b) => b.toString(16).padStart(2, "0"))
53
+ .join("");
46
54
  }
47
55
  /**
48
56
  * Client for /1sat/chaintracks/* routes.
49
- * Provides block header data and implements ChainTracker interface.
57
+ * Implements ChaintracksClientApi interface from @bsv/wallet-toolbox.
50
58
  *
51
59
  * Routes:
52
60
  * - GET /tip - Get chain tip
53
61
  * - GET /tip/stream - SSE stream of new blocks
54
- * - GET /height - Get current height
55
62
  * - GET /network - Get network type
56
63
  * - GET /headers?height=N&count=M - Get raw header bytes
57
64
  * - GET /header/height/:height - Get header by height
@@ -60,6 +67,10 @@ async function parseHeader(data, height) {
60
67
  export class ChaintracksClient extends BaseClient {
61
68
  eventSource = null;
62
69
  subscribers = new Set();
70
+ // Chain tip cache (30 second TTL)
71
+ cachedTip = null;
72
+ cachedTipTime = 0;
73
+ static TIP_CACHE_TTL_MS = 30_000;
63
74
  constructor(baseUrl, options = {}) {
64
75
  super(`${baseUrl}/1sat/chaintracks`, options);
65
76
  }
@@ -67,7 +78,7 @@ export class ChaintracksClient extends BaseClient {
67
78
  * Get current blockchain height (ChainTracker interface)
68
79
  */
69
80
  async currentHeight() {
70
- const tip = await this.getTip();
81
+ const tip = await this.findChainTipHeader();
71
82
  return tip.height;
72
83
  }
73
84
  /**
@@ -75,9 +86,8 @@ export class ChaintracksClient extends BaseClient {
75
86
  */
76
87
  async isValidRootForHeight(root, height) {
77
88
  try {
78
- const header = await this.getHeaderByHeight(height);
79
- const isValid = header.merkleRoot === root;
80
- return isValid;
89
+ const header = await this.findHeaderForHeight(height);
90
+ return header?.merkleRoot === root;
81
91
  }
82
92
  catch (e) {
83
93
  console.error(`isValidRootForHeight(${height}) failed:`, e);
@@ -85,43 +95,128 @@ export class ChaintracksClient extends BaseClient {
85
95
  }
86
96
  }
87
97
  /**
88
- * Get the network type (main or test)
98
+ * Get the blockchain network (main or test)
89
99
  */
90
- async getNetwork() {
100
+ async getChain() {
91
101
  const data = await this.request("/network");
92
102
  return data.network;
93
103
  }
94
104
  /**
95
- * Get the current chain tip
105
+ * Get service info - synthetic for remote client
96
106
  */
97
- async getTip() {
98
- return this.request("/tip");
107
+ async getInfo() {
108
+ const tip = await this.findChainTipHeader();
109
+ const chain = await this.getChain();
110
+ return {
111
+ chain,
112
+ heightBulk: tip.height,
113
+ heightLive: tip.height,
114
+ storage: "remote",
115
+ bulkIngestors: [],
116
+ liveIngestors: [],
117
+ packages: [],
118
+ };
99
119
  }
100
120
  /**
101
- * Get block header by height
121
+ * Get current chain height
102
122
  */
103
- async getHeaderByHeight(height) {
104
- return this.request(`/header/height/${height}`);
123
+ async getPresentHeight() {
124
+ return this.currentHeight();
105
125
  }
106
126
  /**
107
- * Get block header by hash
127
+ * Get headers as serialized 80-byte hex string
108
128
  */
109
- async getHeaderByHash(hash) {
110
- return this.request(`/header/hash/${hash}`);
129
+ async getHeaders(height, count) {
130
+ const data = await this.requestBinary(`/headers?height=${height}&count=${count}`);
131
+ return toHex(data);
111
132
  }
112
133
  /**
113
- * Get multiple headers as parsed BlockHeader objects
134
+ * Get the current chain tip header (cached for 30 seconds)
114
135
  */
115
- async getHeaders(height, count) {
116
- const data = await this.requestBinary(`/headers?height=${height}&count=${count}`);
117
- if (data.length % 80 !== 0) {
118
- throw new Error(`Invalid response length: ${data.length} bytes`);
136
+ async findChainTipHeader() {
137
+ const now = Date.now();
138
+ if (this.cachedTip && now - this.cachedTipTime < ChaintracksClient.TIP_CACHE_TTL_MS) {
139
+ return this.cachedTip;
119
140
  }
120
- const headers = [];
121
- for (let i = 0; i < data.length; i += 80) {
122
- headers.push(await parseHeader(data.slice(i, i + 80), height + i / 80));
141
+ const header = await this.request("/tip");
142
+ console.log("[ChaintracksClient] findChainTipHeader:", header.height, header.hash);
143
+ this.cachedTip = header;
144
+ this.cachedTipTime = now;
145
+ return header;
146
+ }
147
+ /**
148
+ * Get the current chain tip hash
149
+ */
150
+ async findChainTipHash() {
151
+ const tip = await this.findChainTipHeader();
152
+ return tip.hash;
153
+ }
154
+ /**
155
+ * Get block header by height
156
+ */
157
+ async findHeaderForHeight(height) {
158
+ try {
159
+ return await this.request(`/header/height/${height}`);
160
+ }
161
+ catch {
162
+ return undefined;
163
+ }
164
+ }
165
+ /**
166
+ * Get block header by hash
167
+ */
168
+ async findHeaderForBlockHash(hash) {
169
+ try {
170
+ return await this.request(`/header/hash/${hash}`);
171
+ }
172
+ catch {
173
+ return undefined;
174
+ }
175
+ }
176
+ /** No-op: Remote server tracks the chain */
177
+ async addHeader(_header) { }
178
+ /** No-op: Client is always ready */
179
+ async startListening() { }
180
+ /** No-op: Resolves immediately */
181
+ async listening() { }
182
+ /** Always true for remote client */
183
+ async isListening() {
184
+ return true;
185
+ }
186
+ /** Always true for remote client */
187
+ async isSynchronized() {
188
+ return true;
189
+ }
190
+ /** Not implemented for remote client */
191
+ async subscribeReorgs(_listener) {
192
+ throw new Error("Method not implemented.");
193
+ }
194
+ /** Unsubscribe from events */
195
+ async unsubscribe(_subscriptionId) {
196
+ return false;
197
+ }
198
+ /** Subscribe to new header events */
199
+ async subscribeHeaders(listener) {
200
+ this.subscribers.add(listener);
201
+ if (!this.eventSource) {
202
+ this.eventSource = new EventSource(`${this.baseUrl}/tip/stream`);
203
+ this.eventSource.onmessage = (event) => {
204
+ try {
205
+ const header = JSON.parse(event.data);
206
+ for (const cb of this.subscribers) {
207
+ cb(header);
208
+ }
209
+ }
210
+ catch {
211
+ // Ignore parse errors
212
+ }
213
+ };
214
+ this.eventSource.onerror = () => {
215
+ this.eventSource?.close();
216
+ this.eventSource = null;
217
+ };
123
218
  }
124
- return headers;
219
+ return "subscription";
125
220
  }
126
221
  /**
127
222
  * Get raw header bytes for one or more headers
@@ -4,8 +4,8 @@ import { BaseClient } from "./BaseClient";
4
4
  * Client for ordfs routes.
5
5
  * Provides inscription content and metadata.
6
6
  *
7
- * Content is served from /1sat/content (e.g., https://api.1sat.app/1sat/content/:outpoint)
8
- * API routes use /1sat/ordfs (e.g., https://api.1sat.app/1sat/ordfs/metadata/:outpoint)
7
+ * Content is served from /content (e.g., https://1sat.shruggr.cloud/content/:outpoint)
8
+ * API routes use /1sat/ordfs (e.g., https://1sat.shruggr.cloud/1sat/ordfs/metadata/:outpoint)
9
9
  */
10
10
  export declare class OrdfsClient extends BaseClient {
11
11
  private readonly contentBaseUrl;
@@ -3,14 +3,15 @@ import { BaseClient } from "./BaseClient";
3
3
  * Client for ordfs routes.
4
4
  * Provides inscription content and metadata.
5
5
  *
6
- * Content is served from /1sat/content (e.g., https://api.1sat.app/1sat/content/:outpoint)
7
- * API routes use /1sat/ordfs (e.g., https://api.1sat.app/1sat/ordfs/metadata/:outpoint)
6
+ * Content is served from /content (e.g., https://1sat.shruggr.cloud/content/:outpoint)
7
+ * API routes use /1sat/ordfs (e.g., https://1sat.shruggr.cloud/1sat/ordfs/metadata/:outpoint)
8
8
  */
9
9
  export class OrdfsClient extends BaseClient {
10
10
  contentBaseUrl;
11
11
  constructor(baseUrl, options = {}) {
12
12
  super(`${baseUrl}/1sat/ordfs`, options);
13
- this.contentBaseUrl = `${baseUrl.replace(/\/$/, "")}/1sat/content`;
13
+ // Content is at root /content, not under /1sat/
14
+ this.contentBaseUrl = `${baseUrl.replace(/\/$/, "")}/content`;
14
15
  }
15
16
  /**
16
17
  * Get metadata for an inscription
@@ -21,6 +21,9 @@ export class TxoClient extends BaseClient {
21
21
  async get(outpoint, opts) {
22
22
  const qs = this.buildQueryString({
23
23
  tags: opts?.tags,
24
+ sats: opts?.sats,
25
+ spend: opts?.spend,
26
+ block: opts?.block,
24
27
  });
25
28
  return this.request(`/${outpoint}${qs}`);
26
29
  }
@@ -30,6 +33,9 @@ export class TxoClient extends BaseClient {
30
33
  async getBatch(outpoints, opts) {
31
34
  const qs = this.buildQueryString({
32
35
  tags: opts?.tags,
36
+ sats: opts?.sats,
37
+ spend: opts?.spend,
38
+ block: opts?.block,
33
39
  });
34
40
  return this.request(`/outpoints${qs}`, {
35
41
  method: "POST",
@@ -61,6 +67,9 @@ export class TxoClient extends BaseClient {
61
67
  async getByTxid(txid, opts) {
62
68
  const qs = this.buildQueryString({
63
69
  tags: opts?.tags,
70
+ sats: opts?.sats,
71
+ spend: opts?.spend,
72
+ block: opts?.block,
64
73
  });
65
74
  return this.request(`/tx/${txid}${qs}`);
66
75
  }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * AddressManager - Manages yours receive addresses using BRC-29 derivation format.
3
+ *
4
+ * Yours receive addresses are fixed, public addresses that users share publicly.
5
+ * They are derived deterministically from the identity key using:
6
+ * - derivationPrefix: "yours receive" (fixed)
7
+ * - derivationSuffix: "0", "1", "2", ... (sequential counter)
8
+ * - senderIdentityKey: our own identity public key (self-referential derivation)
9
+ *
10
+ * This allows:
11
+ * 1. Deterministic regeneration on wallet restore
12
+ * 2. Syncing external payments to these addresses
13
+ * 3. Auto-signing via BRC-29/ScriptTemplateBRC29 (wallet knows the derivation info)
14
+ *
15
+ * Address derivation is done externally (in yours-wallet) and passed to this class.
16
+ */
17
+ import { type WalletProtocol } from "@bsv/sdk";
18
+ /** Fixed prefix for yours receive addresses */
19
+ export declare const YOURS_PREFIX = "yours";
20
+ /** BRC-29 protocol ID - used by wallet-toolbox for key derivation and signing */
21
+ export declare const BRC29_PROTOCOL_ID: WalletProtocol;
22
+ /**
23
+ * Derivation info for a yours receive address.
24
+ * This is what's needed for internalizeAction's paymentRemittance.
25
+ */
26
+ export interface AddressDerivation {
27
+ /** The Bitcoin address (base58check) */
28
+ address: string;
29
+ /** The key index (0, 1, 2, etc.) for internal lookups */
30
+ index: number;
31
+ /** Base64-encoded derivation prefix (e.g., base64("yours receive")) */
32
+ derivationPrefix: string;
33
+ /** Base64-encoded derivation suffix (e.g., base64("0"), base64("1"), etc.) */
34
+ derivationSuffix: string;
35
+ /** Our own identity public key (self-referential) */
36
+ senderIdentityKey: string;
37
+ /** The public key for this address */
38
+ publicKey: string;
39
+ }
40
+ /**
41
+ * AddressManager manages yours receive addresses.
42
+ * Accepts pre-derived addresses - derivation is done externally.
43
+ */
44
+ export declare class AddressManager {
45
+ private addressMap;
46
+ private maxKeyIndex;
47
+ /**
48
+ * @param derivations - Pre-derived address derivations
49
+ */
50
+ constructor(derivations: AddressDerivation[]);
51
+ /**
52
+ * Add a new address derivation.
53
+ */
54
+ addAddress(derivation: AddressDerivation): void;
55
+ /**
56
+ * Get the current max key index.
57
+ * This should be persisted to chrome.storage.
58
+ */
59
+ getMaxKeyIndex(): number;
60
+ /**
61
+ * Get all known addresses.
62
+ */
63
+ getAddresses(): string[];
64
+ /**
65
+ * Get derivation info for an address, or undefined if not ours.
66
+ */
67
+ getDerivation(address: string): AddressDerivation | undefined;
68
+ /**
69
+ * Check if an address belongs to this wallet.
70
+ */
71
+ isOurAddress(address: string): boolean;
72
+ /**
73
+ * Get the primary receive address (index 0).
74
+ */
75
+ getPrimaryAddress(): string | undefined;
76
+ /**
77
+ * Get address at a specific index.
78
+ */
79
+ getAddressAtIndex(index: number): AddressDerivation | undefined;
80
+ /**
81
+ * Build the locking script for an address at a specific index.
82
+ * Useful for verifying outputs match expected scripts.
83
+ */
84
+ getLockingScriptAtIndex(index: number): string | undefined;
85
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * AddressManager - Manages yours receive addresses using BRC-29 derivation format.
3
+ *
4
+ * Yours receive addresses are fixed, public addresses that users share publicly.
5
+ * They are derived deterministically from the identity key using:
6
+ * - derivationPrefix: "yours receive" (fixed)
7
+ * - derivationSuffix: "0", "1", "2", ... (sequential counter)
8
+ * - senderIdentityKey: our own identity public key (self-referential derivation)
9
+ *
10
+ * This allows:
11
+ * 1. Deterministic regeneration on wallet restore
12
+ * 2. Syncing external payments to these addresses
13
+ * 3. Auto-signing via BRC-29/ScriptTemplateBRC29 (wallet knows the derivation info)
14
+ *
15
+ * Address derivation is done externally (in yours-wallet) and passed to this class.
16
+ */
17
+ import { P2PKH } from "@bsv/sdk";
18
+ /** Fixed prefix for yours receive addresses */
19
+ export const YOURS_PREFIX = 'yours';
20
+ /** BRC-29 protocol ID - used by wallet-toolbox for key derivation and signing */
21
+ export const BRC29_PROTOCOL_ID = [2, "3241645161d8"];
22
+ /**
23
+ * AddressManager manages yours receive addresses.
24
+ * Accepts pre-derived addresses - derivation is done externally.
25
+ */
26
+ export class AddressManager {
27
+ addressMap = new Map();
28
+ maxKeyIndex = -1;
29
+ /**
30
+ * @param derivations - Pre-derived address derivations
31
+ */
32
+ constructor(derivations) {
33
+ for (const derivation of derivations) {
34
+ this.addressMap.set(derivation.address, derivation);
35
+ if (derivation.index > this.maxKeyIndex) {
36
+ this.maxKeyIndex = derivation.index;
37
+ }
38
+ }
39
+ }
40
+ /**
41
+ * Add a new address derivation.
42
+ */
43
+ addAddress(derivation) {
44
+ this.addressMap.set(derivation.address, derivation);
45
+ if (derivation.index > this.maxKeyIndex) {
46
+ this.maxKeyIndex = derivation.index;
47
+ }
48
+ }
49
+ /**
50
+ * Get the current max key index.
51
+ * This should be persisted to chrome.storage.
52
+ */
53
+ getMaxKeyIndex() {
54
+ return this.maxKeyIndex;
55
+ }
56
+ /**
57
+ * Get all known addresses.
58
+ */
59
+ getAddresses() {
60
+ return Array.from(this.addressMap.keys());
61
+ }
62
+ /**
63
+ * Get derivation info for an address, or undefined if not ours.
64
+ */
65
+ getDerivation(address) {
66
+ return this.addressMap.get(address);
67
+ }
68
+ /**
69
+ * Check if an address belongs to this wallet.
70
+ */
71
+ isOurAddress(address) {
72
+ return this.addressMap.has(address);
73
+ }
74
+ /**
75
+ * Get the primary receive address (index 0).
76
+ */
77
+ getPrimaryAddress() {
78
+ for (const derivation of this.addressMap.values()) {
79
+ if (derivation.index === 0) {
80
+ return derivation.address;
81
+ }
82
+ }
83
+ return undefined;
84
+ }
85
+ /**
86
+ * Get address at a specific index.
87
+ */
88
+ getAddressAtIndex(index) {
89
+ for (const derivation of this.addressMap.values()) {
90
+ if (derivation.index === index) {
91
+ return derivation;
92
+ }
93
+ }
94
+ return undefined;
95
+ }
96
+ /**
97
+ * Build the locking script for an address at a specific index.
98
+ * Useful for verifying outputs match expected scripts.
99
+ */
100
+ getLockingScriptAtIndex(index) {
101
+ const derivation = this.getAddressAtIndex(index);
102
+ if (!derivation)
103
+ return undefined;
104
+ const p2pkh = new P2PKH();
105
+ return p2pkh.lock(derivation.address).toHex();
106
+ }
107
+ }