@1sat/wallet-toolbox 0.0.5 → 0.0.7

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 (38) hide show
  1. package/dist/OneSatWallet.d.ts +46 -17
  2. package/dist/OneSatWallet.js +956 -0
  3. package/dist/errors.js +11 -0
  4. package/dist/index.d.ts +0 -2
  5. package/dist/index.js +12 -93707
  6. package/dist/indexers/Bsv21Indexer.js +232 -0
  7. package/dist/indexers/CosignIndexer.js +25 -0
  8. package/dist/indexers/FundIndexer.js +64 -0
  9. package/dist/indexers/InscriptionIndexer.js +115 -0
  10. package/dist/indexers/LockIndexer.js +42 -0
  11. package/dist/indexers/MapIndexer.js +62 -0
  12. package/dist/indexers/OpNSIndexer.js +38 -0
  13. package/dist/indexers/OrdLockIndexer.js +63 -0
  14. package/dist/indexers/OriginIndexer.js +240 -0
  15. package/dist/indexers/Outpoint.js +53 -0
  16. package/dist/indexers/SigmaIndexer.js +133 -0
  17. package/dist/indexers/TransactionParser.d.ts +53 -0
  18. package/dist/indexers/index.js +13 -0
  19. package/dist/indexers/parseAddress.js +24 -0
  20. package/dist/indexers/types.js +18 -0
  21. package/dist/services/OneSatServices.d.ts +12 -4
  22. package/dist/services/OneSatServices.js +231 -0
  23. package/dist/services/client/ArcadeClient.js +107 -0
  24. package/dist/services/client/BaseClient.js +125 -0
  25. package/dist/services/client/BeefClient.js +33 -0
  26. package/dist/services/client/Bsv21Client.js +65 -0
  27. package/dist/services/client/ChaintracksClient.js +175 -0
  28. package/dist/services/client/OrdfsClient.js +122 -0
  29. package/dist/services/client/OwnerClient.js +123 -0
  30. package/dist/services/client/TxoClient.js +85 -0
  31. package/dist/services/client/index.js +8 -0
  32. package/dist/services/types.js +5 -0
  33. package/dist/signers/ReadOnlySigner.js +47 -0
  34. package/dist/sync/IndexedDbSyncQueue.js +355 -0
  35. package/dist/sync/SqliteSyncQueue.js +197 -0
  36. package/dist/sync/index.js +3 -0
  37. package/dist/sync/types.js +4 -0
  38. package/package.json +5 -5
@@ -0,0 +1,175 @@
1
+ import { BaseClient } from "./BaseClient";
2
+ /**
3
+ * Helper to read a 32-bit unsigned integer from little-endian bytes
4
+ */
5
+ function readUint32LE(data, offset) {
6
+ return ((data[offset] |
7
+ (data[offset + 1] << 8) |
8
+ (data[offset + 2] << 16) |
9
+ (data[offset + 3] << 24)) >>>
10
+ 0);
11
+ }
12
+ /**
13
+ * Convert bytes to hex string (little-endian)
14
+ */
15
+ function toHexLE(data) {
16
+ return Array.from(data)
17
+ .reverse()
18
+ .map((b) => b.toString(16).padStart(2, "0"))
19
+ .join("");
20
+ }
21
+ /**
22
+ * SHA256 hash
23
+ */
24
+ async function sha256(data) {
25
+ const buf = await crypto.subtle.digest("SHA-256", data);
26
+ return new Uint8Array(buf);
27
+ }
28
+ /**
29
+ * Double SHA256 hash (used for block header hash)
30
+ */
31
+ async function doubleSha256(data) {
32
+ return sha256(await sha256(data));
33
+ }
34
+ /**
35
+ * Parse raw 80-byte header into BlockHeader object
36
+ */
37
+ async function parseHeader(data, height) {
38
+ const version = readUint32LE(data, 0);
39
+ const prevHash = toHexLE(data.slice(4, 36));
40
+ const merkleRoot = toHexLE(data.slice(36, 68));
41
+ const time = readUint32LE(data, 68);
42
+ const bits = readUint32LE(data, 72);
43
+ const nonce = readUint32LE(data, 76);
44
+ const hash = toHexLE(await doubleSha256(data));
45
+ return { height, hash, version, prevHash, merkleRoot, time, bits, nonce };
46
+ }
47
+ /**
48
+ * Client for /1sat/chaintracks/* routes.
49
+ * Provides block header data and implements ChainTracker interface.
50
+ *
51
+ * Routes:
52
+ * - GET /tip - Get chain tip
53
+ * - GET /tip/stream - SSE stream of new blocks
54
+ * - GET /height - Get current height
55
+ * - GET /network - Get network type
56
+ * - GET /headers?height=N&count=M - Get raw header bytes
57
+ * - GET /header/height/:height - Get header by height
58
+ * - GET /header/hash/:hash - Get header by hash
59
+ */
60
+ export class ChaintracksClient extends BaseClient {
61
+ eventSource = null;
62
+ subscribers = new Set();
63
+ constructor(baseUrl, options = {}) {
64
+ super(`${baseUrl}/1sat/chaintracks`, options);
65
+ }
66
+ /**
67
+ * Get current blockchain height (ChainTracker interface)
68
+ */
69
+ async currentHeight() {
70
+ const tip = await this.getTip();
71
+ return tip.height;
72
+ }
73
+ /**
74
+ * Validate merkle root for a given height (ChainTracker interface)
75
+ */
76
+ async isValidRootForHeight(root, height) {
77
+ try {
78
+ const header = await this.getHeaderByHeight(height);
79
+ const isValid = header.merkleRoot === root;
80
+ return isValid;
81
+ }
82
+ catch (e) {
83
+ console.error(`isValidRootForHeight(${height}) failed:`, e);
84
+ return false;
85
+ }
86
+ }
87
+ /**
88
+ * Get the network type (main or test)
89
+ */
90
+ async getNetwork() {
91
+ const data = await this.request("/network");
92
+ return data.network;
93
+ }
94
+ /**
95
+ * Get the current chain tip
96
+ */
97
+ async getTip() {
98
+ return this.request("/tip");
99
+ }
100
+ /**
101
+ * Get block header by height
102
+ */
103
+ async getHeaderByHeight(height) {
104
+ return this.request(`/header/height/${height}`);
105
+ }
106
+ /**
107
+ * Get block header by hash
108
+ */
109
+ async getHeaderByHash(hash) {
110
+ return this.request(`/header/hash/${hash}`);
111
+ }
112
+ /**
113
+ * Get multiple headers as parsed BlockHeader objects
114
+ */
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`);
119
+ }
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));
123
+ }
124
+ return headers;
125
+ }
126
+ /**
127
+ * Get raw header bytes for one or more headers
128
+ */
129
+ async getHeaderBytes(height, count = 1) {
130
+ const data = await this.requestBinary(`/headers?height=${height}&count=${count}`);
131
+ return Array.from(data);
132
+ }
133
+ /**
134
+ * Subscribe to new block notifications via SSE
135
+ * Returns unsubscribe function
136
+ */
137
+ subscribe(callback) {
138
+ this.subscribers.add(callback);
139
+ if (!this.eventSource) {
140
+ this.eventSource = new EventSource(`${this.baseUrl}/tip/stream`);
141
+ this.eventSource.onmessage = (event) => {
142
+ try {
143
+ const header = JSON.parse(event.data);
144
+ for (const cb of this.subscribers) {
145
+ cb(header);
146
+ }
147
+ }
148
+ catch {
149
+ // Ignore parse errors (e.g., keepalive messages)
150
+ }
151
+ };
152
+ this.eventSource.onerror = () => {
153
+ this.eventSource?.close();
154
+ this.eventSource = null;
155
+ };
156
+ }
157
+ return () => {
158
+ this.subscribers.delete(callback);
159
+ if (this.subscribers.size === 0 && this.eventSource) {
160
+ this.eventSource.close();
161
+ this.eventSource = null;
162
+ }
163
+ };
164
+ }
165
+ /**
166
+ * Close all connections
167
+ */
168
+ close() {
169
+ if (this.eventSource) {
170
+ this.eventSource.close();
171
+ this.eventSource = null;
172
+ }
173
+ this.subscribers.clear();
174
+ }
175
+ }
@@ -0,0 +1,122 @@
1
+ import { BaseClient } from "./BaseClient";
2
+ /**
3
+ * Client for ordfs routes.
4
+ * Provides inscription content and metadata.
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)
8
+ */
9
+ export class OrdfsClient extends BaseClient {
10
+ contentBaseUrl;
11
+ constructor(baseUrl, options = {}) {
12
+ super(`${baseUrl}/1sat/ordfs`, options);
13
+ this.contentBaseUrl = `${baseUrl.replace(/\/$/, "")}/1sat/content`;
14
+ }
15
+ /**
16
+ * Get metadata for an inscription
17
+ * @param outpoint - Outpoint (txid_vout) or txid
18
+ * @param seq - Optional sequence number (-1 for latest)
19
+ */
20
+ async getMetadata(outpoint, seq) {
21
+ const path = seq !== undefined ? `${outpoint}:${seq}` : outpoint;
22
+ return this.request(`/metadata/${path}`);
23
+ }
24
+ /**
25
+ * Get inscription content with full response headers
26
+ * @param outpoint - Outpoint (txid_vout) or txid
27
+ * @param options - Content request options
28
+ */
29
+ async getContent(outpoint, options = {}) {
30
+ const url = this.getContentUrl(outpoint, options);
31
+ const controller = new AbortController();
32
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
33
+ try {
34
+ const response = await this.fetchFn(url, {
35
+ signal: controller.signal,
36
+ });
37
+ if (!response.ok) {
38
+ throw new Error(`Failed to fetch content: ${response.statusText}`);
39
+ }
40
+ const headers = this.parseResponseHeaders(response);
41
+ const arrayBuffer = await response.arrayBuffer();
42
+ return {
43
+ data: new Uint8Array(arrayBuffer),
44
+ headers,
45
+ };
46
+ }
47
+ finally {
48
+ clearTimeout(timeoutId);
49
+ }
50
+ }
51
+ /**
52
+ * Preview base64-encoded HTML content
53
+ * @param b64HtmlData - Base64-encoded HTML
54
+ */
55
+ async previewHtml(b64HtmlData) {
56
+ const response = await this.requestRaw(`/preview/${b64HtmlData}`);
57
+ return response.text();
58
+ }
59
+ /**
60
+ * Preview content by posting it directly
61
+ * @param content - Content to preview
62
+ * @param contentType - Content type header
63
+ */
64
+ async previewContent(content, contentType) {
65
+ return this.requestBinary("/preview", {
66
+ method: "POST",
67
+ headers: { "Content-Type": contentType },
68
+ body: content,
69
+ });
70
+ }
71
+ /**
72
+ * Get the URL for fetching inscription content directly.
73
+ * Useful for displaying in img/video tags.
74
+ * @param outpoint - Outpoint (txid_vout) or txid
75
+ * @param options - Content request options
76
+ */
77
+ getContentUrl(outpoint, options = {}) {
78
+ let path = outpoint;
79
+ if (options.seq !== undefined) {
80
+ path = `${outpoint}:${options.seq}`;
81
+ }
82
+ const queryParams = this.buildQueryString({
83
+ map: options.map,
84
+ parent: options.parent,
85
+ raw: options.raw,
86
+ });
87
+ return `${this.contentBaseUrl}/${path}${queryParams}`;
88
+ }
89
+ /**
90
+ * Parse response headers into structured object
91
+ */
92
+ parseResponseHeaders(response) {
93
+ const headers = {
94
+ contentType: response.headers.get("content-type") || "application/octet-stream",
95
+ };
96
+ const outpoint = response.headers.get("x-outpoint");
97
+ if (outpoint)
98
+ headers.outpoint = outpoint;
99
+ const origin = response.headers.get("x-origin");
100
+ if (origin)
101
+ headers.origin = origin;
102
+ const seq = response.headers.get("x-ord-seq");
103
+ if (seq)
104
+ headers.sequence = Number.parseInt(seq, 10);
105
+ const cacheControl = response.headers.get("cache-control");
106
+ if (cacheControl)
107
+ headers.cacheControl = cacheControl;
108
+ const mapData = response.headers.get("x-map");
109
+ if (mapData) {
110
+ try {
111
+ headers.map = JSON.parse(mapData);
112
+ }
113
+ catch {
114
+ // Invalid JSON, skip
115
+ }
116
+ }
117
+ const parent = response.headers.get("x-parent");
118
+ if (parent)
119
+ headers.parent = parent;
120
+ return headers;
121
+ }
122
+ }
@@ -0,0 +1,123 @@
1
+ import { BaseClient } from "./BaseClient";
2
+ /**
3
+ * Client for /1sat/owner/* routes.
4
+ * Provides owner (address) queries and sync.
5
+ *
6
+ * Routes:
7
+ * - GET /:owner/txos - Get TXOs for owner
8
+ * - GET /:owner/balance - Get balance
9
+ * - GET /sync?owner=... - SSE stream of outputs for sync (supports multiple owners)
10
+ */
11
+ export class OwnerClient extends BaseClient {
12
+ constructor(baseUrl, options = {}) {
13
+ super(`${baseUrl}/1sat/owner`, options);
14
+ }
15
+ /**
16
+ * Get TXOs owned by an address/owner
17
+ */
18
+ async getTxos(owner, opts) {
19
+ const qs = this.buildQueryString({
20
+ tags: opts?.tags,
21
+ from: opts?.from,
22
+ limit: opts?.limit,
23
+ rev: opts?.rev,
24
+ unspent: opts?.unspent,
25
+ refresh: opts?.refresh,
26
+ });
27
+ return this.request(`/${owner}/txos${qs}`);
28
+ }
29
+ /**
30
+ * Get balance for an address/owner
31
+ */
32
+ async getBalance(owner) {
33
+ return this.request(`/${owner}/balance`);
34
+ }
35
+ /**
36
+ * Sync outputs for owner(s) via SSE stream.
37
+ * The server merges results from all owners in score order.
38
+ *
39
+ * @param owners - Array of addresses/owners to sync
40
+ * @param onOutput - Callback for each output
41
+ * @param from - Starting score (for pagination/resumption)
42
+ * @param onDone - Callback when sync completes (client should retry after delay)
43
+ * @param onError - Callback for errors
44
+ * @returns Unsubscribe function
45
+ */
46
+ sync(owners, onOutput, from, onDone, onError) {
47
+ const params = new URLSearchParams();
48
+ for (const owner of owners) {
49
+ params.append("owner", owner);
50
+ }
51
+ if (from !== undefined) {
52
+ params.set("from", String(from));
53
+ }
54
+ const url = `${this.baseUrl}/sync?${params.toString()}`;
55
+ const eventSource = new EventSource(url);
56
+ eventSource.onmessage = (event) => {
57
+ try {
58
+ const output = JSON.parse(event.data);
59
+ onOutput(output);
60
+ }
61
+ catch (e) {
62
+ onError?.(e instanceof Error ? e : new Error(String(e)));
63
+ }
64
+ };
65
+ eventSource.addEventListener("done", () => {
66
+ eventSource.close();
67
+ onDone?.();
68
+ });
69
+ eventSource.onerror = () => {
70
+ eventSource.close();
71
+ onError?.(new Error("SSE connection error"));
72
+ };
73
+ return () => {
74
+ eventSource.close();
75
+ };
76
+ }
77
+ /**
78
+ * Sync outputs as an async iterator.
79
+ * Yields SyncOutput objects until the stream is done.
80
+ */
81
+ async *syncIterator(owners, from) {
82
+ const outputs = [];
83
+ let done = false;
84
+ let error = null;
85
+ let resolve = null;
86
+ const unsubscribe = this.sync(owners, (output) => {
87
+ outputs.push(output);
88
+ resolve?.();
89
+ }, from, () => {
90
+ done = true;
91
+ resolve?.();
92
+ }, (e) => {
93
+ error = e;
94
+ resolve?.();
95
+ });
96
+ try {
97
+ while (!done && !error) {
98
+ if (outputs.length > 0) {
99
+ const output = outputs.shift();
100
+ if (output)
101
+ yield output;
102
+ }
103
+ else {
104
+ await new Promise((r) => {
105
+ resolve = r;
106
+ });
107
+ }
108
+ }
109
+ // Yield remaining outputs
110
+ while (outputs.length > 0) {
111
+ const output = outputs.shift();
112
+ if (output)
113
+ yield output;
114
+ }
115
+ if (error) {
116
+ throw error;
117
+ }
118
+ }
119
+ finally {
120
+ unsubscribe();
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,85 @@
1
+ import { BaseClient } from "./BaseClient";
2
+ /**
3
+ * Client for /1sat/txo/* routes.
4
+ * Provides TXO (transaction output) lookup and search.
5
+ *
6
+ * Routes:
7
+ * - GET /:outpoint - Get single TXO (outpoint pattern-matched)
8
+ * - GET /:outpoint/spend - Get spend info
9
+ * - POST /outpoints - Get multiple TXOs
10
+ * - POST /spends - Get multiple spends
11
+ * - GET /tx/:txid - Get all TXOs for a transaction
12
+ * - GET /search?key=... - Search by key(s)
13
+ */
14
+ export class TxoClient extends BaseClient {
15
+ constructor(baseUrl, options = {}) {
16
+ super(`${baseUrl}/1sat/txo`, options);
17
+ }
18
+ /**
19
+ * Get a single TXO by outpoint
20
+ */
21
+ async get(outpoint, opts) {
22
+ const qs = this.buildQueryString({
23
+ tags: opts?.tags,
24
+ });
25
+ return this.request(`/${outpoint}${qs}`);
26
+ }
27
+ /**
28
+ * Get multiple TXOs by outpoints
29
+ */
30
+ async getBatch(outpoints, opts) {
31
+ const qs = this.buildQueryString({
32
+ tags: opts?.tags,
33
+ });
34
+ return this.request(`/outpoints${qs}`, {
35
+ method: "POST",
36
+ headers: { "Content-Type": "application/json" },
37
+ body: JSON.stringify(outpoints),
38
+ });
39
+ }
40
+ /**
41
+ * Get spend info for an outpoint
42
+ */
43
+ async getSpend(outpoint) {
44
+ const resp = await this.request(`/${outpoint}/spend`);
45
+ return resp.spendTxid;
46
+ }
47
+ /**
48
+ * Get spend info for multiple outpoints
49
+ */
50
+ async getSpends(outpoints) {
51
+ const resp = await this.request("/spends", {
52
+ method: "POST",
53
+ headers: { "Content-Type": "application/json" },
54
+ body: JSON.stringify(outpoints),
55
+ });
56
+ return resp.map((r) => r.spendTxid);
57
+ }
58
+ /**
59
+ * Get all TXOs for a transaction
60
+ */
61
+ async getByTxid(txid, opts) {
62
+ const qs = this.buildQueryString({
63
+ tags: opts?.tags,
64
+ });
65
+ return this.request(`/tx/${txid}${qs}`);
66
+ }
67
+ /**
68
+ * Search TXOs by key(s)
69
+ */
70
+ async search(keys, opts) {
71
+ const qs = this.buildQueryString({
72
+ key: Array.isArray(keys) ? keys : [keys],
73
+ tags: opts?.tags,
74
+ from: opts?.from,
75
+ limit: opts?.limit,
76
+ rev: opts?.rev,
77
+ unspent: opts?.unspent,
78
+ sats: opts?.sats,
79
+ spend: opts?.spend,
80
+ events: opts?.events,
81
+ block: opts?.block,
82
+ });
83
+ return this.request(`/search${qs}`);
84
+ }
85
+ }
@@ -0,0 +1,8 @@
1
+ export { BaseClient } from "./BaseClient";
2
+ export { ChaintracksClient } from "./ChaintracksClient";
3
+ export { BeefClient } from "./BeefClient";
4
+ export { ArcadeClient } from "./ArcadeClient";
5
+ export { TxoClient } from "./TxoClient";
6
+ export { OwnerClient } from "./OwnerClient";
7
+ export { OrdfsClient } from "./OrdfsClient";
8
+ export { Bsv21Client } from "./Bsv21Client";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Consolidated type definitions for 1sat-stack API clients.
3
+ * These types mirror the structures returned by the 1sat-stack server.
4
+ */
5
+ export {};
@@ -0,0 +1,47 @@
1
+ import { PublicKey, } from "@bsv/sdk";
2
+ /**
3
+ * A mock PrivateKey that only supports toPublicKey().
4
+ * All other operations throw.
5
+ */
6
+ class ReadOnlyPrivateKey {
7
+ publicKey;
8
+ constructor(publicKeyHex) {
9
+ this.publicKey = PublicKey.fromString(publicKeyHex);
10
+ }
11
+ toPublicKey() {
12
+ return this.publicKey;
13
+ }
14
+ toString() {
15
+ return this.publicKey.toString();
16
+ }
17
+ toHex() {
18
+ return this.publicKey.toString();
19
+ }
20
+ }
21
+ /**
22
+ * A read-only KeyDeriver that exposes an identity key but throws on any signing/derivation operation.
23
+ * Used when the wallet is instantiated with only a public key.
24
+ */
25
+ export class ReadOnlySigner {
26
+ identityKey;
27
+ rootKey;
28
+ constructor(identityPublicKey) {
29
+ this.identityKey = identityPublicKey;
30
+ this.rootKey = new ReadOnlyPrivateKey(identityPublicKey);
31
+ }
32
+ derivePrivateKey(_protocolID, _keyID, _counterparty) {
33
+ throw new Error("Cannot derive private key in read-only mode");
34
+ }
35
+ derivePublicKey(_protocolID, _keyID, _counterparty, _forSelf) {
36
+ throw new Error("Cannot derive public key in read-only mode");
37
+ }
38
+ deriveSymmetricKey(_protocolID, _keyID, _counterparty) {
39
+ throw new Error("Cannot derive symmetric key in read-only mode");
40
+ }
41
+ revealCounterpartySecret(_counterparty) {
42
+ throw new Error("Cannot reveal secrets in read-only mode");
43
+ }
44
+ revealSpecificSecret(_counterparty, _protocolID, _keyID) {
45
+ throw new Error("Cannot reveal secrets in read-only mode");
46
+ }
47
+ }