@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,28 @@
1
+ import { ManyScanCachesOpened, ScanCacheOpened, type ManyScanCachesOpenedCreateOptions } from "./scanresult/scanCacheOpened";
2
+ /**
3
+ * Opens all **non halted wallets listed in ScanSettings.json** for scanning.
4
+ *
5
+ * @param options.scan_settings_path if you want to use a different settings file other than the default "ScanSettings.json"
6
+ * @param options.pathPrefix if you want to keep wallet scan caches, getblocksbinbuffer in a different directory
7
+ * @param options.no_worker to feed the ManyScanCachesOpened manually with .feed(params) from CacheChangedCallbackParams
8
+ * @param options.no_stats to disable creation of stats file with aggregated information e.g. amount per wallet / subaddress
9
+ * @param options.notifyMasterChanged pass the output of this to another (no_worker) instance to feed
10
+ * @returns Promise<ManyScanCachesOpened>
11
+ */
12
+ export declare function openWallets(options?: ManyScanCachesOpenedCreateOptions): Promise<ManyScanCachesOpened | undefined>;
13
+ /**
14
+ * Opens a **single wallet** for scanning.
15
+ * **Touches ScanSettings.json**,
16
+ * halts all other wallets and scans only this wallet (use `openWallets()` for scanning all non halted wallets in the settings file).
17
+ *
18
+ * Scan runs in a worker thread to not block the main thread.
19
+ *
20
+ * @param primary_address Wallet address
21
+ * @param scan_settings_path if you want to use a different settings file other than the default "ScanSettings.json"
22
+ * @param pathPrefix if you want to keep wallet scan caches, getblocksbinbuffer in a different directory
23
+ * @param no_worker to feed the ManyScanCachesOpened manually with .feed(params) from CacheChangedCallbackParams
24
+ * @param options.no_stats to disable creation of stats file with aggregated information e.g. amount per wallet / subaddress
25
+ * @returns Promise<ScanCacheOpened>
26
+ */
27
+ export declare function openWallet(primary_address: string, scan_settings_path?: string, pathPrefix?: string, no_worker?: boolean, no_stats?: boolean): Promise<ScanCacheOpened>;
28
+ export declare function haltAllWalletsExcept(primary_address: string, scan_settings_path?: string): Promise<void>;
@@ -0,0 +1,57 @@
1
+ import { ManyScanCachesOpened, ScanCacheOpened, } from "./scanresult/scanCacheOpened";
2
+ import { openScanSettingsFile, writeWalletToScanSettings, } from "./scanSettings";
3
+ /**
4
+ * Opens all **non halted wallets listed in ScanSettings.json** for scanning.
5
+ *
6
+ * @param options.scan_settings_path if you want to use a different settings file other than the default "ScanSettings.json"
7
+ * @param options.pathPrefix if you want to keep wallet scan caches, getblocksbinbuffer in a different directory
8
+ * @param options.no_worker to feed the ManyScanCachesOpened manually with .feed(params) from CacheChangedCallbackParams
9
+ * @param options.no_stats to disable creation of stats file with aggregated information e.g. amount per wallet / subaddress
10
+ * @param options.notifyMasterChanged pass the output of this to another (no_worker) instance to feed
11
+ * @returns Promise<ManyScanCachesOpened>
12
+ */
13
+ export async function openWallets(options) {
14
+ return await ManyScanCachesOpened.create(options ?? {});
15
+ }
16
+ /**
17
+ * Opens a **single wallet** for scanning.
18
+ * **Touches ScanSettings.json**,
19
+ * halts all other wallets and scans only this wallet (use `openWallets()` for scanning all non halted wallets in the settings file).
20
+ *
21
+ * Scan runs in a worker thread to not block the main thread.
22
+ *
23
+ * @param primary_address Wallet address
24
+ * @param scan_settings_path if you want to use a different settings file other than the default "ScanSettings.json"
25
+ * @param pathPrefix if you want to keep wallet scan caches, getblocksbinbuffer in a different directory
26
+ * @param no_worker to feed the ManyScanCachesOpened manually with .feed(params) from CacheChangedCallbackParams
27
+ * @param options.no_stats to disable creation of stats file with aggregated information e.g. amount per wallet / subaddress
28
+ * @returns Promise<ScanCacheOpened>
29
+ */
30
+ export async function openWallet(primary_address, scan_settings_path, pathPrefix, no_worker, no_stats) {
31
+ await haltAllWalletsExcept(primary_address, scan_settings_path);
32
+ return await ScanCacheOpened.create({
33
+ primary_address,
34
+ scan_settings_path,
35
+ pathPrefix,
36
+ no_worker,
37
+ no_stats,
38
+ });
39
+ }
40
+ export async function haltAllWalletsExcept(primary_address, scan_settings_path) {
41
+ const scan_settings = await openScanSettingsFile(scan_settings_path);
42
+ if (!scan_settings?.wallets)
43
+ return;
44
+ const nonHaltedWallets = scan_settings.wallets.filter((wallet) => !wallet?.halted);
45
+ for (const wallet of nonHaltedWallets) {
46
+ await writeWalletToScanSettings({
47
+ primary_address: wallet.primary_address, // halt all wallets
48
+ halted: true,
49
+ scan_settings_path,
50
+ });
51
+ }
52
+ await writeWalletToScanSettings({
53
+ primary_address, // unhalt this wallet
54
+ halted: false,
55
+ scan_settings_path,
56
+ });
57
+ }
@@ -0,0 +1,96 @@
1
+ export declare const SCAN_SETTINGS_STORE_NAME_DEFAULT = "ScanSettings.json";
2
+ export type ScanSetting = {
3
+ primary_address: string;
4
+ subaddress_index?: number;
5
+ halted?: boolean;
6
+ wallet_route?: string;
7
+ };
8
+ export type WriteScanSettingParams = {
9
+ primary_address: string;
10
+ start_height?: number | null;
11
+ subaddress_index?: number;
12
+ halted?: boolean;
13
+ scan_settings_path?: string;
14
+ node_url?: string;
15
+ wallet_route?: string;
16
+ };
17
+ export type ScanSettingOpened = {
18
+ primary_address: string;
19
+ start_height: number | null;
20
+ node_url: string;
21
+ subaddress_index?: number;
22
+ secret_view_key?: string;
23
+ halted?: boolean;
24
+ secret_spend_key?: string;
25
+ wallet_route?: string;
26
+ };
27
+ export type ScanSettings = {
28
+ wallets: ScanSetting[];
29
+ node_url: string;
30
+ start_height: number | null;
31
+ };
32
+ export type ScanSettingsOpened = {
33
+ wallets: (ScanSettingOpened | undefined)[];
34
+ node_url: string;
35
+ start_height: number | null;
36
+ };
37
+ /**
38
+ * Writes scan settings to the default or specified storage file in json.
39
+ *
40
+ * @example
41
+ * ```
42
+ * const settings: ScanSettings = {
43
+ * wallets: [{
44
+ * primary_address: "5dsf...",
45
+ * start_height: 1741707,
46
+ * }],
47
+ * node_urls: ["https://monerooo.roooo"]
48
+ * };
49
+ * await writeScanSettings(settings);
50
+ * ```
51
+ *
52
+ * @param scan_settings - The complete {@link ScanSettings} configuration to persist.
53
+ * @param settingsStorePath - Optional path for the settings file. Defaults to `SCAN_SETTINGS_STORE_NAME_DEFAULT`.
54
+ * @returns A promise that resolves when the file is successfully written.
55
+ * @throws Will throw if file writing fails (e.g., permissions, disk space).
56
+ */
57
+ export declare function writeScanSettings(scan_settings: ScanSettings, settingsStorePath?: string): Promise<number>;
58
+ export declare function writeStartHeightToScanSettings(start_height: number | null, settingsStorePath?: string): Promise<number>;
59
+ export declare function writeDaemonHeightAsStartHeightToScanSettings(node_url: string, settingsStorePath?: string): Promise<number>;
60
+ export declare function cullTooLargeScanHeight(node_url: string, settingsStorePath?: string): Promise<number>;
61
+ export declare function writeNodeUrlToScanSettings(node_url: string, settingsStorePath?: string): Promise<number>;
62
+ export declare function readNodeUrlFromScanSettings(settingsStorePath?: string): Promise<string | undefined>;
63
+ /**
64
+ * Reads scan settings from the default or specified storage file.
65
+ * secret_view_key and spend_private_key are read from environment variables
66
+ *
67
+ * @example
68
+ * ```
69
+ * const settings = await readScanSettings();
70
+ * if (settings) {
71
+ * console.log(settings.wallets?.primary_address);
72
+ * }
73
+ * ```
74
+ *
75
+ * @param settingsStorePath - Path to the settings file. Defaults to `SCAN_SETTINGS_STORE_NAME_DEFAULT`.
76
+ * @returns The parsed {@link ScanSettings} object if file exists and is valid JSON, otherwise `undefined`.
77
+ */
78
+ export declare function readScanSettings(scan_settings_path?: string): Promise<ScanSettingsOpened | undefined>;
79
+ export declare function readPrivateSpendKeyFromEnv(primary_address: string): string | undefined;
80
+ export declare function readPrivateViewKeyFromEnv(primary_address: string): string | undefined;
81
+ export declare function readWalletFromScanSettings(primary_address: string, scan_settings_path?: string): Promise<ScanSettingOpened | undefined>;
82
+ export declare function readWalletsFromScanSettings(scan_settings_path?: string): Promise<ScanSettingOpened[]>;
83
+ export declare function walletSettingsPlusKeys(wallet_settings: ScanSettingOpened, secret_view_key?: string, secret_spend_key?: string): Promise<{
84
+ secret_view_key: string;
85
+ secret_spend_key: string | undefined;
86
+ primary_address: string;
87
+ start_height: number | null;
88
+ node_url: string;
89
+ subaddress_index?: number;
90
+ halted?: boolean;
91
+ wallet_route?: string;
92
+ }>;
93
+ export declare function writeWalletToScanSettings(params: WriteScanSettingParams): Promise<number>;
94
+ export declare function openScanSettingsFile(scan_settings_path?: string): Promise<ScanSettings | undefined>;
95
+ export declare function openNonHaltedWallets(scan_settings_path?: string): Promise<ScanSettingOpened[]>;
96
+ export declare function doesScanSettingsFileExist(scan_settings_path?: string): Promise<boolean>;
@@ -0,0 +1,240 @@
1
+ import { get_info } from "../api";
2
+ import { atomicWrite } from "../io/atomicWrite";
3
+ import { refreshEnvIndexedDB } from "../io/indexedDB";
4
+ import { LOCAL_NODE_DEFAULT_URL } from "../node-interaction/nodeUrl";
5
+ export const SCAN_SETTINGS_STORE_NAME_DEFAULT = "ScanSettings.json";
6
+ /**
7
+ * Writes scan settings to the default or specified storage file in json.
8
+ *
9
+ * @example
10
+ * ```
11
+ * const settings: ScanSettings = {
12
+ * wallets: [{
13
+ * primary_address: "5dsf...",
14
+ * start_height: 1741707,
15
+ * }],
16
+ * node_urls: ["https://monerooo.roooo"]
17
+ * };
18
+ * await writeScanSettings(settings);
19
+ * ```
20
+ *
21
+ * @param scan_settings - The complete {@link ScanSettings} configuration to persist.
22
+ * @param settingsStorePath - Optional path for the settings file. Defaults to `SCAN_SETTINGS_STORE_NAME_DEFAULT`.
23
+ * @returns A promise that resolves when the file is successfully written.
24
+ * @throws Will throw if file writing fails (e.g., permissions, disk space).
25
+ */
26
+ export async function writeScanSettings(scan_settings, settingsStorePath = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
27
+ return await atomicWrite(settingsStorePath, JSON.stringify(scan_settings, null, 2));
28
+ }
29
+ export async function writeStartHeightToScanSettings(start_height, settingsStorePath = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
30
+ const scanSettings = (await openScanSettingsFile(settingsStorePath)) || {
31
+ node_url: "",
32
+ wallets: [],
33
+ start_height: null,
34
+ };
35
+ scanSettings.start_height = start_height;
36
+ return await atomicWrite(SCAN_SETTINGS_STORE_NAME_DEFAULT, JSON.stringify(scanSettings, null, 2));
37
+ }
38
+ export async function writeDaemonHeightAsStartHeightToScanSettings(node_url, settingsStorePath = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
39
+ const getInfo = await get_info(node_url);
40
+ const scanSettings = (await openScanSettingsFile(settingsStorePath)) || {
41
+ node_url: "",
42
+ wallets: [],
43
+ start_height: null,
44
+ };
45
+ scanSettings.start_height = getInfo.height - 1;
46
+ await atomicWrite(settingsStorePath, JSON.stringify(scanSettings, null, 2));
47
+ return getInfo.height;
48
+ }
49
+ export async function cullTooLargeScanHeight(node_url, settingsStorePath = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
50
+ const getInfo = await get_info(node_url);
51
+ const scanSettings = (await openScanSettingsFile(settingsStorePath)) || {
52
+ node_url: "",
53
+ wallets: [],
54
+ start_height: null,
55
+ };
56
+ if (scanSettings.start_height === null ||
57
+ scanSettings.start_height > getInfo.height - 1) {
58
+ scanSettings.start_height = getInfo.height - 1;
59
+ await atomicWrite(settingsStorePath, JSON.stringify(scanSettings, null, 2));
60
+ }
61
+ return scanSettings.start_height;
62
+ }
63
+ export async function writeNodeUrlToScanSettings(node_url, settingsStorePath = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
64
+ const scanSettings = (await openScanSettingsFile(settingsStorePath)) || {
65
+ node_url: "",
66
+ wallets: [],
67
+ start_height: null,
68
+ };
69
+ scanSettings.node_url = node_url;
70
+ return await atomicWrite(settingsStorePath, JSON.stringify(scanSettings, null, 2));
71
+ }
72
+ export async function readNodeUrlFromScanSettings(settingsStorePath = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
73
+ const scanSettings = await openScanSettingsFile(settingsStorePath);
74
+ return scanSettings?.node_url;
75
+ }
76
+ /**
77
+ * Reads scan settings from the default or specified storage file.
78
+ * secret_view_key and spend_private_key are read from environment variables
79
+ *
80
+ * @example
81
+ * ```
82
+ * const settings = await readScanSettings();
83
+ * if (settings) {
84
+ * console.log(settings.wallets?.primary_address);
85
+ * }
86
+ * ```
87
+ *
88
+ * @param settingsStorePath - Path to the settings file. Defaults to `SCAN_SETTINGS_STORE_NAME_DEFAULT`.
89
+ * @returns The parsed {@link ScanSettings} object if file exists and is valid JSON, otherwise `undefined`.
90
+ */
91
+ export async function readScanSettings(scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
92
+ const scanSettings = await openScanSettingsFile(scan_settings_path);
93
+ if (!scanSettings)
94
+ return undefined;
95
+ const openScanSettings = Object.assign({}, scanSettings);
96
+ for (const [i, wallet] of scanSettings.wallets.entries()) {
97
+ if (!wallet.primary_address)
98
+ throw new Error("The entry ${i} in the wallet settings list in ${scan_settings_path} has no primary address");
99
+ const walletWithKeys = await walletSettingsPlusKeys({
100
+ ...wallet,
101
+ node_url: scanSettings.node_url,
102
+ start_height: scanSettings.start_height,
103
+ });
104
+ openScanSettings.wallets[i].secret_view_key =
105
+ walletWithKeys.secret_view_key;
106
+ openScanSettings.wallets[i].secret_spend_key =
107
+ walletWithKeys.secret_spend_key;
108
+ }
109
+ return openScanSettings;
110
+ }
111
+ export function readPrivateSpendKeyFromEnv(primary_address) {
112
+ return Bun.env["sk" + primary_address];
113
+ }
114
+ export function readPrivateViewKeyFromEnv(primary_address) {
115
+ return Bun.env["vk" + primary_address];
116
+ }
117
+ export async function readWalletFromScanSettings(primary_address, scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
118
+ const scanSettings = await openScanSettingsFile(scan_settings_path);
119
+ if (!scanSettings)
120
+ return undefined;
121
+ const walletSettings = scanSettings.wallets.find((wallet) => wallet?.primary_address === primary_address);
122
+ if (!walletSettings)
123
+ throw new Error(`wallet not found in settings. did you call openwallet with the right params?
124
+ Either wrong file name supplied to params.scan_settings_path: ${scan_settings_path}
125
+ Or wrong primary_address supplied params.primary_address: ${primary_address}`);
126
+ return {
127
+ ...walletSettings,
128
+ node_url: scanSettings.node_url,
129
+ start_height: scanSettings.start_height,
130
+ };
131
+ }
132
+ export async function readWalletsFromScanSettings(scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
133
+ const scanSettings = await openScanSettingsFile(scan_settings_path);
134
+ const scanSettingsOpened = [];
135
+ for (const wallet of scanSettings?.wallets || []) {
136
+ scanSettingsOpened.push({
137
+ ...wallet,
138
+ node_url: scanSettings?.node_url || LOCAL_NODE_DEFAULT_URL,
139
+ start_height: scanSettings?.start_height || null,
140
+ });
141
+ }
142
+ return scanSettingsOpened;
143
+ }
144
+ export async function walletSettingsPlusKeys(wallet_settings, secret_view_key, secret_spend_key) {
145
+ // read secret_view_key and secret_spend_key from env
146
+ if (!secret_view_key)
147
+ secret_view_key = Bun.env[`vk${wallet_settings.primary_address}`];
148
+ if (!secret_view_key) {
149
+ await refreshEnvIndexedDB();
150
+ secret_view_key = Bun.env[`vk${wallet_settings.primary_address}`];
151
+ }
152
+ if (!secret_view_key)
153
+ throw ("no secret_view_key provided and not found in env for address: " +
154
+ wallet_settings.primary_address);
155
+ if (!secret_spend_key)
156
+ secret_spend_key = Bun.env[`sk${wallet_settings.primary_address}`];
157
+ return {
158
+ ...wallet_settings,
159
+ secret_view_key,
160
+ secret_spend_key,
161
+ };
162
+ }
163
+ export async function writeWalletToScanSettings(params) {
164
+ if (!params.scan_settings_path)
165
+ params.scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT;
166
+ if (!params.primary_address)
167
+ throw new Error("no primary address provided to writeWalletToScanSettings()");
168
+ const scanSettings = await openScanSettingsFile(params.scan_settings_path);
169
+ if (!scanSettings) {
170
+ if (!params.node_url)
171
+ params.node_url = LOCAL_NODE_DEFAULT_URL;
172
+ // case: no scan settings exist yet
173
+ return await writeScanSettings({
174
+ wallets: [
175
+ {
176
+ primary_address: params.primary_address,
177
+ subaddress_index: params.subaddress_index || 1,
178
+ halted: params.halted,
179
+ wallet_route: params.wallet_route,
180
+ },
181
+ ],
182
+ node_url: params.node_url,
183
+ start_height: params.start_height || null,
184
+ }, params.scan_settings_path);
185
+ }
186
+ scanSettings.node_url = params.node_url || scanSettings.node_url;
187
+ scanSettings.start_height =
188
+ params.start_height === undefined
189
+ ? scanSettings.start_height
190
+ : params.start_height;
191
+ const already_has_settings = scanSettings.wallets.findIndex((wallet) => wallet?.primary_address === params.primary_address);
192
+ if (already_has_settings === -1) {
193
+ // wallet does not exist yet in settings
194
+ scanSettings.wallets.push({
195
+ primary_address: params.primary_address,
196
+ subaddress_index: params.subaddress_index || 1,
197
+ halted: params.halted,
198
+ wallet_route: params.wallet_route,
199
+ });
200
+ }
201
+ else {
202
+ // wallet already exists
203
+ const wallet = scanSettings.wallets[already_has_settings];
204
+ if (wallet) {
205
+ wallet.subaddress_index =
206
+ params.subaddress_index || wallet.subaddress_index || 1;
207
+ wallet.halted = params.halted || wallet.halted;
208
+ wallet.wallet_route = params.wallet_route || wallet.wallet_route;
209
+ }
210
+ }
211
+ return await atomicWrite(params.scan_settings_path, JSON.stringify(scanSettings, null, 2));
212
+ }
213
+ export async function openScanSettingsFile(scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
214
+ const jsonString = await Bun.file(scan_settings_path)
215
+ .text()
216
+ .catch(() => undefined);
217
+ return jsonString ? JSON.parse(jsonString) : undefined;
218
+ }
219
+ export async function openNonHaltedWallets(scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
220
+ const scan_settings = await readWalletsFromScanSettings(scan_settings_path);
221
+ if (!scan_settings)
222
+ throw new Error("no scan settings file found at path: " + scan_settings_path);
223
+ if (!scan_settings)
224
+ throw new Error("no wallets in scan settings");
225
+ const nonHaltedWallets = scan_settings.filter((wallet) => !wallet?.halted);
226
+ if (!nonHaltedWallets.length)
227
+ throw new Error("no non halted wallets in scan settings");
228
+ return nonHaltedWallets;
229
+ }
230
+ export async function doesScanSettingsFileExist(scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
231
+ const scan_settings_file_content = await Bun.file(scan_settings_path)
232
+ .text()
233
+ .catch(() => "");
234
+ if (scan_settings_file_content.length) {
235
+ return true;
236
+ }
237
+ else {
238
+ return false;
239
+ }
240
+ }
@@ -0,0 +1,3 @@
1
+ import type { Output } from "../../api";
2
+ export type KeyImage = string;
3
+ export declare function computeKeyImage(output: Output, sender_spend_key: string): Promise<KeyImage | undefined>;
@@ -0,0 +1,21 @@
1
+ import { WasmProcessor } from "../../wasm-processing/wasmProcessor";
2
+ export async function computeKeyImage(output, sender_spend_key) {
3
+ const wasmProcessor = await WasmProcessor.init();
4
+ wasmProcessor.writeToWasmMemory = (ptr, len) => {
5
+ wasmProcessor.writeString(ptr, len, output.serialized);
6
+ wasmProcessor.writeToWasmMemory = (ptr, len) => {
7
+ wasmProcessor.writeString(ptr, len, sender_spend_key);
8
+ };
9
+ };
10
+ let result = undefined;
11
+ wasmProcessor.readFromWasmMemory = (ptr, len) => {
12
+ result = JSON.parse(wasmProcessor.readString(ptr, len));
13
+ };
14
+ //@ts-ignore
15
+ wasmProcessor.tinywasi.instance.exports.compute_key_image(output.serialized.length, sender_spend_key.length);
16
+ if (!result) {
17
+ throw new Error("Failed to compute key image for output with global id: " +
18
+ output.index_on_blockchain);
19
+ }
20
+ return result["key_image"];
21
+ }
@@ -0,0 +1,28 @@
1
+ import type { BlockInfo, GetBlocksBinRequest, ViewPair } from "../../api";
2
+ import type { ScanSetting } from "../scanSettings";
3
+ import { type CacheRange } from "./scanCache";
4
+ export declare function writeGetblocksBinBuffer(getBlocksBinResponseContent: Uint8Array, block_infos: BlockInfo[], pathPrefix?: string): Promise<number | undefined>;
5
+ export type GetBlocksBinBufferItem = {
6
+ start: number;
7
+ end: number;
8
+ filename: string;
9
+ date: string;
10
+ last_block_hash: string;
11
+ };
12
+ export declare function readGetblocksBinBuffer(current_height: number, pathPrefix?: string): Promise<GetBlocksBinBufferItem[]>;
13
+ export type BlocksGenerator = AsyncGenerator<Uint8Array>;
14
+ export type SlaveViewPair = {
15
+ viewpair: ViewPair;
16
+ current_range: CacheRange;
17
+ secret_spend_key?: string;
18
+ };
19
+ declare module "bun" {
20
+ interface BunFile {
21
+ delete(): Promise<void>;
22
+ }
23
+ }
24
+ export declare function trimGetBlocksBinBuffer(nonHaltedWallets: ScanSetting[], pathPrefix?: string): Promise<void>;
25
+ export declare function readGetblocksBinBufferItems(pathPrefix?: string): Promise<GetBlocksBinBufferItem[]>;
26
+ export interface HasGetBlocksBinExecuteRequestMethod {
27
+ getBlocksBinExecuteRequest: (params: GetBlocksBinRequest, stopSync?: AbortSignal) => Promise<Uint8Array>;
28
+ }
@@ -0,0 +1,52 @@
1
+ import { atomicWrite } from "../../io/atomicWrite";
2
+ import { readDir } from "../../io/readDir";
3
+ import { readCacheFileDefaultLocation } from "./scanCache";
4
+ export async function writeGetblocksBinBuffer(getBlocksBinResponseContent, block_infos, pathPrefix) {
5
+ if (!block_infos.length)
6
+ return;
7
+ const start = block_infos[0].block_height;
8
+ const end = block_infos.at(-1)?.block_height;
9
+ const block_hash = block_infos.at(-1)?.block_hash;
10
+ const bufferItems = await readGetblocksBinBufferItems(pathPrefix);
11
+ if (bufferItems.find((bi) => bi.last_block_hash === block_hash))
12
+ return;
13
+ // if we have the same end blockhash already, dont write
14
+ return await atomicWrite(`${pathPrefix ?? ""}getblocksbinbuffer/${Date.now()}-${start}-${end}-${block_hash}-getblocks.bin`, getBlocksBinResponseContent);
15
+ }
16
+ export async function readGetblocksBinBuffer(current_height, pathPrefix) {
17
+ const bufferItems = await readGetblocksBinBufferItems(pathPrefix);
18
+ const start_buffer = bufferItems.find((r) => current_height >= r.start && current_height <= r.end) ?? null;
19
+ if (start_buffer)
20
+ return [
21
+ start_buffer,
22
+ ...bufferItems.filter((r) => r.date > start_buffer?.date),
23
+ ];
24
+ return [];
25
+ }
26
+ export async function trimGetBlocksBinBuffer(nonHaltedWallets, pathPrefix) {
27
+ const snail = Math.min(...(await Promise.all(nonHaltedWallets
28
+ .slice(1)
29
+ .map(async (wallet) => (await readCacheFileDefaultLocation(wallet.primary_address, pathPrefix))?.scanned_ranges[0]?.end))).filter((x) => x !== undefined));
30
+ // if all wallets have higher start than end of blocksbufferitem, delete
31
+ const bufferItems = await readGetblocksBinBufferItems(pathPrefix);
32
+ for (const bufferItem of bufferItems) {
33
+ if (bufferItem.end <= snail) {
34
+ await Bun.file(`${pathPrefix ?? ""}getblocksbinbuffer/${bufferItem.filename}`).delete();
35
+ }
36
+ }
37
+ }
38
+ export async function readGetblocksBinBufferItems(pathPrefix) {
39
+ const bufferItems = await readDir(`${pathPrefix ?? ""}getblocksbinbuffer/`).catch(() => []);
40
+ const ranges = [];
41
+ for (const filename of bufferItems) {
42
+ const [date, start, end, last_block_hash] = filename.split("-");
43
+ ranges.push({
44
+ start: parseInt(start),
45
+ end: parseInt(end),
46
+ filename,
47
+ date,
48
+ last_block_hash,
49
+ });
50
+ }
51
+ return ranges;
52
+ }
@@ -0,0 +1,14 @@
1
+ import type { BlockInfo, Output } from "../../api";
2
+ import type { KeyImage } from "./computeKeyImage";
3
+ import { type ScanResult } from "./scanResult";
4
+ import type { CacheRange, ChangedOutput, ScanCache } from "./scanCache";
5
+ export type ReorgInfo = {
6
+ split_height: BlockInfo;
7
+ removed_outputs: ReorgedOutput[];
8
+ reverted_spends: ReorgedOutput[];
9
+ };
10
+ export type ReorgedOutput = {
11
+ old_output_state: Output;
12
+ key_image: KeyImage;
13
+ };
14
+ export declare function handleReorg(current_range: CacheRange, result: ScanResult, cache: ScanCache, oldRange: CacheRange): [CacheRange, ChangedOutput[]];
@@ -0,0 +1,78 @@
1
+ import { makeNewRange } from "./scanResult";
2
+ export function handleReorg(current_range, result, cache, oldRange) {
3
+ let changed_outputs = [];
4
+ // we need to check where anchor candidate is and if not found, try the same for anchor
5
+ // if else throw on catastrophic reorg
6
+ for (const block_hash of oldRange.block_hashes) {
7
+ const split_height_index = result.block_infos.findIndex((b) => b.block_hash === block_hash.block_hash);
8
+ const split_height = result.block_infos[split_height_index];
9
+ // still a chance to find the split height, (could be candidate_anchor or anchor)
10
+ if (!split_height)
11
+ continue;
12
+ // we found the split height & do the reorg
13
+ const reorg_info = {
14
+ split_height,
15
+ removed_outputs: [],
16
+ reverted_spends: [],
17
+ };
18
+ const removed_outputs = Object.entries(cache.outputs).filter(([id, output]) => output.block_height >= split_height.block_height);
19
+ for (const [id, old_output_state] of removed_outputs) {
20
+ // 1. find key_image of output to be removed (as it was reorged)
21
+ const [key_image] = Object.entries(cache.own_key_images).find(([own_key_image, globalid]) => globalid === id) || [""]; // if this is viewonly the key_image will be empty
22
+ reorg_info.removed_outputs.push({ old_output_state, key_image });
23
+ // 2. remove from outputs and own_key_images
24
+ delete cache.outputs[id];
25
+ delete cache.own_key_images[key_image];
26
+ changed_outputs.push({
27
+ output: old_output_state,
28
+ change_reason: "reorged",
29
+ });
30
+ }
31
+ //for reverted spents, just do the same again with spent_height
32
+ const reverted_outputs = Object.entries(cache.outputs).filter(([id, output]) => output.spent_block_height !== undefined &&
33
+ output.spent_block_height >= split_height.block_height);
34
+ for (const [id, old_output_state_pointer] of reverted_outputs) {
35
+ const [key_image] = Object.entries(cache.own_key_images).find(([own_key_image, globalid]) => globalid === id) || [""]; // if this is viewonly the key_image will be empty
36
+ const old_output_state = Object.assign({}, old_output_state_pointer);
37
+ reorg_info.reverted_spends.push({
38
+ old_output_state,
39
+ key_image, // in this case key_image only used here, does not get removed
40
+ });
41
+ // remove spend info from original cache
42
+ delete cache.outputs[id].spent_relative_index;
43
+ delete cache.outputs[id].spent_in_tx_hash;
44
+ delete cache.outputs[id].spent_block_height;
45
+ delete cache.outputs[id].spent_block_timestamp;
46
+ changed_outputs.push({
47
+ output: old_output_state,
48
+ change_reason: "reorged_spent",
49
+ });
50
+ }
51
+ // find current range in scanned ranges and change its end value + latest_block_hash
52
+ oldRange.end = split_height.block_height;
53
+ oldRange.block_hashes[0] = split_height;
54
+ cache.reorg_info = reorg_info;
55
+ // fix current_range
56
+ let anchor = result.block_infos.slice(-100)[0]; // if we got lots of new blocks
57
+ const old_anchor = oldRange.block_hashes.at(-1);
58
+ if (!anchor &&
59
+ old_anchor &&
60
+ split_height.block_height > old_anchor.block_height //if we did not get many new blocks + split height was candidate anchor
61
+ ) {
62
+ anchor = old_anchor; // we keep the anchor the same
63
+ }
64
+ if (!anchor)
65
+ anchor = split_height;
66
+ let last_block_hash_of_result = result.block_infos.at(-1);
67
+ const end = last_block_hash_of_result.block_height;
68
+ const start = current_range.start > end ? end : current_range.start;
69
+ const newRange = {
70
+ start,
71
+ end,
72
+ block_hashes: [last_block_hash_of_result, anchor, anchor],
73
+ };
74
+ return [makeNewRange(newRange, cache), changed_outputs];
75
+ }
76
+ // we tried all the block hashes and could not find the split height
77
+ throw new Error("Could not find reorg split height. Most likely connected to faulty node / catastrophic reorg.");
78
+ }