@spirobel/monero-wallet-api 0.2.0 → 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.
Files changed (68) hide show
  1. package/README.md +3 -3
  2. package/dist/api.d.ts +25 -96
  3. package/dist/api.js +23 -175
  4. package/dist/io/BunFileInterface.d.ts +32 -0
  5. package/dist/io/atomicWrite.d.ts +2 -0
  6. package/dist/io/atomicWrite.js +10 -0
  7. package/dist/io/extension.d.ts +18 -0
  8. package/dist/io/extension.js +11 -0
  9. package/dist/io/indexedDB.d.ts +45 -0
  10. package/dist/io/indexedDB.js +221 -0
  11. package/dist/io/readDir.d.ts +1 -0
  12. package/dist/io/readDir.js +7 -0
  13. package/dist/io/sleep.d.ts +1 -0
  14. package/dist/io/sleep.js +1 -0
  15. package/dist/keypairs-seeds/keypairs.d.ts +29 -0
  16. package/dist/keypairs-seeds/keypairs.js +207 -0
  17. package/dist/keypairs-seeds/writeKeypairs.d.ts +12 -0
  18. package/dist/keypairs-seeds/writeKeypairs.js +78 -0
  19. package/dist/node-interaction/binaryEndpoints.d.ts +59 -14
  20. package/dist/node-interaction/binaryEndpoints.js +110 -54
  21. package/dist/node-interaction/jsonEndpoints.d.ts +249 -187
  22. package/dist/node-interaction/jsonEndpoints.js +287 -0
  23. package/dist/node-interaction/nodeUrl.d.ts +129 -0
  24. package/dist/node-interaction/nodeUrl.js +113 -0
  25. package/dist/scanning-syncing/backgroundWorker.d.ts +6 -0
  26. package/dist/scanning-syncing/backgroundWorker.js +56 -0
  27. package/dist/scanning-syncing/connectionStatus.d.ts +15 -0
  28. package/dist/scanning-syncing/connectionStatus.js +35 -0
  29. package/dist/scanning-syncing/openWallet.d.ts +28 -0
  30. package/dist/scanning-syncing/openWallet.js +57 -0
  31. package/dist/scanning-syncing/scanSettings.d.ts +96 -0
  32. package/dist/scanning-syncing/scanSettings.js +240 -0
  33. package/dist/scanning-syncing/scanresult/computeKeyImage.d.ts +3 -0
  34. package/dist/scanning-syncing/scanresult/computeKeyImage.js +21 -0
  35. package/dist/scanning-syncing/scanresult/getBlocksbinBuffer.d.ts +28 -0
  36. package/dist/scanning-syncing/scanresult/getBlocksbinBuffer.js +52 -0
  37. package/dist/scanning-syncing/scanresult/reorg.d.ts +14 -0
  38. package/dist/scanning-syncing/scanresult/reorg.js +78 -0
  39. package/dist/scanning-syncing/scanresult/scanCache.d.ts +84 -0
  40. package/dist/scanning-syncing/scanresult/scanCache.js +134 -0
  41. package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +149 -0
  42. package/dist/scanning-syncing/scanresult/scanCacheOpened.js +648 -0
  43. package/dist/scanning-syncing/scanresult/scanResult.d.ts +64 -0
  44. package/dist/scanning-syncing/scanresult/scanResult.js +213 -0
  45. package/dist/scanning-syncing/scanresult/scanStats.d.ts +60 -0
  46. package/dist/scanning-syncing/scanresult/scanStats.js +273 -0
  47. package/dist/scanning-syncing/worker-entrypoints/worker.d.ts +1 -0
  48. package/dist/scanning-syncing/worker-entrypoints/worker.js +8 -0
  49. package/dist/scanning-syncing/worker-mains/worker.d.ts +1 -0
  50. package/dist/scanning-syncing/worker-mains/worker.js +7 -0
  51. package/dist/send-functionality/conversion.d.ts +4 -0
  52. package/dist/send-functionality/conversion.js +75 -0
  53. package/dist/send-functionality/inputSelection.d.ts +13 -0
  54. package/dist/send-functionality/inputSelection.js +8 -0
  55. package/dist/send-functionality/transactionBuilding.d.ts +51 -0
  56. package/dist/send-functionality/transactionBuilding.js +111 -0
  57. package/dist/tools/monero-tools.d.ts +46 -0
  58. package/dist/tools/monero-tools.js +165 -0
  59. package/dist/viewpair/ViewPair.d.ts +157 -0
  60. package/dist/viewpair/ViewPair.js +346 -0
  61. package/dist/wasm-processing/wasi.js +1 -2
  62. package/dist/wasm-processing/wasmFile.d.ts +1 -1
  63. package/dist/wasm-processing/wasmFile.js +2 -2
  64. package/dist/wasm-processing/wasmProcessor.d.ts +16 -4
  65. package/dist/wasm-processing/wasmProcessor.js +23 -7
  66. package/package.json +29 -6
  67. package/dist/testscrap.js +0 -36
  68. /package/dist/{testscrap.d.ts → io/BunFileInterface.js} +0 -0
@@ -0,0 +1,346 @@
1
+ import { processScanResult, } from "../scanning-syncing/scanresult/scanResult";
2
+ export {};
3
+ export { NodeUrl } from "../node-interaction/nodeUrl";
4
+ import { getBlocksBinScan, getBlocksBinExecuteRequest, getBlocksBinScanResponse, MAINNET_GENESIS_BLOCK_HASH, STAGENET_GENESIS_BLOCK_HASH, } from "../node-interaction/binaryEndpoints";
5
+ import { handleScanError, lastRange, writeCacheFileDefaultLocationThrows, } from "../scanning-syncing/scanresult/scanCache";
6
+ import { makeTransaction, } from "../send-functionality/transactionBuilding";
7
+ import { WasmProcessor } from "../wasm-processing/wasmProcessor";
8
+ import { LOCAL_NODE_DEFAULT_URL } from "../node-interaction/nodeUrl";
9
+ import { atomicWrite, get_block_headers_range, get_info, } from "../api";
10
+ import { readGetblocksBinBuffer, trimGetBlocksBinBuffer, writeGetblocksBinBuffer, } from "../scanning-syncing/scanresult/getBlocksbinBuffer";
11
+ import { initScanCache, readCacheFileDefaultLocation, } from "../scanning-syncing/scanresult/scanCache";
12
+ import { cullTooLargeScanHeight, openNonHaltedWallets, readWalletFromScanSettings, walletSettingsPlusKeys, } from "../scanning-syncing/scanSettings";
13
+ import { sleep } from "../io/sleep";
14
+ import { readWriteConnectionStatusFile, writeConnectionStatusFile, } from "../scanning-syncing/connectionStatus";
15
+ /**
16
+ * This class is useful to interact with Moneros DaemonRpc binary requests in a convenient way.
17
+ * (similar to how you would interact with a REST api that gives you json back.)
18
+ * The wasm part will handle the creation of the binary requests and parse the responses and then parse them
19
+ * and return outputs that belong to the ViewPair.
20
+ * {@link https://docs.getmonero.org/rpc-library/monerod-rpc/#get_blocksbin}
21
+ */
22
+ export class ViewPair extends WasmProcessor {
23
+ node_url;
24
+ primary_address;
25
+ _network;
26
+ get network() {
27
+ return this._network; // we set this in ViewPair.create()
28
+ }
29
+ _genesis_hash;
30
+ get genesis_hash() {
31
+ if (!this._genesis_hash) {
32
+ throw new Error("Genesis hash not set. Node not connected?");
33
+ }
34
+ return this._genesis_hash; // set in first call to ViewPair.getBlocksBin, if params.block_ids is supplied
35
+ }
36
+ constructor(node_url, primary_address) {
37
+ super();
38
+ this.node_url = node_url;
39
+ this.primary_address = primary_address;
40
+ }
41
+ static async create(primary_address, secret_view_key, subaddress_index = 0, node_url) {
42
+ const viewPair = new ViewPair(node_url || LOCAL_NODE_DEFAULT_URL, primary_address);
43
+ const tinywasi = await viewPair.initWasmModule();
44
+ viewPair.writeToWasmMemory = (ptr, len) => {
45
+ viewPair.writeString(ptr, len, primary_address);
46
+ viewPair.writeToWasmMemory = (ptr, len) => {
47
+ viewPair.writeString(ptr, len, secret_view_key);
48
+ };
49
+ };
50
+ let init_viewpair_result = undefined;
51
+ viewPair.readFromWasmMemory = (ptr, len) => {
52
+ init_viewpair_result = JSON.parse(viewPair.readString(ptr, len));
53
+ };
54
+ //@ts-ignore
55
+ tinywasi.instance.exports.init_viewpair(primary_address.length, secret_view_key.length, subaddress_index);
56
+ if (!init_viewpair_result) {
57
+ throw new Error("Failed to init viewpair");
58
+ }
59
+ else {
60
+ //@ts-ignore
61
+ viewPair._network = init_viewpair_result.network;
62
+ }
63
+ return viewPair;
64
+ }
65
+ /**
66
+ * This function helps with making requests to the get_blocks.bin endpoint of the Monerod nodes. It does the Request and returns the outputs that belong to the ViewPair.
67
+ * (if outputs are found in the blocks that are returned)
68
+ *
69
+ * if params.block_ids is supplied, it will add the genesis hash to the end of the block_ids array.
70
+ * (so you can just supply the block_id you want to start fetching from)
71
+ * @link https://docs.getmonero.org/rpc-library/monerod-rpc/#get_blocksbin
72
+ * @param params params that will be turned into epee (monero lib that does binary serialization)
73
+ * @param metaCallBack contains meta information about the getBlocksbin call (new sync height = start_height param + number of blocks)
74
+ * @param stopSync optional AbortSignal to stop the syncing process
75
+ * @returns The difference to the same method on NodeUrl is: It returns {@link ScanResult} (outputs that belong to viewpair) and not just the blocks as json.
76
+ */
77
+ async getBlocksBin(params, metaCallBack, stopSync) {
78
+ return await getBlocksBinScan(this, await this.addGenesisHashToBlockIds(params), metaCallBack, stopSync);
79
+ }
80
+ async addGenesisHashToBlockIds(params) {
81
+ if (params.block_ids) {
82
+ if (!this._genesis_hash && this.network === "mainnet") {
83
+ this._genesis_hash = MAINNET_GENESIS_BLOCK_HASH;
84
+ }
85
+ if (!this._genesis_hash && this.network === "stagenet") {
86
+ this._genesis_hash = STAGENET_GENESIS_BLOCK_HASH;
87
+ }
88
+ if (!this._genesis_hash) {
89
+ // TESTNET
90
+ const range = await this.getBlockHeadersRange({
91
+ start_height: 0,
92
+ end_height: 0,
93
+ });
94
+ this._genesis_hash = range.headers[0].hash;
95
+ }
96
+ params.block_ids.push(this.genesis_hash);
97
+ }
98
+ return params;
99
+ }
100
+ /**
101
+ * This function helps with making requests to the get_blocks.bin endpoint of the Monerod nodes.
102
+ * if params.block_ids is supplied, it will add the genesis hash to the end of the block_ids array.
103
+ * (so you can just supply the block_id you want to start fetching from)
104
+ *
105
+ * The difference compared to the getBlocksBin method is that it returns a Uint8Array that still has to be scanned for outputs.
106
+ * This is useful if you want to scan multiple viewpairs at once. You can take the Uint8Array and pass it to another ViewPair to scan for outputs.
107
+ * @param params params that will be turned into epee (monero lib that does binary serialization)
108
+ * @param stopSync optional AbortSignal to stop the syncing process
109
+ * @returns This method will return a Uint8Array that can subsequently be scanned for outputs with the getBlocksBinScanResponse method.
110
+ */
111
+ async getBlocksBinExecuteRequest(params, stopSync) {
112
+ return await getBlocksBinExecuteRequest(this, await this.addGenesisHashToBlockIds(params), stopSync);
113
+ }
114
+ /**
115
+ * This function helps with scanning the response of the getBlocksBinExecuteRequest method.
116
+ * It will parse the Uint8Array and return the outputs that belong to the ViewPair.
117
+ * (if outputs are found in the blocks that are contained in the Uint8Array that was returned by the getBlocksBinExecuteRequest method)
118
+ * @link https://docs.getmonero.org/rpc-library/monerod-rpc/#get_blocksbin
119
+ * @param getBlocksBinResponseBuffer the Uint8Array that was returned by the getBlocksBinExecuteRequest method.(which contains the blocks in binary format, returned from the Monerod node)
120
+ * @param metaCallBack contains meta information about the getBlocksbin call (new sync height = start_height param + number of blocks)
121
+ * @returns It returns {@link ScanResult} (outputs that belong to viewpair)
122
+ */
123
+ getBlocksBinScanResponse(getBlocksBinResponseBuffer, metaCallBack) {
124
+ return getBlocksBinScanResponse(this, getBlocksBinResponseBuffer, metaCallBack);
125
+ }
126
+ /**
127
+ * scan
128
+ */
129
+ async scan(cacheChanged = (params) => console.log(params), stopSync, scan_settings_path, pathPrefix) {
130
+ const processor = this;
131
+ const nonHaltedWallets = await openNonHaltedWallets(scan_settings_path);
132
+ const masterWalletSettings = nonHaltedWallets[0];
133
+ if (masterWalletSettings.primary_address !== this.primary_address)
134
+ throw new Error("master wallet should be the first of the non halted wallets");
135
+ let masterStartHeight = await cullTooLargeScanHeight(processor.node_url, scan_settings_path);
136
+ let current_range = await initScanCache(processor, masterStartHeight, scan_settings_path, pathPrefix);
137
+ const blockGenerator = (async function* () {
138
+ while (true) {
139
+ if (stopSync?.aborted)
140
+ return;
141
+ const firstResponse = await processor.getBlocksBinExecuteRequest({ block_ids: current_range.block_hashes.map((b) => b.block_hash) }, stopSync);
142
+ await readWriteConnectionStatusFile((cs) => {
143
+ if (cs?.last_packet.status === "catastrophic_reorg")
144
+ return;
145
+ const connectionStatus = {
146
+ last_packet: {
147
+ status: "OK",
148
+ bytes_read: firstResponse.length,
149
+ node_url: processor.node_url,
150
+ timestamp: new Date().toISOString(),
151
+ },
152
+ };
153
+ return connectionStatus;
154
+ });
155
+ yield firstResponse;
156
+ }
157
+ })();
158
+ const masterWithKeys = await walletSettingsPlusKeys(masterWalletSettings);
159
+ const slaveViewPairs = [];
160
+ if (nonHaltedWallets.length > 1) {
161
+ for (const slaveWallet of nonHaltedWallets.slice(1)) {
162
+ const slaveWithKeys = await walletSettingsPlusKeys(slaveWallet);
163
+ const viewpair = await ViewPair.create(slaveWallet.primary_address, slaveWithKeys.secret_view_key, slaveWallet.subaddress_index, masterWalletSettings.node_url);
164
+ slaveViewPairs.push({
165
+ viewpair,
166
+ current_range: await initScanCache(viewpair, masterStartHeight, scan_settings_path, pathPrefix),
167
+ secret_spend_key: slaveWithKeys.secret_spend_key,
168
+ });
169
+ }
170
+ }
171
+ const catastrophic_reorg_cb = async () => {
172
+ writeConnectionStatusFile("catastrophic_reorg", processor.node_url, undefined, scan_settings_path);
173
+ };
174
+ try {
175
+ for await (const firstResponse of blockGenerator) {
176
+ if (!firstResponse)
177
+ continue;
178
+ await this.writeSubaddressesToScanCache(scan_settings_path, pathPrefix);
179
+ const result = await processor.getBlocksBinScanResponse(firstResponse);
180
+ const oldMasterCurrentRange = structuredClone(current_range);
181
+ current_range = await processScanResult({
182
+ current_range,
183
+ result,
184
+ cacheChanged,
185
+ secret_spend_key: masterWithKeys.secret_spend_key,
186
+ pathPrefix,
187
+ catastrophic_reorg_cb,
188
+ });
189
+ if (slaveViewPairs.length > 0) {
190
+ if (result && "block_infos" in result)
191
+ await writeGetblocksBinBuffer(firstResponse, result.block_infos, pathPrefix); // feed the slaves
192
+ for (const slave of slaveViewPairs) {
193
+ let blocksBinItems = await readGetblocksBinBuffer(slave.current_range.end, pathPrefix);
194
+ let use_master_current_range = false;
195
+ if (!blocksBinItems.length) {
196
+ blocksBinItems = await readGetblocksBinBuffer(current_range.end, // we use the new current range end to find the blocks
197
+ pathPrefix);
198
+ slave.current_range = structuredClone(oldMasterCurrentRange); // but we use the old master current range to scan with slaves
199
+ use_master_current_range = true;
200
+ }
201
+ for (const blocksBinItem of blocksBinItems) {
202
+ const blocksbin = new Uint8Array(await Bun.file(`${pathPrefix ?? ""}getblocksbinbuffer/${blocksBinItem.filename}`).arrayBuffer());
203
+ await slave.viewpair.writeSubaddressesToScanCache(scan_settings_path, pathPrefix);
204
+ const slaveResult = await slave.viewpair.getBlocksBinScanResponse(blocksbin);
205
+ slave.current_range = await processScanResult({
206
+ current_range: slave.current_range,
207
+ result: slaveResult,
208
+ cacheChanged,
209
+ secret_spend_key: slave.secret_spend_key,
210
+ pathPrefix,
211
+ use_master_current_range,
212
+ catastrophic_reorg_cb,
213
+ });
214
+ }
215
+ } // scan the slaves
216
+ await trimGetBlocksBinBuffer(nonHaltedWallets, pathPrefix);
217
+ }
218
+ if (!result ||
219
+ (result && "block_infos" in result && result.block_infos.length === 0)) {
220
+ // we are at the tip, and there are no new blocks
221
+ // sleep for 1 second before sending another
222
+ // getBlocks.bin request
223
+ //
224
+ await sleep(1000);
225
+ }
226
+ }
227
+ }
228
+ catch (error) {
229
+ handleScanError(error);
230
+ const cache = await readCacheFileDefaultLocation(processor.primary_address, pathPrefix);
231
+ if (!cache)
232
+ throw new Error(`${error} in scan() + cache not found for primary address: ${processor.primary_address} and path prefix: ${pathPrefix}`);
233
+ await cacheChanged({
234
+ newCache: cache,
235
+ changed_outputs: [],
236
+ });
237
+ }
238
+ }
239
+ /**
240
+ * This method makes an integrated Address for the Address of the Viewpair it was opened with.
241
+ * The network (mainnet, stagenet, testnet) is the same as the one of the Viewpairaddress.
242
+ * @param paymentId (u64 under the hood) you can use a random number or a primary key of an sqlite db to associate payments with customer sessions.
243
+ * @returns Adressstring
244
+ */
245
+ makeIntegratedAddress(paymentId) {
246
+ let address = "";
247
+ this.readFromWasmMemory = (ptr, len) => {
248
+ address = this.readString(ptr, len);
249
+ };
250
+ //@ts-ignore
251
+ this.tinywasi.instance.exports.make_integrated_address(BigInt(paymentId));
252
+ return address;
253
+ }
254
+ /**
255
+ * This method makes a Subaddress for the Address of the Viewpair it was opened with.
256
+ * The network (mainnet, stagenet, testnet) is the same as the one of the Viewpairaddress.
257
+ * if there is an active scan going on, call this on ScanCacheOpened, so the new subaddress will be scanned
258
+ *
259
+ * @param minor address index, we always set major (also called account index) to 0
260
+ * @returns Adressstring
261
+ */
262
+ makeSubaddress(minor) {
263
+ return this.makeSubaddressRaw(0, minor);
264
+ }
265
+ async writeSubaddressesToScanCache(scan_settings_path, pathPrefix) {
266
+ await writeCacheFileDefaultLocationThrows({
267
+ primary_address: this.primary_address,
268
+ pathPrefix: pathPrefix,
269
+ writeCallback: async (cache) => {
270
+ await this.addSubaddressesToScanCache(cache, scan_settings_path);
271
+ },
272
+ });
273
+ }
274
+ async addSubaddressesToScanCache(cache, scan_settings_path) {
275
+ const walletSettings = await readWalletFromScanSettings(this.primary_address, scan_settings_path);
276
+ if (!walletSettings)
277
+ throw new Error(`wallet not found in settings. did you call openwallet with the right params?
278
+ Either wrong file name supplied to params.scan_settings_path: ${scan_settings_path}
279
+ Or wrong primary_address supplied params.primary_address: ${this.primary_address}`);
280
+ const last_subaddress_index = walletSettings.subaddress_index || 1;
281
+ if (!cache.subaddresses)
282
+ cache.subaddresses = [];
283
+ const highestMinor = Math.max(...cache.subaddresses.map((sub) => sub.minor), 0);
284
+ let minor = highestMinor + 1;
285
+ while (minor <= last_subaddress_index) {
286
+ const subaddress = this.makeSubaddress(minor);
287
+ const created_at_height = lastRange(cache.scanned_ranges)?.end || 0;
288
+ const created_at_timestamp = new Date().getTime();
289
+ cache.subaddresses.push({
290
+ minor,
291
+ address: subaddress,
292
+ created_at_height,
293
+ created_at_timestamp,
294
+ });
295
+ minor++;
296
+ }
297
+ }
298
+ /**
299
+ * This method makes a Subaddress for the Address of the Viewpair it was opened with.
300
+ * The network (mainnet, stagenet, testnet) is the same as the one of the Viewpairaddress.
301
+ *
302
+ * @param major account index should be set to 0 in most cases
303
+ * @param minor address index starting at 1
304
+ * @returns Adressstring
305
+ */
306
+ makeSubaddressRaw(major, minor) {
307
+ if (typeof major !== "number" ||
308
+ typeof minor !== "number" ||
309
+ major < 0 ||
310
+ minor < 1)
311
+ throw new Error(`subaddress major and minor must be positive numbers, minor index must be above 1.
312
+ makeSubaddressRaw arguments: major: ${major}, minor: ${minor}`);
313
+ let address = "";
314
+ this.readFromWasmMemory = (ptr, len) => {
315
+ address = this.readString(ptr, len);
316
+ };
317
+ //@ts-ignore
318
+ this.tinywasi.instance.exports.make_subaddress(major, minor);
319
+ return address;
320
+ }
321
+ /**
322
+ * Creates a signable transaction using the provided parameters.
323
+ * @param params - The transaction parameters.
324
+ * @returns The serialized transaction as an array of numbers.
325
+ */
326
+ makeTransaction(params) {
327
+ return makeTransaction(this, params);
328
+ }
329
+ /**
330
+ * Retrieve block headers for a specified range of heights.
331
+ * @link https://docs.getmonero.org/rpc-library/monerod-rpc/#get_block_headers_range
332
+ * @param params The parameters including start_height, end_height, and optional fill_pow_hash.
333
+ * @returns The result object with headers, status, etc. Throws if the range is invalid:(end_height > daemonheight)
334
+ */
335
+ async getBlockHeadersRange(params) {
336
+ return await get_block_headers_range(this.node_url, params);
337
+ }
338
+ /**
339
+ * Fetch general information about the Monero daemon.
340
+ * @link https://docs.getmonero.org/rpc-library/monerod-rpc/#get_info
341
+ * @returns The result object with daemon info like height, status, etc.
342
+ */
343
+ async getInfo() {
344
+ return get_info(this.node_url);
345
+ }
346
+ }
@@ -1,4 +1,3 @@
1
- import * as crypto from "crypto";
2
1
  //vendored from https://github.com/qrdate/tinywasi/blob/main/src/TinyWASI.ts
3
2
  export class TinyWASI {
4
3
  instance = undefined;
@@ -162,7 +161,7 @@ export class TinyWASI {
162
161
  random_get(pointer, size) {
163
162
  const memory = this.getMemory();
164
163
  const buffer = new Uint8Array(memory.buffer, pointer, size);
165
- crypto.randomFillSync(buffer);
164
+ crypto.getRandomValues(buffer);
166
165
  return this.WASI_ERRNO_SUCCESS;
167
166
  }
168
167
  environ_sizes_get(environCount, environBufSize) {
@@ -1 +1 @@
1
- export declare const monero_wallet_api_wasm: Uint8Array;
1
+ export declare const monero_wallet_api_wasm: Uint8Array<ArrayBuffer>;