@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,231 @@
1
+ import { Beef, Hash, MerklePath, Transaction, Utils } from "@bsv/sdk";
2
+ /**
3
+ * Simple error class for WalletServices error responses.
4
+ */
5
+ class ServiceError extends Error {
6
+ code;
7
+ description;
8
+ isError = true;
9
+ constructor(code, description) {
10
+ super(description);
11
+ this.code = code;
12
+ this.description = description;
13
+ this.name = code;
14
+ }
15
+ }
16
+ import { ArcadeClient, BeefClient, Bsv21Client, ChaintracksClient, OrdfsClient, OwnerClient, TxoClient, } from "./client";
17
+ /**
18
+ * WalletServices implementation for 1Sat ecosystem.
19
+ *
20
+ * Provides access to 1Sat API clients and implements the WalletServices
21
+ * interface required by wallet-toolbox.
22
+ *
23
+ * API Routes:
24
+ * - /1sat/chaintracks/* - Block headers and chain tracking
25
+ * - /1sat/beef/* - Raw transactions and proofs
26
+ * - /1sat/arcade/* - Transaction broadcasting
27
+ * - /1sat/bsv21/* - BSV21 token data
28
+ * - /1sat/txo/* - Transaction outputs
29
+ * - /1sat/owner/* - Address queries and sync
30
+ * - /1sat/ordfs/* - Content/inscription serving
31
+ */
32
+ export class OneSatServices {
33
+ chain;
34
+ baseUrl;
35
+ storage;
36
+ // ===== API Clients =====
37
+ chaintracks;
38
+ beef;
39
+ arcade;
40
+ txo;
41
+ owner;
42
+ ordfs;
43
+ bsv21;
44
+ constructor(chain, baseUrl, storage) {
45
+ this.chain = chain;
46
+ this.baseUrl =
47
+ baseUrl ||
48
+ (chain === "main"
49
+ ? "https://1sat.shruggr.cloud"
50
+ : "https://testnet.api.1sat.app");
51
+ this.storage = storage;
52
+ const opts = { timeout: 30000 };
53
+ this.chaintracks = new ChaintracksClient(this.baseUrl, opts);
54
+ this.beef = new BeefClient(this.baseUrl, opts);
55
+ this.arcade = new ArcadeClient(this.baseUrl, opts);
56
+ this.txo = new TxoClient(this.baseUrl, opts);
57
+ this.owner = new OwnerClient(this.baseUrl, opts);
58
+ this.ordfs = new OrdfsClient(this.baseUrl, opts);
59
+ this.bsv21 = new Bsv21Client(this.baseUrl, opts);
60
+ }
61
+ // ===== Utility Methods =====
62
+ /**
63
+ * Get list of enabled capabilities from the server
64
+ */
65
+ async getCapabilities() {
66
+ const response = await fetch(`${this.baseUrl}/capabilities`);
67
+ if (!response.ok) {
68
+ throw new Error(`Failed to fetch capabilities: ${response.statusText}`);
69
+ }
70
+ return response.json();
71
+ }
72
+ /**
73
+ * Close all client connections
74
+ */
75
+ close() {
76
+ this.chaintracks.close();
77
+ }
78
+ // ===== WalletServices Interface (Required by wallet-toolbox) =====
79
+ async getRawTx(txid, _useNext) {
80
+ // This is a network-only call for the WalletServices interface.
81
+ // Wallet should check storage before calling this.
82
+ try {
83
+ const beefBytes = await this.beef.getBeef(txid);
84
+ const tx = Transaction.fromBEEF(Array.from(beefBytes));
85
+ return { txid, name: "1sat-api", rawTx: Array.from(tx.toBinary()) };
86
+ }
87
+ catch (error) {
88
+ return {
89
+ txid,
90
+ error: new ServiceError("NETWORK_ERROR", error instanceof Error ? error.message : "Unknown error"),
91
+ };
92
+ }
93
+ }
94
+ async getChainTracker() {
95
+ return this.chaintracks;
96
+ }
97
+ async getHeaderForHeight(height) {
98
+ return this.chaintracks.getHeaderBytes(height);
99
+ }
100
+ async getHeight() {
101
+ return this.chaintracks.currentHeight();
102
+ }
103
+ async getMerklePath(txid, _useNext) {
104
+ try {
105
+ const proofBytes = await this.beef.getProof(txid);
106
+ const merklePath = MerklePath.fromBinary(Array.from(proofBytes));
107
+ return { name: "1sat-api", merklePath };
108
+ }
109
+ catch (error) {
110
+ return {
111
+ name: "1sat-api",
112
+ error: new ServiceError("NETWORK_ERROR", error instanceof Error ? error.message : "Unknown error"),
113
+ };
114
+ }
115
+ }
116
+ async postBeef(beef, txids) {
117
+ const results = [];
118
+ for (const txid of txids) {
119
+ try {
120
+ const beefTx = beef.findTxid(txid);
121
+ if (!beefTx?.tx) {
122
+ results.push({
123
+ name: "1sat-api",
124
+ status: "error",
125
+ error: new ServiceError("TX_NOT_FOUND", `Transaction ${txid} not found in BEEF`),
126
+ txidResults: [
127
+ {
128
+ txid,
129
+ status: "error",
130
+ data: { detail: "Transaction not found in BEEF" },
131
+ },
132
+ ],
133
+ });
134
+ continue;
135
+ }
136
+ // Use Extended Format (EF) which includes source transaction data
137
+ const status = await this.arcade.submitTransaction(beefTx.tx.toEF());
138
+ if (status.txStatus === "MINED" ||
139
+ status.txStatus === "SEEN_ON_NETWORK" ||
140
+ status.txStatus === "ACCEPTED_BY_NETWORK") {
141
+ results.push({
142
+ name: "1sat-api",
143
+ status: "success",
144
+ txidResults: [{ txid: status.txid || txid, status: "success" }],
145
+ });
146
+ }
147
+ else if (status.txStatus === "REJECTED" ||
148
+ status.txStatus === "DOUBLE_SPEND_ATTEMPTED") {
149
+ results.push({
150
+ name: "1sat-api",
151
+ status: "error",
152
+ error: new ServiceError(status.txStatus, status.extraInfo || "Transaction rejected"),
153
+ txidResults: [{ txid, status: "error", data: status }],
154
+ });
155
+ }
156
+ else {
157
+ // Still processing - report as success since tx was accepted
158
+ results.push({
159
+ name: "1sat-api",
160
+ status: "success",
161
+ txidResults: [{ txid: status.txid || txid, status: "success" }],
162
+ });
163
+ }
164
+ }
165
+ catch (error) {
166
+ results.push({
167
+ name: "1sat-api",
168
+ status: "error",
169
+ error: new ServiceError("NETWORK_ERROR", error instanceof Error ? error.message : "Unknown error"),
170
+ txidResults: [{ txid, status: "error" }],
171
+ });
172
+ }
173
+ }
174
+ return results;
175
+ }
176
+ async getBeefForTxid(txid) {
177
+ const beefBytes = await this.beef.getBeef(txid);
178
+ return Beef.fromBinary(Array.from(beefBytes));
179
+ }
180
+ hashOutputScript(script) {
181
+ const scriptBin = Utils.toArray(script, "hex");
182
+ return Utils.toHex(Hash.hash256(scriptBin).reverse());
183
+ }
184
+ getServicesCallHistory(_reset) {
185
+ const emptyHistory = {
186
+ serviceName: "",
187
+ historyByProvider: {},
188
+ };
189
+ return {
190
+ version: 1,
191
+ getMerklePath: { ...emptyHistory, serviceName: "getMerklePath" },
192
+ getRawTx: { ...emptyHistory, serviceName: "getRawTx" },
193
+ postBeef: { ...emptyHistory, serviceName: "postBeef" },
194
+ getUtxoStatus: { ...emptyHistory, serviceName: "getUtxoStatus" },
195
+ getStatusForTxids: { ...emptyHistory, serviceName: "getStatusForTxids" },
196
+ getScriptHashHistory: {
197
+ ...emptyHistory,
198
+ serviceName: "getScriptHashHistory",
199
+ },
200
+ updateFiatExchangeRates: {
201
+ ...emptyHistory,
202
+ serviceName: "updateFiatExchangeRates",
203
+ },
204
+ };
205
+ }
206
+ // ===== WalletServices Interface (Not Yet Implemented) =====
207
+ async getBsvExchangeRate() {
208
+ throw new Error("getBsvExchangeRate not yet implemented");
209
+ }
210
+ async getFiatExchangeRate(_currency, _base) {
211
+ throw new Error("getFiatExchangeRate not yet implemented");
212
+ }
213
+ async getStatusForTxids(_txids, _useNext) {
214
+ throw new Error("getStatusForTxids not yet implemented");
215
+ }
216
+ async isUtxo(_output) {
217
+ throw new Error("isUtxo not yet implemented");
218
+ }
219
+ async getUtxoStatus(_output, _outputFormat, _outpoint, _useNext) {
220
+ throw new Error("getUtxoStatus not yet implemented");
221
+ }
222
+ async getScriptHashHistory(_hash, _useNext) {
223
+ throw new Error("getScriptHashHistory not yet implemented");
224
+ }
225
+ async hashToHeader(_hash) {
226
+ throw new Error("hashToHeader not yet implemented");
227
+ }
228
+ async nLockTimeIsFinal(_txOrLockTime) {
229
+ throw new Error("nLockTimeIsFinal not yet implemented");
230
+ }
231
+ }
@@ -0,0 +1,107 @@
1
+ import { Utils } from "@bsv/sdk";
2
+ import { BaseClient } from "./BaseClient";
3
+ /**
4
+ * Client for /1sat/arcade/* routes.
5
+ * Provides transaction broadcast and status checking.
6
+ *
7
+ * Routes:
8
+ * - POST /tx - Submit single transaction
9
+ * - POST /txs - Submit multiple transactions
10
+ * - GET /tx/:txid - Get transaction status
11
+ * - GET /policy - Get mining policy
12
+ * - GET /events - SSE stream of transaction events
13
+ */
14
+ export class ArcadeClient extends BaseClient {
15
+ constructor(baseUrl, options = {}) {
16
+ super(`${baseUrl}/1sat/arcade`, options);
17
+ }
18
+ /**
19
+ * Submit a single transaction for broadcast
20
+ */
21
+ async submitTransaction(rawTx, options) {
22
+ const bytes = rawTx instanceof Uint8Array ? rawTx : new Uint8Array(rawTx);
23
+ return this.request("/tx", {
24
+ method: "POST",
25
+ headers: {
26
+ "Content-Type": "application/octet-stream",
27
+ ...this.buildSubmitHeaders(options),
28
+ },
29
+ body: bytes,
30
+ });
31
+ }
32
+ /**
33
+ * Submit a transaction as hex string
34
+ */
35
+ async submitTransactionHex(rawTxHex, options) {
36
+ return this.submitTransaction(Utils.toArray(rawTxHex, "hex"), options);
37
+ }
38
+ /**
39
+ * Submit multiple transactions for broadcast
40
+ */
41
+ async submitTransactions(rawTxs, options) {
42
+ return this.request("/txs", {
43
+ method: "POST",
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ ...this.buildSubmitHeaders(options),
47
+ },
48
+ body: JSON.stringify(rawTxs.map((tx) => ({
49
+ rawTx: Utils.toHex(tx instanceof Uint8Array ? Array.from(tx) : tx),
50
+ }))),
51
+ });
52
+ }
53
+ /**
54
+ * Get status of a submitted transaction
55
+ */
56
+ async getStatus(txid) {
57
+ return this.request(`/tx/${txid}`);
58
+ }
59
+ /**
60
+ * Get current mining policy
61
+ */
62
+ async getPolicy() {
63
+ return this.request("/policy");
64
+ }
65
+ /**
66
+ * Subscribe to transaction status events via SSE
67
+ * Returns unsubscribe function
68
+ */
69
+ subscribeEvents(callback, callbackToken) {
70
+ const url = callbackToken
71
+ ? `${this.baseUrl}/events?token=${encodeURIComponent(callbackToken)}`
72
+ : `${this.baseUrl}/events`;
73
+ const eventSource = new EventSource(url);
74
+ eventSource.onmessage = (event) => {
75
+ try {
76
+ const status = JSON.parse(event.data);
77
+ callback(status);
78
+ }
79
+ catch {
80
+ // Ignore parse errors
81
+ }
82
+ };
83
+ eventSource.onerror = () => {
84
+ eventSource.close();
85
+ };
86
+ return () => {
87
+ eventSource.close();
88
+ };
89
+ }
90
+ /**
91
+ * Build headers for submit requests
92
+ */
93
+ buildSubmitHeaders(options) {
94
+ const headers = {};
95
+ if (options?.callbackUrl)
96
+ headers["X-CallbackUrl"] = options.callbackUrl;
97
+ if (options?.callbackToken)
98
+ headers["X-CallbackToken"] = options.callbackToken;
99
+ if (options?.fullStatusUpdates)
100
+ headers["X-FullStatusUpdates"] = "true";
101
+ if (options?.skipFeeValidation)
102
+ headers["X-SkipFeeValidation"] = "true";
103
+ if (options?.skipScriptValidation)
104
+ headers["X-SkipScriptValidation"] = "true";
105
+ return headers;
106
+ }
107
+ }
@@ -0,0 +1,125 @@
1
+ import { HttpError } from "../../errors";
2
+ /**
3
+ * Base client with shared HTTP utilities for all 1sat-stack API clients.
4
+ * Provides timeout handling, error parsing, and request helpers.
5
+ */
6
+ export class BaseClient {
7
+ baseUrl;
8
+ timeout;
9
+ fetchFn;
10
+ constructor(baseUrl, options = {}) {
11
+ this.baseUrl = baseUrl.replace(/\/$/, "");
12
+ this.timeout = options.timeout ?? 30000;
13
+ this.fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
14
+ }
15
+ /**
16
+ * Make a JSON request and parse the response
17
+ */
18
+ async request(path, init) {
19
+ const controller = new AbortController();
20
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
21
+ try {
22
+ const response = await this.fetchFn(`${this.baseUrl}${path}`, {
23
+ ...init,
24
+ signal: controller.signal,
25
+ });
26
+ if (!response.ok) {
27
+ throw await this.parseError(response);
28
+ }
29
+ // Handle empty responses
30
+ const text = await response.text();
31
+ if (!text) {
32
+ return undefined;
33
+ }
34
+ return JSON.parse(text);
35
+ }
36
+ finally {
37
+ clearTimeout(timeoutId);
38
+ }
39
+ }
40
+ /**
41
+ * Make a request and return raw binary data
42
+ */
43
+ async requestBinary(path, init) {
44
+ const controller = new AbortController();
45
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
46
+ try {
47
+ const response = await this.fetchFn(`${this.baseUrl}${path}`, {
48
+ ...init,
49
+ signal: controller.signal,
50
+ });
51
+ if (!response.ok) {
52
+ throw await this.parseError(response);
53
+ }
54
+ const arrayBuffer = await response.arrayBuffer();
55
+ return new Uint8Array(arrayBuffer);
56
+ }
57
+ finally {
58
+ clearTimeout(timeoutId);
59
+ }
60
+ }
61
+ /**
62
+ * Make a request and return the raw Response object
63
+ * Useful for streaming responses
64
+ */
65
+ async requestRaw(path, init) {
66
+ const controller = new AbortController();
67
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
68
+ try {
69
+ const response = await this.fetchFn(`${this.baseUrl}${path}`, {
70
+ ...init,
71
+ signal: controller.signal,
72
+ });
73
+ if (!response.ok) {
74
+ throw await this.parseError(response);
75
+ }
76
+ return response;
77
+ }
78
+ finally {
79
+ clearTimeout(timeoutId);
80
+ }
81
+ }
82
+ /**
83
+ * Parse error response into HttpError
84
+ */
85
+ async parseError(response) {
86
+ try {
87
+ const data = await response.json();
88
+ const message = data.message || data.error || data.detail || response.statusText;
89
+ return new HttpError(response.status, message);
90
+ }
91
+ catch {
92
+ try {
93
+ const text = await response.text();
94
+ return new HttpError(response.status, text || response.statusText);
95
+ }
96
+ catch {
97
+ return new HttpError(response.status, response.statusText);
98
+ }
99
+ }
100
+ }
101
+ /**
102
+ * Build query string from options object
103
+ */
104
+ buildQueryString(params) {
105
+ const entries = [];
106
+ for (const [key, value] of Object.entries(params)) {
107
+ if (value === undefined)
108
+ continue;
109
+ if (Array.isArray(value)) {
110
+ if (value.length > 0) {
111
+ entries.push(`${key}=${encodeURIComponent(value.join(","))}`);
112
+ }
113
+ }
114
+ else if (typeof value === "boolean") {
115
+ if (value) {
116
+ entries.push(`${key}=true`);
117
+ }
118
+ }
119
+ else {
120
+ entries.push(`${key}=${encodeURIComponent(String(value))}`);
121
+ }
122
+ }
123
+ return entries.length > 0 ? `?${entries.join("&")}` : "";
124
+ }
125
+ }
@@ -0,0 +1,33 @@
1
+ import { BaseClient } from "./BaseClient";
2
+ /**
3
+ * Client for /1sat/beef/* routes.
4
+ * Provides BEEF data, raw transactions, and merkle proofs.
5
+ *
6
+ * Routes:
7
+ * - GET /:txid - Get BEEF for transaction
8
+ * - GET /:txid/raw - Get raw transaction bytes
9
+ * - GET /:txid/proof - Get merkle proof
10
+ */
11
+ export class BeefClient extends BaseClient {
12
+ constructor(baseUrl, options = {}) {
13
+ super(`${baseUrl}/1sat/beef`, options);
14
+ }
15
+ /**
16
+ * Get BEEF (Background Evaluation Extended Format) for a transaction
17
+ */
18
+ async getBeef(txid) {
19
+ return this.requestBinary(`/${txid}`);
20
+ }
21
+ /**
22
+ * Get raw transaction bytes
23
+ */
24
+ async getRawTx(txid) {
25
+ return this.requestBinary(`/${txid}/tx`);
26
+ }
27
+ /**
28
+ * Get merkle proof bytes for a mined transaction
29
+ */
30
+ async getProof(txid) {
31
+ return this.requestBinary(`/${txid}/proof`);
32
+ }
33
+ }
@@ -0,0 +1,65 @@
1
+ import { BaseClient } from "./BaseClient";
2
+ /**
3
+ * Client for /1sat/bsv21/* routes.
4
+ * Provides BSV21 token queries.
5
+ *
6
+ * Routes:
7
+ * - GET /:tokenId - Get token details
8
+ * - GET /:tokenId/blk/:height - Get token data at block height
9
+ * - GET /:tokenId/tx/:txid - Get token data for transaction
10
+ * - GET /:tokenId/:lockType/:address/balance - Get token balance
11
+ * - GET /:tokenId/:lockType/:address/unspent - Get unspent token UTXOs
12
+ * - GET /:tokenId/:lockType/:address/history - Get token transaction history
13
+ */
14
+ export class Bsv21Client extends BaseClient {
15
+ cache = new Map();
16
+ constructor(baseUrl, options = {}) {
17
+ super(`${baseUrl}/1sat/bsv21`, options);
18
+ }
19
+ /**
20
+ * Get token details (deploy data).
21
+ * Results are cached since token details are immutable.
22
+ */
23
+ async getTokenDetails(tokenId) {
24
+ const cached = this.cache.get(tokenId);
25
+ if (cached)
26
+ return cached;
27
+ const details = await this.request(`/${tokenId}`);
28
+ this.cache.set(tokenId, details);
29
+ return details;
30
+ }
31
+ /**
32
+ * Get token transaction data for a specific txid
33
+ */
34
+ async getTokenByTxid(tokenId, txid) {
35
+ return this.request(`/${tokenId}/tx/${txid}`);
36
+ }
37
+ /**
38
+ * Get token balance for an address
39
+ * @param tokenId - Token ID (outpoint of deploy tx)
40
+ * @param lockType - Lock type (e.g., 'p2pkh', 'ordlock')
41
+ * @param address - Address to check
42
+ */
43
+ async getBalance(tokenId, lockType, address) {
44
+ const data = await this.request(`/${tokenId}/${lockType}/${address}/balance`);
45
+ return BigInt(data.balance);
46
+ }
47
+ /**
48
+ * Get unspent token UTXOs for an address
49
+ */
50
+ async getUnspent(tokenId, lockType, address) {
51
+ return this.request(`/${tokenId}/${lockType}/${address}/unspent`);
52
+ }
53
+ /**
54
+ * Get token transaction history for an address
55
+ */
56
+ async getHistory(tokenId, lockType, address) {
57
+ return this.request(`/${tokenId}/${lockType}/${address}/history`);
58
+ }
59
+ /**
60
+ * Clear the token details cache
61
+ */
62
+ clearCache() {
63
+ this.cache.clear();
64
+ }
65
+ }