zeldhash-miner 0.2.5 → 0.3.1

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 CHANGED
@@ -95,7 +95,7 @@ Creates a new miner instance.
95
95
  interface MineParams {
96
96
  inputs: TxInput[]; // UTXOs to spend
97
97
  outputs: TxOutput[]; // Destinations (one must be change: true)
98
- targetZeros: number; // Leading zero hex digits (1–32)
98
+ targetZeros: number; // Leading zero hex digits (0–32)
99
99
  startNonce?: bigint; // Starting point (default 0n)
100
100
  batchSize?: number; // Override instance batch size
101
101
  distribution?: bigint[]; // Optional ZELD distribution values
@@ -194,7 +194,6 @@ try {
194
194
  | `INVALID_ADDRESS` | Address parsing failed |
195
195
  | `UNSUPPORTED_ADDRESS_TYPE` | Only P2WPKH and P2TR supported |
196
196
  | `INSUFFICIENT_FUNDS` | Inputs don't cover outputs + fees |
197
- | `NO_CHANGE_OUTPUT` | No output marked as change |
198
197
  | `MULTIPLE_CHANGE_OUTPUTS` | More than one change output |
199
198
  | `INVALID_INPUT` | Bad parameter |
200
199
  | `WEBGPU_NOT_AVAILABLE` | WebGPU requested but unavailable |
@@ -202,36 +201,49 @@ try {
202
201
  | `MINING_ABORTED` | Mining was stopped |
203
202
  | `DUST_OUTPUT` | Output below dust limit (310 sats P2WPKH / 330 sats P2TR) |
204
203
 
204
+ Notes:
205
+ - A change output is optional. If provided but the computed change would be below the dust limit, the change is omitted and the extra sats are counted as fees.
206
+ - You can also build transactions with no change output (e.g., sweeping a wallet).
207
+
205
208
  ## Runtime Notes
206
209
 
207
- ### WASM Asset Loading
210
+ ### Asset Setup
211
+
212
+ The SDK requires WASM and worker files to be served from your application's public folder. All assets are bundled in a single `assets/` folder for easy setup:
208
213
 
209
- The SDK automatically loads WASM assets from `/wasm/` on your application's origin. This works out of the box with most setups:
214
+ ```bash
215
+ # Copy all required assets to your public folder
216
+ cp -r node_modules/zeldhash-miner/assets public/zeldhash-miner
217
+ ```
210
218
 
211
- 1. **Copy assets to your public folder**: Copy the `wasm/` folder from `node_modules/zeldhash-miner/` to your app's `public/` directory:
212
- ```bash
213
- cp -r node_modules/zeldhash-miner/wasm public/wasm
214
- ```
219
+ Or add a postinstall script to your `package.json`:
220
+ ```json
221
+ {
222
+ "scripts": {
223
+ "postinstall": "cp -r node_modules/zeldhash-miner/assets public/zeldhash-miner"
224
+ }
225
+ }
226
+ ```
215
227
 
216
- 2. **Also copy the worker**: The worker script needs to be served from your app as well:
217
- ```bash
218
- cp node_modules/zeldhash-miner/dist/worker.js public/worker.js
219
- ```
228
+ This copies 3 files to `public/zeldhash-miner/`:
229
+ - `zeldhash_miner_wasm.js` - WASM JavaScript bindings
230
+ - `zeldhash_miner_wasm_bg.wasm` - WASM binary
231
+ - `worker.js` - Web Worker for mining
220
232
 
221
- The SDK bootstraps `globalThis.__ZELDMINER_WASM_BASE__` to `/wasm/` (relative to your app's origin) automatically, so WASM files are fetched from `https://your-app.com/wasm/` rather than from inside `node_modules/`.
233
+ The SDK automatically loads assets from `/zeldhash-miner/` on your application's origin.
222
234
 
223
- ### Custom WASM Path
235
+ ### Custom Asset Path
224
236
 
225
- If you need to serve WASM assets from a different location, you can override the base path:
237
+ If you need to serve assets from a different location, override the base path before importing:
226
238
 
227
239
  ```ts
228
240
  // Set before importing ZeldMiner
229
- globalThis.__ZELDMINER_WASM_BASE__ = "/custom/path/to/wasm/";
241
+ globalThis.__ZELDMINER_WASM_BASE__ = "/custom/path/to/assets/";
230
242
  ```
231
243
 
232
244
  Or use environment variables with Vite:
233
245
  ```bash
234
- VITE_ZELDMINER_WASM_BASE=/custom/wasm/
246
+ VITE_ZELDMINER_WASM_BASE=/custom/assets/
235
247
  ```
236
248
 
237
249
  ### WebGPU
@@ -0,0 +1,354 @@
1
+ var u = /* @__PURE__ */ ((t) => (t.INVALID_ADDRESS = "INVALID_ADDRESS", t.UNSUPPORTED_ADDRESS_TYPE = "UNSUPPORTED_ADDRESS_TYPE", t.INSUFFICIENT_FUNDS = "INSUFFICIENT_FUNDS", t.MULTIPLE_CHANGE_OUTPUTS = "MULTIPLE_CHANGE_OUTPUTS", t.INVALID_INPUT = "INVALID_INPUT", t.WEBGPU_NOT_AVAILABLE = "WEBGPU_NOT_AVAILABLE", t.WORKER_ERROR = "WORKER_ERROR", t.MINING_ABORTED = "MINING_ABORTED", t.DUST_OUTPUT = "DUST_OUTPUT", t))(u || {});
2
+ const N = (1n << 64n) - 1n, C = (t) => {
3
+ if (t < 0n)
4
+ throw new Error("nonce must be non-negative");
5
+ if (t === 0n) return 1;
6
+ let e = 0, n = t;
7
+ for (; n > 0n; )
8
+ e += 1, n >>= 8n;
9
+ return e;
10
+ }, X = (t) => {
11
+ if (t < 0n)
12
+ throw new Error("nonce must be non-negative");
13
+ if (t <= 23n) return 1;
14
+ if (t <= 0xffn) return 2;
15
+ if (t <= 0xffffn) return 3;
16
+ if (t <= 0xffffffffn) return 5;
17
+ if (t <= N) return 9;
18
+ throw new Error("nonce range exceeds u64");
19
+ }, j = (t) => {
20
+ if (!Number.isInteger(t) || t <= 0 || t > 8)
21
+ throw new Error("nonceLength must be between 1 and 8");
22
+ return (1n << BigInt(t * 8)) - 1n;
23
+ }, H = (t) => {
24
+ switch (t) {
25
+ case 1:
26
+ return 23n;
27
+ case 2:
28
+ return 0xffn;
29
+ case 3:
30
+ return 0xffffn;
31
+ case 5:
32
+ return 0xffffffffn;
33
+ case 9:
34
+ return N;
35
+ default:
36
+ throw new Error("cbor nonceLength must be one of 1, 2, 3, 5, 9");
37
+ }
38
+ }, Y = (t, e) => {
39
+ if (t < 0n)
40
+ throw new Error("startNonce must be non-negative");
41
+ if (!Number.isInteger(e) || e <= 0)
42
+ throw new Error("batchSize must be a positive integer");
43
+ const n = t + BigInt(e - 1);
44
+ if (n > N)
45
+ throw new Error("nonce range exceeds u64");
46
+ const r = [];
47
+ let i = t;
48
+ for (; i <= n; ) {
49
+ const s = C(i), o = j(s), a = n < o ? n : o, l = a - i + 1n;
50
+ if (l > BigInt(Number.MAX_SAFE_INTEGER))
51
+ throw new Error("segment size exceeds safe integer range");
52
+ if (r.push({
53
+ start: i,
54
+ size: Number(l),
55
+ nonceLength: s
56
+ }), a === n)
57
+ break;
58
+ i = a + 1n;
59
+ }
60
+ return r;
61
+ }, J = (t, e) => {
62
+ if (t < 0n)
63
+ throw new Error("startNonce must be non-negative");
64
+ if (!Number.isInteger(e) || e <= 0)
65
+ throw new Error("batchSize must be a positive integer");
66
+ const n = t + BigInt(e - 1);
67
+ if (n > N)
68
+ throw new Error("nonce range exceeds u64");
69
+ const r = [];
70
+ let i = t;
71
+ for (; i <= n; ) {
72
+ const s = X(i), o = H(s), a = n < o ? n : o, l = a - i + 1n;
73
+ if (l > BigInt(Number.MAX_SAFE_INTEGER))
74
+ throw new Error("segment size exceeds safe integer range");
75
+ if (r.push({
76
+ start: i,
77
+ size: Number(l),
78
+ nonceLength: s
79
+ }), a === n)
80
+ break;
81
+ i = a + 1n;
82
+ }
83
+ return r;
84
+ }, Q = {};
85
+ if (typeof globalThis.__ZELDMINER_WASM_BASE__ > "u")
86
+ try {
87
+ const t = typeof window < "u" && window.location?.origin ? window.location.origin : typeof self < "u" && self.location?.origin ? self.location.origin : "http://localhost";
88
+ globalThis.__ZELDMINER_WASM_BASE__ = new URL("/zeldhash-miner/", t).href;
89
+ } catch {
90
+ globalThis.__ZELDMINER_WASM_BASE__ = "/zeldhash-miner/";
91
+ }
92
+ let T = null, w = null, W = !1;
93
+ const Z = () => {
94
+ if (W) return;
95
+ W = !0;
96
+ const t = globalThis.GPUAdapter?.prototype, e = t?.requestDevice;
97
+ !t || typeof e != "function" || (t.requestDevice = function(r) {
98
+ if (r?.requiredLimits && typeof this.limits == "object") {
99
+ const i = r.requiredLimits, s = this.limits;
100
+ for (const o of Object.keys(i))
101
+ (!(o in s) || s[o] === void 0) && delete i[o];
102
+ }
103
+ return e.call(this, r);
104
+ });
105
+ }, B = (t) => t.endsWith("/") ? t : `${t}/`, A = (t) => {
106
+ const e = t.trim();
107
+ return e && (typeof window < "u" && typeof window.location?.origin == "string" ? B(new URL(e, window.location.origin).href) : B(new URL(e, import.meta.url).href));
108
+ }, tt = () => {
109
+ const t = globalThis.__ZELDMINER_WASM_BASE__;
110
+ if (typeof t == "string" && t.trim())
111
+ return A(t);
112
+ const e = Q?.VITE_ZELDMINER_WASM_BASE;
113
+ if (typeof e == "string" && e.trim())
114
+ return A(e);
115
+ const n = "/";
116
+ return n.trim() ? A(`${B(n.trim())}zeldhash-miner/`) : A("/zeldhash-miner/");
117
+ }, x = tt(), z = `${x}zeldhash_miner_wasm.js`, et = `${x}zeldhash_miner_wasm_bg.wasm`, nt = async (t) => (0, eval)("s => import(s)")(t), O = (t) => t instanceof Error ? t.message : String(t), rt = async () => {
118
+ Z();
119
+ let t;
120
+ try {
121
+ t = await nt(
122
+ /* @vite-ignore */
123
+ z
124
+ );
125
+ } catch (r) {
126
+ throw new Error(
127
+ `Failed to import WASM bundle (${z}). Did you run ./scripts/build-wasm.sh? (${O(r)})`
128
+ );
129
+ }
130
+ const e = t.default;
131
+ if (typeof e != "function")
132
+ throw new Error("WASM init function is missing from the bundle.");
133
+ try {
134
+ const r = new URL(et, import.meta.url);
135
+ await e({ module_or_path: r });
136
+ } catch (r) {
137
+ throw new Error(
138
+ `Failed to initialize WASM bundle: ${O(r)}`
139
+ );
140
+ }
141
+ const n = t;
142
+ try {
143
+ n.init_panic_hook?.();
144
+ } catch {
145
+ }
146
+ return n;
147
+ }, it = async () => T || (w || (w = rt().then((t) => (T = t, t)).catch((t) => {
148
+ throw w = null, t;
149
+ })), w);
150
+ if (typeof globalThis.__ZELDMINER_WASM_BASE__ > "u")
151
+ try {
152
+ const t = typeof self < "u" && self.location?.origin ? self.location.origin : "http://localhost";
153
+ globalThis.__ZELDMINER_WASM_BASE__ = new URL("/zeldhash-miner/", t).href;
154
+ } catch {
155
+ globalThis.__ZELDMINER_WASM_BASE__ = "/zeldhash-miner/";
156
+ }
157
+ const U = self, ot = U.name ?? void 0, I = (t) => t instanceof Error ? t.message : String(t);
158
+ let R = "cpu", b = null;
159
+ const d = (t) => {
160
+ U.postMessage({ ...t, workerId: ot });
161
+ }, h = (t, e = u.WORKER_ERROR, n) => {
162
+ d({ type: "error", message: t, code: e, details: n });
163
+ }, st = (t) => typeof t == "object" && t !== null && "ok" in t, at = (t) => typeof t == "object" && t !== null && "nonce" in t && "txid" in t, ct = (t) => {
164
+ const e = BigInt(Number.MAX_SAFE_INTEGER);
165
+ return t > e ? Number.MAX_SAFE_INTEGER : t < -e ? -Number.MAX_SAFE_INTEGER : Number(t);
166
+ }, ut = (t) => ({
167
+ nonceLength: t.nonceLength,
168
+ prefix: new Uint8Array(t.prefix),
169
+ suffix: new Uint8Array(t.suffix),
170
+ useCborNonce: t.useCborNonce
171
+ }), F = (t, e) => {
172
+ t.set(e.nonceLength, ut(e));
173
+ }, lt = async (t, e, n, r, i) => {
174
+ const s = t.get(r.nonceLength);
175
+ if (s)
176
+ return s;
177
+ const o = e.build_mining_template(
178
+ n.inputs,
179
+ n.outputs,
180
+ i,
181
+ BigInt(n.satsPerVbyte),
182
+ r.start,
183
+ r.size,
184
+ n.distribution ?? null
185
+ ), a = {
186
+ ...o,
187
+ nonceLength: r.nonceLength,
188
+ useCborNonce: o.useCborNonce ?? n.useCborNonce
189
+ };
190
+ return F(t, a), a;
191
+ }, ft = async (t, e, n, r, i, s, o, a) => {
192
+ if (e === "gpu") {
193
+ if (!t.mine_batch_gpu)
194
+ throw new Error("GPU mining requested but mine_batch_gpu is unavailable");
195
+ return t.mine_batch_gpu(
196
+ n,
197
+ r,
198
+ i,
199
+ s,
200
+ o,
201
+ a
202
+ );
203
+ }
204
+ return t.mine_batch_wasm(
205
+ n,
206
+ r,
207
+ i,
208
+ s,
209
+ o,
210
+ a
211
+ );
212
+ }, _t = async (t, e) => {
213
+ let n;
214
+ try {
215
+ if (n = await it(), R === "gpu") {
216
+ if (!n.mine_batch_gpu) {
217
+ h(
218
+ "GPU mining requested but mine_batch_gpu is unavailable",
219
+ u.WEBGPU_NOT_AVAILABLE
220
+ );
221
+ return;
222
+ }
223
+ n.init_gpu && await n.init_gpu();
224
+ }
225
+ } catch (g) {
226
+ const m = I(g), E = R === "gpu" ? u.WEBGPU_NOT_AVAILABLE : u.WORKER_ERROR;
227
+ h(`Failed to initialize WASM: ${m}`, E);
228
+ return;
229
+ }
230
+ const r = /* @__PURE__ */ new Map(), i = t.template.useCborNonce ?? !!(t.distribution && t.distribution.length > 0);
231
+ F(r, { ...t.template, useCborNonce: i });
232
+ const s = t.nonceStep ?? BigInt(t.batchSize), o = t.network === "signet" ? "testnet" : t.network;
233
+ let a = t.startNonce, l = 0n;
234
+ const G = performance.now();
235
+ for (; !e.signal.aborted; ) {
236
+ const g = a;
237
+ let m = t.batchSize, E = 0n;
238
+ for (; m > 0 && !e.signal.aborted; ) {
239
+ const D = g + E;
240
+ let c;
241
+ try {
242
+ c = (i ? J(D, m) : Y(D, m))[0];
243
+ } catch (_) {
244
+ h(
245
+ `Invalid nonce range: ${I(_)}`,
246
+ u.INVALID_INPUT
247
+ ), e.abort();
248
+ return;
249
+ }
250
+ let S;
251
+ try {
252
+ S = await lt(
253
+ r,
254
+ n,
255
+ {
256
+ inputs: t.inputs,
257
+ outputs: t.outputs,
258
+ satsPerVbyte: t.satsPerVbyte,
259
+ distribution: t.distribution,
260
+ useCborNonce: i
261
+ },
262
+ c,
263
+ o
264
+ );
265
+ } catch (_) {
266
+ h(
267
+ `Failed to build mining template: ${I(_)}`,
268
+ u.WORKER_ERROR
269
+ ), e.abort();
270
+ return;
271
+ }
272
+ let f;
273
+ const k = performance.now();
274
+ try {
275
+ f = await ft(
276
+ n,
277
+ R,
278
+ S.prefix,
279
+ S.suffix,
280
+ c.start,
281
+ c.size,
282
+ t.targetZeros,
283
+ i
284
+ );
285
+ } catch (_) {
286
+ const p = I(_);
287
+ h(
288
+ `Batch mining failed: ${p}`,
289
+ u.WORKER_ERROR
290
+ ), e.abort();
291
+ return;
292
+ }
293
+ const M = performance.now() - k;
294
+ if (st(f)) {
295
+ if (!f.ok) {
296
+ h(
297
+ f.error ?? "Validation failed",
298
+ u.INVALID_INPUT
299
+ ), e.abort();
300
+ return;
301
+ }
302
+ } else if (at(f)) {
303
+ const _ = l + E, p = BigInt(f.nonce) - c.start + 1n, L = _ + p, y = performance.now() - G, P = y > 0 ? ct(L) / (y / 1e3) : 0, q = c.start + p - 1n, K = {
304
+ psbt: "",
305
+ txid: f.txid,
306
+ nonce: BigInt(f.nonce),
307
+ attempts: L,
308
+ duration: y,
309
+ hashRate: P
310
+ };
311
+ d({
312
+ type: "found",
313
+ result: K,
314
+ hashesProcessed: L,
315
+ hashRate: P,
316
+ lastNonce: q
317
+ }), e.abort();
318
+ return;
319
+ }
320
+ l += BigInt(c.size), E += BigInt(c.size), m -= c.size;
321
+ const v = M > 0 ? c.size / (M / 1e3) : c.size, $ = c.start + BigInt(c.size) - 1n;
322
+ d({ type: "progress", hashesProcessed: l, hashRate: v, lastNonce: $ });
323
+ }
324
+ if (e.signal.aborted)
325
+ break;
326
+ const V = g + BigInt(t.batchSize) - 1n;
327
+ d({ type: "batch_complete", lastNonce: V }), a = g + s;
328
+ }
329
+ }, ht = (t) => {
330
+ const e = new AbortController();
331
+ b?.abort(), b = e, _t(t, e).finally(() => {
332
+ b === e && (b = null);
333
+ });
334
+ };
335
+ U.addEventListener("message", (t) => {
336
+ const e = t.data;
337
+ switch (e.type) {
338
+ case "init":
339
+ R = e.mode, d({ type: "ready" });
340
+ break;
341
+ case "mine":
342
+ ht(e);
343
+ break;
344
+ case "stop":
345
+ b?.abort();
346
+ break;
347
+ default:
348
+ h(
349
+ `Unknown message type: ${e.type}`,
350
+ u.WORKER_ERROR
351
+ );
352
+ }
353
+ });
354
+ //# sourceMappingURL=worker.js.map