@ninsei-labs/sdk 0.39.0 → 0.39.2

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/src/sweep.mjs CHANGED
@@ -14,6 +14,22 @@ const STEPS = ["unlock", "sync", "sweep", "relay"];
14
14
 
15
15
  const contentsOf = (file) => (typeof file === "string" ? file : file && typeof file.contents === "string" ? file.contents : null);
16
16
 
17
+ // A LOW-LEVEL WALLET ERROR IS STILL A REFUSAL, AND EVERY REFUSAL CARRIES A CODE.
18
+ //
19
+ // The library brings its own exceptions with no code at all - monero-ts answers "failed to get hashes" while
20
+ // scanning, and lets it out as a bare `Error`. Releasing one turns a named refusal into `code: null` + raw
21
+ // text: the interface cannot translate it (it has no phrase to look up) and a check cannot assert it. So a
22
+ // wallet step that fails with anything but an SdkError is re-thrown as one, on a code from the package's own
23
+ // dictionary, with the library's message kept as a detail.
24
+ const walletStep = async (surface, step) => {
25
+ try {
26
+ return await step();
27
+ } catch (error) {
28
+ if (error instanceof SdkError) throw error;
29
+ throw new SdkError("wallet-rejected", { surface, why: String((error && error.message) || error) });
30
+ }
31
+ };
32
+
17
33
  export function createSweep({ http, config }) {
18
34
  // THE NAME IS NEEDED BY THE OBJECT ITSELF: `run` calls `api.plan(...)` - the step order is held by the plan and
19
35
  // the withdrawal living in one object, not in two copies. During a manual merge of branches the object was left
@@ -157,9 +173,16 @@ export function createSweep({ http, config }) {
157
173
 
158
174
  // SIGNING WITHOUT SENDING - AS A SEPARATE STEP. The core HOLDS the signed transaction until the person's
159
175
  // decision: otherwise the interface could not show what exactly will leave, and would tell about it only after
160
- // sending. Here is the whole order UP TO AND INCLUDING SIGNING: plan -> wallet -> open -> sync -> free remainder
176
+ // sending. Here is the whole order UP TO AND INCLUDING SIGNING: plan -> wallet -> open -> free remainder
161
177
  // (an empty one STOPS the withdrawal with a code) -> sign (relay: false). The wallet stays open until `relay`,
162
178
  // and the session token is the only handle to it.
179
+ //
180
+ // THE FREE REMAINDER IS READ AND JUDGED BEFORE ANY INPUT SELECTION, AND A SYNC PASS IS NOT THE VERDICT.
181
+ // The wallet library fails low-level while scanning a chain it cannot read (measured: monero-ts `sync()` against
182
+ // a regtest node answering "failed to get hashes"), and that bare error used to escape BEFORE the remainder was
183
+ // read - an empty wallet was refused by a raw exception (`code: null`) instead of `insufficient-funds`. A failed
184
+ // pass is not fatal by itself: the remainder below decides, and anything still blocking the signature is refused
185
+ // WITH A CODE from the dictionary (see `walletStep`).
163
186
  async sign(request) {
164
187
  const plan = await api.plan(request);
165
188
  if (!request || typeof request.to !== "string" || !request.to) throw new SdkError("bad-input", { field: "to" });
@@ -171,12 +194,14 @@ export function createSweep({ http, config }) {
171
194
 
172
195
  await wallet.open(plan);
173
196
  try {
174
- await wallet.sync();
175
- const unlocked = BigInt(await wallet.unlockedBalance());
197
+ // A SYNC PASS - BEST-EFFORT, NOT THE VERDICT. A node whose chain the wallet cannot scan makes the library
198
+ // throw; that must not pre-empt the funds verdict below, and it must not leave as raw text.
199
+ try { await wallet.sync(); } catch { /* the remainder below decides; a real blocker is refused by a code */ }
200
+ const unlocked = BigInt(await walletStep("unlockedBalance", () => wallet.unlockedBalance()));
176
201
  // AN EMPTY REMAINDER STOPS THE WITHDRAWAL BEFORE SIGNING: otherwise the wallet would sign a transaction for
177
202
  // zero, and the refusal would look like success.
178
203
  if (unlocked <= 0n) throw new SdkError("insufficient-funds", { unlocked: unlocked.toString() });
179
- const signed = await wallet.sweepUnlocked({ address: request.to, relay: false });
204
+ const signed = await walletStep("sweepUnlocked", () => wallet.sweepUnlocked({ address: request.to, relay: false }));
180
205
  const token = "sweep-" + (++sessionSeq);
181
206
  sessions.set(token, { wallet, plan, signed });
182
207
  return { token, amount: unlocked.toString(), to: request.to, swept: plan.address };