@spirobel/monero-wallet-api 0.5.4 → 0.6.0

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 (91) hide show
  1. package/dist/actionlog/actionLogOpened.d.ts +96 -1
  2. package/dist/actionlog/actionLogOpened.js +386 -1
  3. package/dist/actionlog/actionLogTypes.d.ts +114 -0
  4. package/dist/actionlog/actionLogTypes.js +198 -0
  5. package/dist/actionlog/backend/idbBackend.d.ts +13 -0
  6. package/dist/actionlog/backend/idbBackend.js +109 -0
  7. package/dist/actionlog/backend/jsonlBackend.d.ts +19 -0
  8. package/dist/actionlog/backend/jsonlBackend.js +110 -0
  9. package/dist/actionlog/backend/memoryBackend.d.ts +11 -0
  10. package/dist/actionlog/backend/memoryBackend.js +31 -0
  11. package/dist/actionlog/backend/sqliteBackend.d.ts +14 -0
  12. package/dist/actionlog/backend/sqliteBackend.js +96 -0
  13. package/dist/actionlog/backend.d.ts +12 -0
  14. package/dist/actionlog/backend.js +1 -0
  15. package/dist/actionlog/idbBackend.d.ts +19 -0
  16. package/dist/actionlog/idbBackend.js +98 -0
  17. package/dist/actionlog/jsonlBackend.d.ts +19 -0
  18. package/dist/actionlog/jsonlBackend.js +75 -0
  19. package/dist/actionlog/memoryBackend.d.ts +14 -0
  20. package/dist/actionlog/memoryBackend.js +38 -0
  21. package/dist/actionlog/openMaps.d.ts +11 -0
  22. package/dist/actionlog/openMaps.js +32 -0
  23. package/dist/actionlog/sqliteBackend.d.ts +18 -0
  24. package/dist/actionlog/sqliteBackend.js +105 -0
  25. package/dist/actionlog/toolContext.d.ts +36 -0
  26. package/dist/actionlog/toolContext.js +1 -0
  27. package/dist/actionlog/types.d.ts +62 -0
  28. package/dist/actionlog/types.js +62 -0
  29. package/dist/api.d.ts +4 -0
  30. package/dist/api.js +3 -0
  31. package/dist/io/sleep.d.ts +1 -1
  32. package/dist/io/sleep.js +16 -1
  33. package/dist/scanning-syncing/backgroundWorker.d.ts +2 -0
  34. package/dist/scanning-syncing/backgroundWorker.js +92 -10
  35. package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +33 -2
  36. package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.js +2 -2
  37. package/dist/scanning-syncing/connectionStatus.d.ts +8 -0
  38. package/dist/scanning-syncing/connectionStatus.js +50 -0
  39. package/dist/scanning-syncing/connectionStatusOpened.d.ts +3 -1
  40. package/dist/scanning-syncing/connectionStatusOpened.js +22 -14
  41. package/dist/scanning-syncing/openWallet.d.ts +4 -1
  42. package/dist/scanning-syncing/openWallet.js +3 -0
  43. package/dist/scanning-syncing/scanresult/scanCache.d.ts +5 -1
  44. package/dist/scanning-syncing/scanresult/scanCache.js +11 -2
  45. package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +56 -5
  46. package/dist/scanning-syncing/scanresult/scanCacheOpened.js +422 -83
  47. package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +7 -3
  48. package/dist/scanning-syncing/scanresult/scanCoordination.js +126 -28
  49. package/dist/scanning-syncing/scanresult/scanLoop.d.ts +1 -2
  50. package/dist/scanning-syncing/scanresult/scanLoop.js +0 -13
  51. package/dist/scanning-syncing/scanresult/scanResult.js +17 -12
  52. package/dist/scanning-syncing/scanresult/scanStats.js +8 -5
  53. package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
  54. package/dist/scanning-syncing/worker-mains/coordinator-main.d.ts +1 -0
  55. package/dist/scanning-syncing/worker-mains/coordinator-main.js +16 -1
  56. package/dist/scanning-syncing/worker-mains/cpubound-main.js +3 -2
  57. package/dist/scanning-syncing/worker-mains/worker.js +45 -2
  58. package/dist/tools/calls/001.d.ts +20 -0
  59. package/dist/tools/calls/001.js +236 -0
  60. package/dist/tools/calls/002-content.d.ts +46 -0
  61. package/dist/tools/calls/002-content.js +135 -0
  62. package/dist/tools/calls/002-fail-cleanup.d.ts +27 -0
  63. package/dist/tools/calls/002-fail-cleanup.js +60 -0
  64. package/dist/tools/calls/002.d.ts +64 -0
  65. package/dist/tools/calls/002.js +353 -0
  66. package/dist/tools/content.d.ts +22 -0
  67. package/dist/tools/content.js +41 -0
  68. package/dist/tools/counterparty.d.ts +3 -0
  69. package/dist/tools/counterparty.js +1 -0
  70. package/dist/tools/download-public-suffix.d.ts +1 -0
  71. package/dist/tools/download-public-suffix.js +20 -0
  72. package/dist/tools/globals.d.ts +196 -0
  73. package/dist/tools/globals.js +14 -0
  74. package/dist/tools/monero-tools-content.d.ts +29 -0
  75. package/dist/tools/monero-tools-content.js +80 -0
  76. package/dist/tools/monero-tools.d.ts +27 -52
  77. package/dist/tools/monero-tools.js +127 -222
  78. package/dist/tools/public-suffix-list.d.ts +1 -0
  79. package/dist/tools/public-suffix-list.js +10336 -0
  80. package/dist/tools/runtimeMessages.d.ts +4 -0
  81. package/dist/tools/runtimeMessages.js +18 -0
  82. package/dist/tools/toolActionLogContext.d.ts +33 -0
  83. package/dist/tools/toolActionLogContext.js +1 -0
  84. package/dist/tools/toolArm.d.ts +47 -0
  85. package/dist/tools/toolArm.js +1 -0
  86. package/dist/tools/ui.d.ts +2 -0
  87. package/dist/tools/ui.js +1 -0
  88. package/dist/tools/worker.d.ts +1 -0
  89. package/dist/tools/worker.js +1 -0
  90. package/dist/wasm-processing/wasmFile.js +2 -2
  91. package/package.json +8 -1
@@ -1 +1,2 @@
1
+ export declare function requestCoordinatorShutdown(): void;
1
2
  export declare function coordinatorMainWorker(scan_settings_path?: string, pathPrefix?: string, cpuPorts?: MessagePort[]): Promise<void>;
@@ -1,9 +1,16 @@
1
1
  import { coordinatorMainMultithreaded } from "../scanresult/scanCoordination";
2
2
  import { log } from "../../io/logging";
3
+ // one controller per coordinator worker; shutdown aborts fetch + race
4
+ let shutdownController;
5
+ export function requestCoordinatorShutdown() {
6
+ log("coordinatorMainWorker", ["shutdown requested"]);
7
+ shutdownController?.abort();
8
+ }
3
9
  export async function coordinatorMainWorker(scan_settings_path, pathPrefix, cpuPorts) {
4
10
  log("coordinatorMainWorker", "cpuPorts=" + cpuPorts?.length);
11
+ shutdownController = new AbortController();
5
12
  try {
6
- const gen = coordinatorMainMultithreaded(scan_settings_path, pathPrefix, cpuPorts);
13
+ const gen = coordinatorMainMultithreaded(scan_settings_path, pathPrefix, cpuPorts, shutdownController.signal);
7
14
  for await (const event of gen) {
8
15
  log("coordinatorMainWorker", ["event:", event]);
9
16
  if (event.type === "scan_ready") {
@@ -15,9 +22,17 @@ export async function coordinatorMainWorker(scan_settings_path, pathPrefix, cpuP
15
22
  },
16
23
  });
17
24
  }
25
+ else if (event.type === "shutdown_done") {
26
+ self.postMessage({ type: "SHUTDOWN_DONE" });
27
+ return;
28
+ }
18
29
  }
30
+ // generator returned without shutdown_done (e.g. empty)
31
+ self.postMessage({ type: "SHUTDOWN_DONE" });
19
32
  }
20
33
  catch (error) {
34
+ // still tell main thread we stopped so terminate can proceed
35
+ self.postMessage({ type: "SHUTDOWN_DONE" });
21
36
  self.postMessage({ type: "ERROR", payload: error });
22
37
  }
23
38
  }
@@ -14,8 +14,9 @@ export async function handleCpuboundScan(msg, cpu_worker_status) {
14
14
  throw new Error("[cpubound] no content msg! ( ScanLoopInput = undefined )");
15
15
  }
16
16
  if (msg === "cancel") {
17
+ // idle cancel: nothing in flight, just ack
17
18
  log("handleCpuboundScan", "cancel msg! ( ScanLoopInput = cancel )");
18
- throw new Error("[cpubound] no content msg! ( ScanLoopInput = undefined )");
19
+ return { type: "Canceled" };
19
20
  }
20
21
  log("handleCpuboundScan", "got scan msg, walletConfig=" +
21
22
  msg.walletConfig?.primary_address?.slice(0, 6));
@@ -89,7 +90,7 @@ export async function handleCpuboundScan(msg, cpu_worker_status) {
89
90
  if (cpu_worker_status.cancel) {
90
91
  cpu_worker_status.cancel = false;
91
92
  log("handleCpuboundScan", ["cpu work canceled"]);
92
- return { type: "Ready" };
93
+ return { type: "Canceled", work_uuid: item.work_uuid };
93
94
  }
94
95
  await sleep(10); // make sure the loop is not tight
95
96
  }
@@ -1,4 +1,4 @@
1
- import { coordinatorMainWorker } from "./coordinator-main";
1
+ import { coordinatorMainWorker, requestCoordinatorShutdown, } from "./coordinator-main";
2
2
  import { handleCpuboundScanTry, sendFromCpuWorker } from "./cpubound-main";
3
3
  import { log, setupLoggingPath } from "../../io/logging";
4
4
  import { multisigMainWorkerCall } from "./multisig-main";
@@ -9,21 +9,32 @@ let SCAN_SETTINGS_PATH;
9
9
  let PATH_PREFIX;
10
10
  let cpuPort;
11
11
  const cpu_worker_status = { cancel: false };
12
+ // true while a scan is running so idle cancel can ack immediately
13
+ let cpu_work_in_flight = false;
12
14
  let multisig_dkg;
13
15
  export function CPU_PORT_HANDLER(pe) {
14
16
  if (pe.data === "cancel") {
15
- cpu_worker_status.cancel = true;
17
+ if (cpu_work_in_flight) {
18
+ cpu_worker_status.cancel = true;
19
+ return;
20
+ }
21
+ // idle cancel: ack right away so coordinator drain does not hang
22
+ if (cpuPort) {
23
+ sendFromCpuWorker(cpuPort, { type: "Canceled" });
24
+ }
16
25
  return;
17
26
  }
18
27
  if (!cpuPort)
19
28
  throw new Error("[cpubound] cpuPort is undefined in port.onmessage");
20
29
  log("CPU_PORT_HANDLER", "new workitem msg received");
30
+ cpu_work_in_flight = true;
21
31
  cpuPort.postMessage({
22
32
  type: "WORKSTART",
23
33
  work_uuid: pe.data.work_uuid,
24
34
  });
25
35
  handleCpuboundScanTry(pe.data, cpu_worker_status).then((result) => {
26
36
  log("CPU_PORT_HANDLER", "work finished, sending result");
37
+ cpu_work_in_flight = false;
27
38
  if (!cpuPort)
28
39
  throw new Error("[cpubound] cpuPort is undefined in port.onmessage");
29
40
  cpuPort.onmessage = CPU_PORT_HANDLER;
@@ -33,6 +44,38 @@ export function CPU_PORT_HANDLER(pe) {
33
44
  const handleMessage = async (e) => {
34
45
  const msg = e.data;
35
46
  log("handleMessage", ["msg received", msg]);
47
+ if (msg.type === "shutdown") {
48
+ // only coordinator listens for this; abort fetch + race loop
49
+ requestCoordinatorShutdown();
50
+ return;
51
+ }
52
+ if (msg.type === "heap_snapshot") {
53
+ try {
54
+ const path = typeof msg.path === "string" && msg.path.length > 0
55
+ ? msg.path
56
+ : `worker-heap-${Date.now()}.heapsnapshot`;
57
+ // @ts-ignore bun has generateHeapSnapshot; node has v8.writeHeapSnapshot
58
+ let out;
59
+ if (typeof Bun !== "undefined" && typeof Bun.generateHeapSnapshot === "function") {
60
+ const snap = Bun.generateHeapSnapshot("v8");
61
+ await Bun.write(path, typeof snap === "string" ? snap : JSON.stringify(snap));
62
+ out = path;
63
+ }
64
+ else {
65
+ throw Error("in the browser use the inspector directly to observe worker memory growth");
66
+ // const v8 = await import("node:v8");
67
+ // out = v8.writeHeapSnapshot(path);
68
+ }
69
+ self.postMessage({ type: "HEAP_SNAPSHOT_DONE", path: out ?? path });
70
+ }
71
+ catch (err) {
72
+ self.postMessage({
73
+ type: "HEAP_SNAPSHOT_ERROR",
74
+ error: err instanceof Error ? err.message : String(err),
75
+ });
76
+ }
77
+ return;
78
+ }
36
79
  if (msg.type === "setup") {
37
80
  const settingsPath = msg.scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT;
38
81
  SCAN_SETTINGS_PATH = settingsPath;
@@ -0,0 +1,20 @@
1
+ import { type ToolArm } from "../globals";
2
+ export type SendTransactionTool = {
3
+ tool_id: "001";
4
+ payload: SendTransactionToolPayload;
5
+ };
6
+ export type SendTransactionToolPayload = {
7
+ address: string;
8
+ amount: string;
9
+ no_check: boolean;
10
+ };
11
+ export type IsPayAddressKnown = (address: string) => boolean | Promise<boolean>;
12
+ declare function validate_route(deps: {
13
+ isPayAddressKnown: IsPayAddressKnown;
14
+ }): (req: Request) => Promise<Response>;
15
+ export declare function amountForChainSend(amount: string): string;
16
+ type Tool001CounterpartyExtra = {
17
+ validate_route: typeof validate_route;
18
+ };
19
+ declare const tool001: ToolArm<SendTransactionToolPayload, Tool001CounterpartyExtra>;
20
+ export default tool001;
@@ -0,0 +1,236 @@
1
+ import { convertAmountBigInt, convertAmountBigIntThrows, } from "../../send-functionality/conversion";
2
+ import { TOOL_MAGIC_STRING, } from "../globals";
3
+ function recognize_parse(args) {
4
+ const amount = args[1];
5
+ const address = args[3];
6
+ // trailing _no_check splits into ["no", "check"] after underscore split
7
+ const no_check = args.length >= 6 &&
8
+ args[args.length - 2] === "no" &&
9
+ args[args.length - 1] === "check";
10
+ try {
11
+ convertAmountBigIntThrows(amount);
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ if (address && amount) {
17
+ return {
18
+ tool_id: "001",
19
+ payload: { address, amount, no_check },
20
+ };
21
+ }
22
+ return null;
23
+ }
24
+ function make(payload) {
25
+ convertAmountBigIntThrows(payload.amount);
26
+ const base = `${TOOL_MAGIC_STRING}001_amount_${payload.amount}_address_${payload.address}`;
27
+ return payload.no_check ? `${base}_no_check` : base;
28
+ }
29
+ async function validate_check(invo) {
30
+ const tool = invo.tool;
31
+ if (tool.tool_id !== "001")
32
+ return "unverified";
33
+ if (tool.payload.no_check)
34
+ return "unverified";
35
+ const link = invo[invo.found_in];
36
+ const invo_link = new URL(link);
37
+ const checkUrl = `${invo_link.origin}/monerochan001/${tool.payload.address}`;
38
+ try {
39
+ const result = (await (await fetch(checkUrl)).json());
40
+ if (result &&
41
+ typeof result === "object" &&
42
+ "valid_address" in result &&
43
+ result.valid_address === true) {
44
+ return "valid";
45
+ }
46
+ return "invalid";
47
+ }
48
+ catch {
49
+ return "invalid";
50
+ }
51
+ }
52
+ const ADDRESS_VALID_RESPONSE = {
53
+ valid_address: true,
54
+ };
55
+ const ADDRESS_INVALID_RESPONSE = {
56
+ valid_address: false,
57
+ };
58
+ function addressFromMonerochan001Req(req) {
59
+ const r = req;
60
+ if (r.params?.address)
61
+ return decodeURIComponent(r.params.address);
62
+ const m = new URL(req.url).pathname.match(/monerochan001\/([^/]+)\/?$/);
63
+ return m?.[1] ? decodeURIComponent(m[1]) : "";
64
+ }
65
+ async function handleAddressCheck(req, deps) {
66
+ const address = addressFromMonerochan001Req(req);
67
+ if (!address)
68
+ return Response.json(ADDRESS_INVALID_RESPONSE);
69
+ const known = await deps.isPayAddressKnown(address);
70
+ return Response.json(known ? ADDRESS_VALID_RESPONSE : ADDRESS_INVALID_RESPONSE);
71
+ }
72
+ function validate_route(deps) {
73
+ return (req) => handleAddressCheck(req, deps);
74
+ }
75
+ function flatFromParsed(invo) {
76
+ const p = invo.tool.payload;
77
+ return {
78
+ invocationId: invo.invocation_id,
79
+ toolId: "001",
80
+ amount: p.amount,
81
+ address: p.address,
82
+ no_check: p.no_check,
83
+ context_domain: invo.context_domain,
84
+ destination_domain: invo.destination_domain,
85
+ context_href: invo.context_href,
86
+ found_in: invo.found_in,
87
+ link: invo.link,
88
+ linkText: invo.linkText,
89
+ };
90
+ }
91
+ /** first toolCall state transition: session_start invoked. */
92
+ async function invoke_write(ctx, invo) {
93
+ if (invo.tool.tool_id !== "001")
94
+ return;
95
+ const flat = flatFromParsed(invo);
96
+ await ctx.log.append({
97
+ type: "session_start",
98
+ stage: "invoked",
99
+ ...flat,
100
+ });
101
+ }
102
+ /** second toolCall state transition: validate_result validated. */
103
+ async function validate_write(ctx, invo) {
104
+ if (invo.tool.tool_id !== "001")
105
+ return;
106
+ const flat = flatFromParsed(invo);
107
+ await ctx.log.append({
108
+ type: "validate_result",
109
+ stage: "validated",
110
+ ...flat,
111
+ valid: invo.valid,
112
+ });
113
+ }
114
+ export function amountForChainSend(amount) {
115
+ return convertAmountBigInt(amount).toString();
116
+ }
117
+ function alreadyAccepted(type) {
118
+ return (type === "accept" ||
119
+ type === "execute_start" ||
120
+ type === "execute_error" ||
121
+ type === "execute_result" ||
122
+ type === "dismiss" ||
123
+ type === "aborted");
124
+ }
125
+ /** third toolCall state transition: accept accepted. */
126
+ async function accept_yes(ctx, invocationId, args) {
127
+ const invo = await ctx.log.invocation(invocationId);
128
+ if (!invo || alreadyAccepted(invo.lastType))
129
+ return;
130
+ const address = String(args.address ?? invo.address ?? "");
131
+ const amount = String(args.amount ?? invo.amount ?? "");
132
+ const wallet_to_send_from_pa = String(args.wallet_to_send_from_pa ?? "");
133
+ await ctx.log.append({
134
+ type: "accept",
135
+ stage: "accepted",
136
+ invocationId,
137
+ toolId: "001",
138
+ address,
139
+ amount,
140
+ wallet_to_send_from_pa,
141
+ });
142
+ }
143
+ /** fourth toolCall state transition: execute_start executed. fifth: execute_result executed. */
144
+ async function execute_run(ctx, invocationId, args) {
145
+ const invo = await ctx.log.invocation(invocationId);
146
+ if (!invo) {
147
+ console.error("send with no invocation");
148
+ return;
149
+ }
150
+ const address = String(args.address ?? invo.address ?? "");
151
+ const amount = String(args.amount ?? invo.amount ?? "");
152
+ const wallet_to_send_from_pa = String(args.wallet_to_send_from_pa ?? invo.wallet_to_send_from_pa ?? "");
153
+ const toolId = "001";
154
+ await ctx.log.append({
155
+ type: "execute_start",
156
+ stage: "executed",
157
+ invocationId,
158
+ toolId,
159
+ address,
160
+ amount,
161
+ wallet_to_send_from_pa,
162
+ });
163
+ let ok = false;
164
+ let error;
165
+ try {
166
+ const wallets = ctx.getWallets?.();
167
+ const wallet = wallets?.wallets?.find((w) => w.primary_address === wallet_to_send_from_pa);
168
+ if (!wallet)
169
+ throw new Error("wallet not found");
170
+ await wallet.makeSignSendTransaction({
171
+ payments: [{ address, amount: amountForChainSend(amount) }],
172
+ invocationId,
173
+ });
174
+ ok = true;
175
+ }
176
+ catch (e) {
177
+ console.error(e);
178
+ ok = false;
179
+ error = e instanceof Error ? e.message : String(e);
180
+ }
181
+ await ctx.log.append({
182
+ type: "execute_result",
183
+ stage: "executed",
184
+ invocationId,
185
+ toolId,
186
+ ok,
187
+ ...(error ? { error } : {}),
188
+ address,
189
+ amount,
190
+ wallet_to_send_from_pa,
191
+ });
192
+ }
193
+ /** toolCall state transition: dismiss accepted. */
194
+ async function accept_no(ctx, invocationId) {
195
+ const invo = await ctx.log.invocation(invocationId);
196
+ // once the user accepted, execution is in flight. a late dismiss (the send
197
+ // plate clears the form right after execute) must not flip the entry to dismissed
198
+ if (!invo ||
199
+ invo.lastType === "dismiss" ||
200
+ invo.lastType === "accept" ||
201
+ invo.lastType === "execute_start" ||
202
+ invo.lastType === "execute_result" ||
203
+ invo.lastType === "aborted") {
204
+ return;
205
+ }
206
+ await ctx.log.append({
207
+ type: "dismiss",
208
+ stage: "accepted",
209
+ invocationId,
210
+ toolId: "001",
211
+ });
212
+ }
213
+ const tool001 = {
214
+ permissions: ["spend"],
215
+ ui: {
216
+ openTitle: "payment link",
217
+ acceptLabel: "SEND",
218
+ dismissLabel: "DISMISS",
219
+ },
220
+ content: {
221
+ recognize_parse,
222
+ validate_check,
223
+ },
224
+ worker: {
225
+ invoke_write,
226
+ validate_write,
227
+ accept_yes,
228
+ accept_no,
229
+ execute_run,
230
+ },
231
+ counterparty: {
232
+ make,
233
+ validate_route,
234
+ },
235
+ };
236
+ export default tool001;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * content-script-safe 002 surface (no scansettings / atomicWrite / indexedDB).
3
+ */
4
+ import { type ToolInvocationValidity } from "../globals";
5
+ import type { ToolInvocationForValidate } from "../toolArm";
6
+ export type CreateAndShareViewOnlyWalletTool = {
7
+ tool_id: "002";
8
+ payload: CreateAndShareViewOnlyWalletToolPayload;
9
+ };
10
+ export type CreateAndShareViewOnlyWalletToolPayload = {
11
+ wallet_slot: number;
12
+ };
13
+ export declare function parseCreateAndShareViewOnlyWalletToolArgs(args: string[]): CreateAndShareViewOnlyWalletTool | null;
14
+ export declare function createCreateAndShareViewOnlyWalletToolLink(wallet_slot?: number): string;
15
+ export declare function make002ToolLink(wallet_slot?: number): string;
16
+ export declare function makeFromPayload(payload: CreateAndShareViewOnlyWalletToolPayload): string;
17
+ export declare function validate(invo: ToolInvocationForValidate): ToolInvocationValidity;
18
+ export type ShareViewkeyPayload = {
19
+ viewkey: string;
20
+ primary_address: string;
21
+ tool_invo: {
22
+ tool: {
23
+ tool_id: string;
24
+ payload: {
25
+ wallet_slot: number;
26
+ };
27
+ };
28
+ found_in: "link" | "linkText";
29
+ link: string;
30
+ linkText: string;
31
+ valid: ToolInvocationValidity;
32
+ };
33
+ };
34
+ export type ShareViewkeyResult = {
35
+ ok: boolean;
36
+ successUrl: string | null;
37
+ error?: string;
38
+ };
39
+ export type ShareViewkey002Pruned = {
40
+ viewkey: string;
41
+ primary_address: string;
42
+ wallet_slot: number;
43
+ };
44
+ export declare function shareViewKey002(payload: ShareViewkeyPayload): Promise<ShareViewkeyResult>;
45
+ export declare function potentialSuccessRedirect002(payload: ShareViewkeyPayload): Promise<ShareViewkeyResult | undefined>;
46
+ export declare function afterValidate(invo: ToolInvocationForValidate): void;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * content-script-safe 002 surface (no scansettings / atomicWrite / indexedDB).
3
+ */
4
+ import { TOOL_MAGIC_STRING, } from "../globals";
5
+ export function parseCreateAndShareViewOnlyWalletToolArgs(args) {
6
+ const wallet_slot = args[5];
7
+ if (wallet_slot && !isNaN(parseInt(wallet_slot))) {
8
+ return {
9
+ tool_id: "002",
10
+ payload: {
11
+ wallet_slot: parseInt(wallet_slot),
12
+ },
13
+ };
14
+ }
15
+ return null;
16
+ }
17
+ export function createCreateAndShareViewOnlyWalletToolLink(wallet_slot) {
18
+ wallet_slot = Number(wallet_slot) || 0;
19
+ return `${TOOL_MAGIC_STRING}002_create_and_share_viewkey_slot_${wallet_slot}`;
20
+ }
21
+ export function make002ToolLink(wallet_slot) {
22
+ return createCreateAndShareViewOnlyWalletToolLink(wallet_slot);
23
+ }
24
+ export function makeFromPayload(payload) {
25
+ return createCreateAndShareViewOnlyWalletToolLink(payload.wallet_slot);
26
+ }
27
+ export function validate(invo) {
28
+ if (invo.tool.tool_id !== "002")
29
+ return "unverified";
30
+ if (invo.context_domain == invo.destination_domain)
31
+ return "valid";
32
+ return "invalid";
33
+ }
34
+ export async function shareViewKey002(payload) {
35
+ const invo = payload.tool_invo;
36
+ if (invo.tool.tool_id !== "002")
37
+ return {
38
+ ok: false,
39
+ successUrl: null,
40
+ };
41
+ if (invo.valid !== "valid")
42
+ return {
43
+ ok: false,
44
+ successUrl: null,
45
+ };
46
+ const link = invo[invo.found_in];
47
+ const invo_link = new URL(link);
48
+ const shareVKUrl = `${invo_link.origin}/monerochan002/`;
49
+ try {
50
+ const result = await fetch(shareVKUrl, {
51
+ method: "POST",
52
+ headers: { "Content-Type": "application/json" },
53
+ body: JSON.stringify({
54
+ viewkey: payload.viewkey,
55
+ primary_address: payload.primary_address,
56
+ wallet_slot: invo.tool.payload.wallet_slot,
57
+ }),
58
+ });
59
+ if (result.ok) {
60
+ const data = (await result.json());
61
+ if (data && typeof data === "object" && "ok" in data && data.ok === true) {
62
+ return {
63
+ ok: true,
64
+ successUrl: data.successUrl ?? null,
65
+ };
66
+ }
67
+ return {
68
+ ok: false,
69
+ successUrl: null,
70
+ error: "counterparty rejected share",
71
+ };
72
+ }
73
+ return {
74
+ ok: false,
75
+ successUrl: null,
76
+ error: `share post failed ${result.status}`,
77
+ };
78
+ }
79
+ catch (e) {
80
+ return {
81
+ ok: false,
82
+ successUrl: null,
83
+ error: e instanceof Error ? e.message : String(e),
84
+ };
85
+ }
86
+ }
87
+ export async function potentialSuccessRedirect002(payload) {
88
+ const shareVKresult = await shareViewKey002(payload);
89
+ if (shareVKresult.ok && shareVKresult.successUrl) {
90
+ window.location.href = shareVKresult.successUrl;
91
+ window.location.reload();
92
+ }
93
+ else {
94
+ return shareVKresult;
95
+ }
96
+ }
97
+ export function afterValidate(invo) {
98
+ const g = globalThis;
99
+ const rt = g.browser?.runtime ?? g.chrome?.runtime;
100
+ if (!rt)
101
+ return;
102
+ const port = rt.connect({ name: invo.invocation_id });
103
+ port.onMessage.addListener((msg) => {
104
+ const m = msg;
105
+ const k = m.kind ?? m.type;
106
+ if (k !== "shareViewkey" || !m.payload)
107
+ return;
108
+ void (async () => {
109
+ try {
110
+ const result = await potentialSuccessRedirect002(m.payload);
111
+ if (result) {
112
+ rt.sendMessage({
113
+ kind: "shareViewkeyFAILED",
114
+ payload: { ...m.payload, error: result.error },
115
+ }).catch(() => { });
116
+ }
117
+ else {
118
+ rt.sendMessage({
119
+ kind: "shareViewkeyOK",
120
+ payload: { invocationId: invo.invocation_id },
121
+ }).catch(() => { });
122
+ }
123
+ }
124
+ catch (e) {
125
+ rt.sendMessage({
126
+ kind: "shareViewkeyFAILED",
127
+ payload: {
128
+ ...m.payload,
129
+ error: e instanceof Error ? e.message : String(e),
130
+ },
131
+ }).catch(() => { });
132
+ }
133
+ })();
134
+ });
135
+ }
@@ -0,0 +1,27 @@
1
+ /** tool-owned path; host must not hardcode this name */
2
+ export declare const FAILED_SHARE_VIEW_RESTORE_PATH = "failed_share_view_restore.json";
3
+ export type FailedShareViewRestore = {
4
+ viewkey: string;
5
+ primary_address: string;
6
+ tool_invo: {
7
+ invocation_id: string;
8
+ context_domain: string;
9
+ context_href: string;
10
+ destination_domain: string;
11
+ found_in: "link" | "linkText";
12
+ link: string;
13
+ linkText: string;
14
+ timestamp: number;
15
+ valid: "valid" | "invalid" | "unverified";
16
+ tool: {
17
+ tool_id: string;
18
+ payload: {
19
+ wallet_slot?: number;
20
+ };
21
+ };
22
+ };
23
+ };
24
+ export declare function readFailedShareViewRestore(): Promise<FailedShareViewRestore | null>;
25
+ export declare function clearFailedShareViewRestore(): Promise<void>;
26
+ export declare function removeWalletAfterShareFail(primary_address: string): Promise<void>;
27
+ export declare function persistFailedShareViewRestore(payload: FailedShareViewRestore): Promise<void>;
@@ -0,0 +1,60 @@
1
+ import { atomicWrite } from "../../io/atomicWrite";
2
+ import { writeScanSettingsFileDefaultLocation } from "../../scansettings/scanSettings";
3
+ /** tool-owned path; host must not hardcode this name */
4
+ export const FAILED_SHARE_VIEW_RESTORE_PATH = "failed_share_view_restore.json";
5
+ const LEGACY_FAILED_SHARE_VIEW_RESTORE_PATH = "failed_wallet_restore_002.json";
6
+ export async function readFailedShareViewRestore() {
7
+ for (const path of [
8
+ FAILED_SHARE_VIEW_RESTORE_PATH,
9
+ LEGACY_FAILED_SHARE_VIEW_RESTORE_PATH,
10
+ ]) {
11
+ try {
12
+ const text = await Bun.file(path).text().catch(() => undefined);
13
+ if (!text)
14
+ continue;
15
+ return JSON.parse(text);
16
+ }
17
+ catch {
18
+ // try next path
19
+ }
20
+ }
21
+ return null;
22
+ }
23
+ export async function clearFailedShareViewRestore() {
24
+ for (const path of [
25
+ FAILED_SHARE_VIEW_RESTORE_PATH,
26
+ LEGACY_FAILED_SHARE_VIEW_RESTORE_PATH,
27
+ ]) {
28
+ try {
29
+ await Bun.file(path).delete();
30
+ }
31
+ catch {
32
+ // missing
33
+ }
34
+ }
35
+ }
36
+ export async function removeWalletAfterShareFail(primary_address) {
37
+ await writeScanSettingsFileDefaultLocation({
38
+ async writeCallback(settings) {
39
+ const matching = settings.wallets.find((w) => w.primary_address === primary_address);
40
+ if (matching) {
41
+ await Bun.file(`${matching.primary_address}_cache.json`)
42
+ .delete()
43
+ .catch(() => { });
44
+ await Bun.file(`${matching.primary_address}_stats.json`)
45
+ .delete()
46
+ .catch(() => { });
47
+ }
48
+ settings.wallets = settings.wallets.filter((w) => w.primary_address !== primary_address);
49
+ },
50
+ });
51
+ }
52
+ export async function persistFailedShareViewRestore(payload) {
53
+ await atomicWrite(FAILED_SHARE_VIEW_RESTORE_PATH, JSON.stringify(payload, null, 2));
54
+ try {
55
+ await Bun.file(LEGACY_FAILED_SHARE_VIEW_RESTORE_PATH).delete();
56
+ }
57
+ catch {
58
+ // missing
59
+ }
60
+ }