@bananapus/nana-sdk-core 6.0.0 → 6.2.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 (213) hide show
  1. package/dist/cjs/jbcenter/ensureDeployed.d.ts +2 -2
  2. package/dist/cjs/jbcenter/ensureDeployed.d.ts.map +1 -1
  3. package/dist/cjs/jbcenter/ensureDeployed.js.map +1 -1
  4. package/dist/cjs/jbcenter.d.ts +4 -1
  5. package/dist/cjs/jbcenter.d.ts.map +1 -1
  6. package/dist/cjs/jbcenter.js +6 -6
  7. package/dist/cjs/jbcenter.js.map +1 -1
  8. package/dist/cjs/review/gas.d.ts +5 -2
  9. package/dist/cjs/review/gas.d.ts.map +1 -1
  10. package/dist/cjs/review/gas.js +8 -3
  11. package/dist/cjs/review/gas.js.map +1 -1
  12. package/dist/cjs/review/heldWrite.d.ts +6 -1
  13. package/dist/cjs/review/heldWrite.d.ts.map +1 -1
  14. package/dist/cjs/review/heldWrite.js +21 -16
  15. package/dist/cjs/review/heldWrite.js.map +1 -1
  16. package/dist/cjs/review/index.d.ts +2 -0
  17. package/dist/cjs/review/index.d.ts.map +1 -1
  18. package/dist/cjs/review/index.js +2 -0
  19. package/dist/cjs/review/index.js.map +1 -1
  20. package/dist/cjs/review/locks.d.ts +69 -0
  21. package/dist/cjs/review/locks.d.ts.map +1 -0
  22. package/dist/cjs/review/locks.js +232 -0
  23. package/dist/cjs/review/locks.js.map +1 -0
  24. package/dist/cjs/review/machines.d.ts +9 -0
  25. package/dist/cjs/review/machines.d.ts.map +1 -0
  26. package/dist/cjs/review/machines.js +25 -0
  27. package/dist/cjs/review/machines.js.map +1 -0
  28. package/dist/cjs/review/react/index.d.ts +3 -0
  29. package/dist/cjs/review/react/index.d.ts.map +1 -1
  30. package/dist/cjs/review/react/index.js +3 -0
  31. package/dist/cjs/review/react/index.js.map +1 -1
  32. package/dist/cjs/review/react/useFeeBuybackReview.d.ts +6 -0
  33. package/dist/cjs/review/react/useFeeBuybackReview.d.ts.map +1 -0
  34. package/dist/cjs/review/react/useFeeBuybackReview.js +53 -0
  35. package/dist/cjs/review/react/useFeeBuybackReview.js.map +1 -0
  36. package/dist/cjs/review/react/useMobileWallet.d.ts +15 -0
  37. package/dist/cjs/review/react/useMobileWallet.d.ts.map +1 -0
  38. package/dist/cjs/review/react/useMobileWallet.js +50 -0
  39. package/dist/cjs/review/react/useMobileWallet.js.map +1 -0
  40. package/dist/cjs/review/react/useReviewedRun.d.ts +28 -1
  41. package/dist/cjs/review/react/useReviewedRun.d.ts.map +1 -1
  42. package/dist/cjs/review/react/useReviewedRun.js +251 -39
  43. package/dist/cjs/review/react/useReviewedRun.js.map +1 -1
  44. package/dist/cjs/review/react/useSavedAttempt.d.ts +23 -0
  45. package/dist/cjs/review/react/useSavedAttempt.d.ts.map +1 -1
  46. package/dist/cjs/review/react/useSavedAttempt.js +101 -11
  47. package/dist/cjs/review/react/useSavedAttempt.js.map +1 -1
  48. package/dist/cjs/review/react/useStartOnce.d.ts +8 -0
  49. package/dist/cjs/review/react/useStartOnce.d.ts.map +1 -0
  50. package/dist/cjs/review/react/useStartOnce.js +27 -0
  51. package/dist/cjs/review/react/useStartOnce.js.map +1 -0
  52. package/dist/cjs/review/relayr.d.ts +1 -1
  53. package/dist/cjs/review/relayr.d.ts.map +1 -1
  54. package/dist/cjs/review/relayr.js +15 -0
  55. package/dist/cjs/review/relayr.js.map +1 -1
  56. package/dist/cjs/review/relayrLaunch.d.ts +90 -0
  57. package/dist/cjs/review/relayrLaunch.d.ts.map +1 -0
  58. package/dist/cjs/review/relayrLaunch.js +106 -0
  59. package/dist/cjs/review/relayrLaunch.js.map +1 -0
  60. package/dist/cjs/review/relayrSend.d.ts +5 -0
  61. package/dist/cjs/review/relayrSend.d.ts.map +1 -1
  62. package/dist/cjs/review/relayrSend.js +133 -107
  63. package/dist/cjs/review/relayrSend.js.map +1 -1
  64. package/dist/cjs/review/reviewDialogQueue.d.ts +47 -0
  65. package/dist/cjs/review/reviewDialogQueue.d.ts.map +1 -0
  66. package/dist/cjs/review/reviewDialogQueue.js +120 -0
  67. package/dist/cjs/review/reviewDialogQueue.js.map +1 -0
  68. package/dist/cjs/review/runStatus.d.ts +51 -0
  69. package/dist/cjs/review/runStatus.d.ts.map +1 -0
  70. package/dist/cjs/review/runStatus.js +54 -0
  71. package/dist/cjs/review/runStatus.js.map +1 -0
  72. package/dist/cjs/review/safeRelayr.d.ts +60 -16
  73. package/dist/cjs/review/safeRelayr.d.ts.map +1 -1
  74. package/dist/cjs/review/safeRelayr.js +358 -161
  75. package/dist/cjs/review/safeRelayr.js.map +1 -1
  76. package/dist/cjs/review/savedAttempt.d.ts.map +1 -1
  77. package/dist/cjs/review/savedAttempt.js +7 -2
  78. package/dist/cjs/review/savedAttempt.js.map +1 -1
  79. package/dist/cjs/review/send.d.ts +27 -5
  80. package/dist/cjs/review/send.d.ts.map +1 -1
  81. package/dist/cjs/review/send.js +67 -24
  82. package/dist/cjs/review/send.js.map +1 -1
  83. package/dist/cjs/review/sign.d.ts +6 -1
  84. package/dist/cjs/review/sign.d.ts.map +1 -1
  85. package/dist/cjs/review/sign.js +10 -4
  86. package/dist/cjs/review/sign.js.map +1 -1
  87. package/dist/cjs/review/wallet.d.ts +16 -4
  88. package/dist/cjs/review/wallet.d.ts.map +1 -1
  89. package/dist/cjs/review/wallet.js +60 -17
  90. package/dist/cjs/review/wallet.js.map +1 -1
  91. package/dist/cjs/review/writeHold.d.ts +18 -3
  92. package/dist/cjs/review/writeHold.d.ts.map +1 -1
  93. package/dist/cjs/review/writeHold.js +37 -10
  94. package/dist/cjs/review/writeHold.js.map +1 -1
  95. package/dist/cjs/review/writeRecovery.d.ts +13 -8
  96. package/dist/cjs/review/writeRecovery.d.ts.map +1 -1
  97. package/dist/cjs/review/writeRecovery.js +67 -144
  98. package/dist/cjs/review/writeRecovery.js.map +1 -1
  99. package/dist/cjs/v6/feeBuyback.d.ts +49 -0
  100. package/dist/cjs/v6/feeBuyback.d.ts.map +1 -1
  101. package/dist/cjs/v6/feeBuyback.js +151 -0
  102. package/dist/cjs/v6/feeBuyback.js.map +1 -1
  103. package/dist/cjs/v6/suckers.d.ts +24 -2
  104. package/dist/cjs/v6/suckers.d.ts.map +1 -1
  105. package/dist/cjs/v6/suckers.js +5 -5
  106. package/dist/cjs/v6/suckers.js.map +1 -1
  107. package/dist/esm/jbcenter/ensureDeployed.d.ts +2 -2
  108. package/dist/esm/jbcenter/ensureDeployed.d.ts.map +1 -1
  109. package/dist/esm/jbcenter/ensureDeployed.js.map +1 -1
  110. package/dist/esm/jbcenter.d.ts +4 -1
  111. package/dist/esm/jbcenter.d.ts.map +1 -1
  112. package/dist/esm/jbcenter.js +6 -6
  113. package/dist/esm/jbcenter.js.map +1 -1
  114. package/dist/esm/review/gas.d.ts +5 -2
  115. package/dist/esm/review/gas.d.ts.map +1 -1
  116. package/dist/esm/review/gas.js +7 -3
  117. package/dist/esm/review/gas.js.map +1 -1
  118. package/dist/esm/review/heldWrite.d.ts +6 -1
  119. package/dist/esm/review/heldWrite.d.ts.map +1 -1
  120. package/dist/esm/review/heldWrite.js +21 -16
  121. package/dist/esm/review/heldWrite.js.map +1 -1
  122. package/dist/esm/review/index.d.ts +2 -0
  123. package/dist/esm/review/index.d.ts.map +1 -1
  124. package/dist/esm/review/index.js +2 -0
  125. package/dist/esm/review/index.js.map +1 -1
  126. package/dist/esm/review/locks.d.ts +69 -0
  127. package/dist/esm/review/locks.d.ts.map +1 -0
  128. package/dist/esm/review/locks.js +225 -0
  129. package/dist/esm/review/locks.js.map +1 -0
  130. package/dist/esm/review/machines.d.ts +9 -0
  131. package/dist/esm/review/machines.d.ts.map +1 -0
  132. package/dist/esm/review/machines.js +9 -0
  133. package/dist/esm/review/machines.js.map +1 -0
  134. package/dist/esm/review/react/index.d.ts +3 -0
  135. package/dist/esm/review/react/index.d.ts.map +1 -1
  136. package/dist/esm/review/react/index.js +3 -0
  137. package/dist/esm/review/react/index.js.map +1 -1
  138. package/dist/esm/review/react/useFeeBuybackReview.d.ts +6 -0
  139. package/dist/esm/review/react/useFeeBuybackReview.d.ts.map +1 -0
  140. package/dist/esm/review/react/useFeeBuybackReview.js +50 -0
  141. package/dist/esm/review/react/useFeeBuybackReview.js.map +1 -0
  142. package/dist/esm/review/react/useMobileWallet.d.ts +15 -0
  143. package/dist/esm/review/react/useMobileWallet.d.ts.map +1 -0
  144. package/dist/esm/review/react/useMobileWallet.js +46 -0
  145. package/dist/esm/review/react/useMobileWallet.js.map +1 -0
  146. package/dist/esm/review/react/useReviewedRun.d.ts +28 -1
  147. package/dist/esm/review/react/useReviewedRun.d.ts.map +1 -1
  148. package/dist/esm/review/react/useReviewedRun.js +252 -40
  149. package/dist/esm/review/react/useReviewedRun.js.map +1 -1
  150. package/dist/esm/review/react/useSavedAttempt.d.ts +23 -0
  151. package/dist/esm/review/react/useSavedAttempt.d.ts.map +1 -1
  152. package/dist/esm/review/react/useSavedAttempt.js +101 -11
  153. package/dist/esm/review/react/useSavedAttempt.js.map +1 -1
  154. package/dist/esm/review/react/useStartOnce.d.ts +8 -0
  155. package/dist/esm/review/react/useStartOnce.d.ts.map +1 -0
  156. package/dist/esm/review/react/useStartOnce.js +24 -0
  157. package/dist/esm/review/react/useStartOnce.js.map +1 -0
  158. package/dist/esm/review/relayr.d.ts +1 -1
  159. package/dist/esm/review/relayr.d.ts.map +1 -1
  160. package/dist/esm/review/relayr.js +1 -0
  161. package/dist/esm/review/relayr.js.map +1 -1
  162. package/dist/esm/review/relayrLaunch.d.ts +90 -0
  163. package/dist/esm/review/relayrLaunch.d.ts.map +1 -0
  164. package/dist/esm/review/relayrLaunch.js +97 -0
  165. package/dist/esm/review/relayrLaunch.js.map +1 -0
  166. package/dist/esm/review/relayrSend.d.ts +5 -0
  167. package/dist/esm/review/relayrSend.d.ts.map +1 -1
  168. package/dist/esm/review/relayrSend.js +134 -108
  169. package/dist/esm/review/relayrSend.js.map +1 -1
  170. package/dist/esm/review/reviewDialogQueue.d.ts +47 -0
  171. package/dist/esm/review/reviewDialogQueue.d.ts.map +1 -0
  172. package/dist/esm/review/reviewDialogQueue.js +117 -0
  173. package/dist/esm/review/reviewDialogQueue.js.map +1 -0
  174. package/dist/esm/review/runStatus.d.ts +51 -0
  175. package/dist/esm/review/runStatus.d.ts.map +1 -0
  176. package/dist/esm/review/runStatus.js +51 -0
  177. package/dist/esm/review/runStatus.js.map +1 -0
  178. package/dist/esm/review/safeRelayr.d.ts +60 -16
  179. package/dist/esm/review/safeRelayr.d.ts.map +1 -1
  180. package/dist/esm/review/safeRelayr.js +358 -161
  181. package/dist/esm/review/safeRelayr.js.map +1 -1
  182. package/dist/esm/review/savedAttempt.d.ts.map +1 -1
  183. package/dist/esm/review/savedAttempt.js +7 -2
  184. package/dist/esm/review/savedAttempt.js.map +1 -1
  185. package/dist/esm/review/send.d.ts +27 -5
  186. package/dist/esm/review/send.d.ts.map +1 -1
  187. package/dist/esm/review/send.js +67 -24
  188. package/dist/esm/review/send.js.map +1 -1
  189. package/dist/esm/review/sign.d.ts +6 -1
  190. package/dist/esm/review/sign.d.ts.map +1 -1
  191. package/dist/esm/review/sign.js +10 -4
  192. package/dist/esm/review/sign.js.map +1 -1
  193. package/dist/esm/review/wallet.d.ts +16 -4
  194. package/dist/esm/review/wallet.d.ts.map +1 -1
  195. package/dist/esm/review/wallet.js +60 -17
  196. package/dist/esm/review/wallet.js.map +1 -1
  197. package/dist/esm/review/writeHold.d.ts +18 -3
  198. package/dist/esm/review/writeHold.d.ts.map +1 -1
  199. package/dist/esm/review/writeHold.js +37 -10
  200. package/dist/esm/review/writeHold.js.map +1 -1
  201. package/dist/esm/review/writeRecovery.d.ts +13 -8
  202. package/dist/esm/review/writeRecovery.d.ts.map +1 -1
  203. package/dist/esm/review/writeRecovery.js +63 -142
  204. package/dist/esm/review/writeRecovery.js.map +1 -1
  205. package/dist/esm/v6/feeBuyback.d.ts +49 -0
  206. package/dist/esm/v6/feeBuyback.d.ts.map +1 -1
  207. package/dist/esm/v6/feeBuyback.js +150 -0
  208. package/dist/esm/v6/feeBuyback.js.map +1 -1
  209. package/dist/esm/v6/suckers.d.ts +24 -2
  210. package/dist/esm/v6/suckers.d.ts.map +1 -1
  211. package/dist/esm/v6/suckers.js +5 -5
  212. package/dist/esm/v6/suckers.js.map +1 -1
  213. package/package.json +11 -1
@@ -15,6 +15,7 @@ const safeService_js_1 = require("../safeService.js");
15
15
  const relayr_js_1 = require("./relayr.js");
16
16
  const lineError_js_1 = require("./lineError.js");
17
17
  const tabState_js_1 = require("./tabState.js");
18
+ const locks_js_1 = require("./locks.js");
18
19
  function hasFundingRecords(records) {
19
20
  if (records === undefined)
20
21
  return false;
@@ -74,6 +75,15 @@ function canReplaceSafeRelayrQuote(session) {
74
75
  return false;
75
76
  }
76
77
  }
78
+ const SAFE_RELAYR_VIEW = {
79
+ phase: null,
80
+ checks: {},
81
+ executions: {},
82
+ result: null,
83
+ running: null,
84
+ error: null,
85
+ session: null,
86
+ };
77
87
  /** The line a Safe action shows when its payment's status couldn't be read. */
78
88
  exports.SAFE_RELAYR_STATUS_UNKNOWN_LINE = "Couldn't check this payment's status. Try again in a moment.";
79
89
  /** The line a Safe action shows when its quote can no longer be paid but hasn't expired onchain. */
@@ -333,14 +343,28 @@ function createSafeRelayrController(options) {
333
343
  * `fundingObservedAt` another flow saved. Nothing ever clears them; an
334
344
  * exit keeps them for reconciliation.
335
345
  */
346
+ /** The locks each account's `withLock` holds, while it runs. */
347
+ const storeLocks = new Map();
336
348
  const store = {
337
349
  get scope() {
338
350
  return options.store.scope;
339
351
  },
340
352
  list: (account) => options.store.list(account),
341
- withLock: (account, run) => options.store.withLock(account, run),
353
+ withLock: (account, run) => options.store.withLock(account, async (locks) => {
354
+ const key = account.toLowerCase();
355
+ const outer = storeLocks.get(key);
356
+ storeLocks.set(key, locks);
357
+ try {
358
+ return await run(locks);
359
+ }
360
+ finally {
361
+ storeLocks.set(key, outer);
362
+ }
363
+ }),
342
364
  async save(next) {
343
365
  const saved = (await options.store.list(next.account)).find((session) => session.id === next.id);
366
+ // Under the store's lock: still this tab's after every read, right before the write.
367
+ await (0, locks_js_1.assertLocksHeld)(storeLocks.get(next.account.toLowerCase()));
344
368
  const at = [saved?.fundingObservedAt, next.fundingObservedAt].filter((value) => typeof value === "number");
345
369
  // Once seen, funding stays, and its clock only moves forward.
346
370
  return options.store.save(saved?.fundingObserved || next.fundingObserved
@@ -354,7 +378,23 @@ function createSafeRelayrController(options) {
354
378
  };
355
379
  const fetchRelayr = options.fetch ?? globalThis.fetch;
356
380
  const displayedExecutions = new Map();
357
- const notify = (progress, rechecking = false) => {
381
+ let view = SAFE_RELAYR_VIEW;
382
+ /** The calls in flight with what each runs, and the last call started before the last reset. */
383
+ const flying = new Map();
384
+ let since = 0;
385
+ /** Each call's own signal, to that call: its progress names the call it came from. */
386
+ const callOf = new WeakMap();
387
+ const listeners = new Set();
388
+ const show = (patch) => {
389
+ view = { ...view, ...patch };
390
+ for (const listener of listeners)
391
+ void (0, receipt_js_1.tell)(listener);
392
+ };
393
+ const notify = (progress, rechecking, signal) => {
394
+ // A call's progress shows once it started since the last reset: a call
395
+ // from before it changes nothing shown. Overlapping calls of one run all show.
396
+ const call = signal && callOf.get(signal);
397
+ const shows = call !== undefined && call > since;
358
398
  if (progress.type === "execution") {
359
399
  const key = `${progress.session.id}:${progress.index}`;
360
400
  const display = JSON.stringify([
@@ -369,15 +409,35 @@ function createSafeRelayrController(options) {
369
409
  (rechecking &&
370
410
  previous === JSON.stringify(["executed", progress.hash, undefined])))
371
411
  return;
372
- displayedExecutions.set(key, display);
412
+ // Only a shown row is a repeat, so a held one shows from the next call.
413
+ if (shows)
414
+ displayedExecutions.set(key, display);
373
415
  }
416
+ if (shows)
417
+ show({
418
+ ...(progress.session
419
+ ? { session: structuredClone(progress.session) }
420
+ : {}),
421
+ ...(progress.type === "phase"
422
+ ? { phase: progress.phase }
423
+ : {
424
+ executions: {
425
+ ...view.executions,
426
+ [progress.index]: {
427
+ status: progress.status,
428
+ ...(progress.hash ? { hash: progress.hash } : {}),
429
+ ...(progress.message ? { message: progress.message } : {}),
430
+ },
431
+ },
432
+ }),
433
+ });
374
434
  // A view must not mutate frozen calls or turn a submitted payment into a
375
435
  // rejected operation: it gets a copy, and its failure is only logged.
376
436
  void (0, receipt_js_1.tell)((shown) => options.onProgress?.(structuredClone(shown)), progress);
377
437
  };
378
438
  const phase = (phase, session, signal) => {
379
439
  if (!signal?.aborted)
380
- notify({ type: "phase", phase, session });
440
+ notify({ type: "phase", phase, session }, false, signal);
381
441
  };
382
442
  const savedPhase = (session, signal) => {
383
443
  if (session.state === "released")
@@ -755,7 +815,7 @@ function createSafeRelayrController(options) {
755
815
  async function inspect(session, signal) {
756
816
  const report = (progress, rechecking = false) => {
757
817
  if (!signal?.aborted)
758
- notify(progress, rechecking);
818
+ notify(progress, rechecking, signal);
759
819
  };
760
820
  try {
761
821
  savedPhase(session, signal);
@@ -1170,179 +1230,240 @@ function createSafeRelayrController(options) {
1170
1230
  });
1171
1231
  }
1172
1232
  async function fund({ account, sessionId, paymentChainId, signal, onStatus, }) {
1173
- return store.withLock(account, async () => {
1174
- checkAccount(account, signal);
1175
- let session = await load(account, sessionId);
1176
- const checked = await inspect(session, signal);
1177
- session = checked.session;
1178
- if (checked.state !== "ready")
1179
- throw new SafeRelayrRecoveryError(session, checked.recovery?.message, checked.recovery);
1180
- if (session.payments.length >= relayr_js_1.MAX_RELAYR_SENT_PAYMENTS)
1181
- throw new SafeRelayrRecoveryError(session, "This quote has reached its saved payment attempt limit. Check the existing bundle.");
1182
- const payment = checked.payments.find((option) => option.chain === paymentChainId);
1183
- if (!payment)
1184
- throw new Error("Choose a current payment option from this saved quote.");
1185
- await assertUnreserved(account, session.executions, session.id);
1186
- if (!reviewedIntents.has(reviewKey(session))) {
1187
- phase("reviewing", session, signal);
1188
- await options.review(session.executions, { resumed: true });
1189
- checkAccount(account, signal);
1190
- rememberReview(session);
1233
+ // Once this attempt saved "sending", every later save goes through
1234
+ // `keep`: one refused for a lost lock leaves what it would have saved
1235
+ // (the attempt's outcome) to be applied under a fresh lock, to the saved
1236
+ // record only while it is still this attempt's.
1237
+ let late;
1238
+ const keep = async (next, onLost) => {
1239
+ try {
1240
+ await store.save(next);
1191
1241
  }
1192
- let verified = false;
1193
- let sending = false;
1194
- let paymentPersisted = false;
1195
- const requireOpenPayment = () => (0, relayr_js_1.relayrPaymentDetails)(payment, {
1196
- bundleUuid: session.bundleUuid,
1197
- destinationChainIds: session.executions.map(({ entry }) => entry.chain),
1198
- });
1199
- const beforeSend = async () => {
1200
- phase("payment-checking", session, signal);
1201
- await revalidate(session.executions, account, onStatus, true, signal);
1242
+ catch (error) {
1243
+ if (error instanceof locks_js_1.LockLostError)
1244
+ late = onLost;
1245
+ throw error;
1246
+ }
1247
+ };
1248
+ try {
1249
+ return await funding();
1250
+ }
1251
+ catch (error) {
1252
+ const settle = late;
1253
+ if (settle)
1254
+ await store
1255
+ .withLock(account, async () => {
1256
+ const next = settle(await load(account, sessionId));
1257
+ if (next)
1258
+ await store.save(next);
1259
+ })
1260
+ // Unreconciled, the record stays as the lost lock left it.
1261
+ .catch(() => undefined);
1262
+ throw error;
1263
+ }
1264
+ async function funding() {
1265
+ return store.withLock(account, async () => {
1266
+ checkAccount(account, signal);
1267
+ let session = await load(account, sessionId);
1268
+ const checked = await inspect(session, signal);
1269
+ session = checked.session;
1270
+ if (checked.state !== "ready")
1271
+ throw new SafeRelayrRecoveryError(session, checked.recovery?.message, checked.recovery);
1272
+ if (session.payments.length >= relayr_js_1.MAX_RELAYR_SENT_PAYMENTS)
1273
+ throw new SafeRelayrRecoveryError(session, "This quote has reached its saved payment attempt limit. Check the existing bundle.");
1274
+ const payment = checked.payments.find((option) => option.chain === paymentChainId);
1275
+ if (!payment)
1276
+ throw new Error("Choose a current payment option from this saved quote.");
1202
1277
  await assertUnreserved(account, session.executions, session.id);
1203
- await statusRead(() => session.payments.length
1204
- ? (0, relayr_js_1.requireRelayrRetry)(options.clientFor, {
1205
- payments: session.payments,
1206
- from: account,
1207
- bundleUuid: session.bundleUuid,
1208
- }, { fetch: fetchRelayr })
1209
- : (0, relayr_js_1.requireRelayrBundleUnpaid)(session.bundleUuid, {
1210
- fetch: fetchRelayr,
1211
- })).catch(async (error) => {
1212
- if (statusUnknown(error))
1213
- throw unknownStatus(session);
1214
- if (fundingReported(error))
1215
- session = await observed(session);
1216
- throw error;
1278
+ if (!reviewedIntents.has(reviewKey(session))) {
1279
+ phase("reviewing", session, signal);
1280
+ await options.review(session.executions, { resumed: true });
1281
+ checkAccount(account, signal);
1282
+ rememberReview(session);
1283
+ }
1284
+ let verified = false;
1285
+ let sending = false;
1286
+ let paymentPersisted = false;
1287
+ const requireOpenPayment = () => (0, relayr_js_1.relayrPaymentDetails)(payment, {
1288
+ bundleUuid: session.bundleUuid,
1289
+ destinationChainIds: session.executions.map(({ entry }) => entry.chain),
1217
1290
  });
1218
- checkAccount(account, signal);
1219
- requireOpenPayment();
1220
- verified = true;
1221
- };
1222
- const onSending = async () => {
1223
- checkAccount(account, signal);
1224
- if (!verified || sending)
1225
- throw new Error("Recheck the Safe execution before funding it.");
1226
- // The saved record, read again just before the wallet opens: funding
1227
- // another flow reported, or any change to its payment, refuses.
1228
- const stored = await load(account, session.id);
1229
- if (hasObservedFunding(stored) ||
1230
- stored.state !== session.state ||
1231
- stored.paymentStatus !== session.paymentStatus ||
1232
- stored.bundleUuid !== session.bundleUuid ||
1233
- JSON.stringify(stored.payments) !== JSON.stringify(session.payments))
1234
- throw new SafeRelayrRecoveryError(stored, hasObservedFunding(stored)
1235
- ? exports.SAFE_RELAYR_FUNDING_EXIT_LINE
1236
- : "This saved selection changed while it was being paid. Check it again before paying.");
1237
- session = { ...session, paymentStatus: "sending" };
1238
- await store.save(session);
1239
- try {
1291
+ const beforeSend = async () => {
1292
+ phase("payment-checking", session, signal);
1293
+ await revalidate(session.executions, account, onStatus, true, signal);
1294
+ await assertUnreserved(account, session.executions, session.id);
1295
+ await statusRead(() => session.payments.length
1296
+ ? (0, relayr_js_1.requireRelayrRetry)(options.clientFor, {
1297
+ payments: session.payments,
1298
+ from: account,
1299
+ bundleUuid: session.bundleUuid,
1300
+ }, { fetch: fetchRelayr })
1301
+ : (0, relayr_js_1.requireRelayrBundleUnpaid)(session.bundleUuid, {
1302
+ fetch: fetchRelayr,
1303
+ })).catch(async (error) => {
1304
+ if (statusUnknown(error))
1305
+ throw unknownStatus(session);
1306
+ if (fundingReported(error))
1307
+ session = await observed(session);
1308
+ throw error;
1309
+ });
1240
1310
  checkAccount(account, signal);
1241
1311
  requireOpenPayment();
1242
- }
1243
- catch (error) {
1312
+ verified = true;
1313
+ };
1314
+ /**
1315
+ * Nothing was sent after "sending" was saved: the status before this
1316
+ * attempt again. A lost lock leaves it to the fresh-lock reconcile,
1317
+ * and the caller still gets the reason nothing was sent.
1318
+ */
1319
+ const restoreUnsent = async () => {
1244
1320
  session = {
1245
1321
  ...session,
1246
1322
  paymentStatus: checked.session.paymentStatus,
1247
1323
  };
1324
+ const restored = session;
1325
+ await keep(restored, (current) => current.paymentStatus === "sending" &&
1326
+ current.bundleUuid === restored.bundleUuid &&
1327
+ JSON.stringify(current.payments) ===
1328
+ JSON.stringify(restored.payments)
1329
+ ? { ...current, paymentStatus: restored.paymentStatus }
1330
+ : undefined).catch((saveError) => {
1331
+ if (!(saveError instanceof locks_js_1.LockLostError))
1332
+ throw saveError;
1333
+ });
1334
+ };
1335
+ const onSending = async () => {
1336
+ checkAccount(account, signal);
1337
+ if (!verified || sending)
1338
+ throw new Error("Recheck the Safe execution before funding it.");
1339
+ // The saved record, read again just before the wallet opens: funding
1340
+ // another flow reported, or any change to its payment, refuses.
1341
+ const stored = await load(account, session.id);
1342
+ if (hasObservedFunding(stored) ||
1343
+ stored.state !== session.state ||
1344
+ stored.paymentStatus !== session.paymentStatus ||
1345
+ stored.bundleUuid !== session.bundleUuid ||
1346
+ JSON.stringify(stored.payments) !== JSON.stringify(session.payments))
1347
+ throw new SafeRelayrRecoveryError(stored, hasObservedFunding(stored)
1348
+ ? exports.SAFE_RELAYR_FUNDING_EXIT_LINE
1349
+ : "This saved selection changed while it was being paid. Check it again before paying.");
1350
+ session = { ...session, paymentStatus: "sending" };
1248
1351
  await store.save(session);
1249
- throw error;
1250
- }
1251
- sending = true;
1252
- phase("payment-submitting", session, signal);
1253
- };
1254
- const onSent = async (payments) => {
1255
- const snapshot = (0, relayr_js_1.relayrSentPaymentsSnapshot)(payments);
1256
- if (!sending ||
1257
- !snapshot?.length ||
1258
- snapshot.some((item) => item.bundleUuid !== session.bundleUuid))
1259
- throw new Error("The payment does not match the saved Safe bundle.");
1260
- if (!snapshot.some((item) => !checked.session.payments.some((prior) => prior.hash.toLowerCase() === item.hash.toLowerCase())))
1261
- throw new Error("The new payment attempt has no new transaction hash.");
1262
- for (const prior of session.payments) {
1263
- if (!snapshot.some((item) => item.hash.toLowerCase() === prior.hash.toLowerCase() &&
1264
- item.chainId === prior.chainId &&
1265
- item.calldata.toLowerCase() === prior.calldata.toLowerCase() &&
1266
- item.amount === prior.amount &&
1267
- item.deadline === prior.deadline &&
1268
- item.target.toLowerCase() === prior.target.toLowerCase()))
1269
- throw new Error("The saved payment history must retain every known payment.");
1270
- }
1271
- const details = (0, relayr_js_1.relayrPaymentDetails)(payment, {
1272
- bundleUuid: session.bundleUuid,
1273
- destinationChainIds: session.executions.map(({ entry }) => entry.chain),
1274
- nowSeconds: 0,
1275
- });
1276
- for (const item of snapshot.filter((item) => !session.payments.some((prior) => prior.hash === item.hash))) {
1277
- if (item.chainId !== details.chainId ||
1278
- BigInt(item.amount) !== details.amount ||
1279
- BigInt(item.deadline) !== details.deadline ||
1280
- item.calldata.toLowerCase() !== details.calldata.toLowerCase() ||
1281
- item.target.toLowerCase() !== details.target.toLowerCase())
1282
- throw new Error("The funding payment differs from the selected Relayr option.");
1283
- }
1284
- paymentPersisted = false;
1285
- session = {
1286
- ...session,
1287
- payments: structuredClone(payments),
1288
- paymentStatus: "submitted",
1352
+ try {
1353
+ checkAccount(account, signal);
1354
+ requireOpenPayment();
1355
+ }
1356
+ catch (error) {
1357
+ await restoreUnsent();
1358
+ throw error;
1359
+ }
1360
+ sending = true;
1361
+ phase("payment-submitting", session, signal);
1289
1362
  };
1290
- await store.save(session);
1291
- paymentPersisted = true;
1292
- phase("payment-confirming", session, signal);
1293
- };
1294
- try {
1295
- phase("payment-review", session, signal);
1296
- const sent = await options.sendPayment({
1297
- session: structuredClone(session),
1298
- payment,
1299
- beforeSend,
1300
- onSending,
1301
- onSent,
1302
- });
1303
- if (!sent.payments.some((item) => item.hash.toLowerCase() === sent.hash.toLowerCase()))
1304
- throw new Error("The funding result omitted its transaction hash.");
1305
- await onSent(sent.payments);
1306
- }
1307
- catch (error) {
1308
- if (sending &&
1309
- // Classify only this attempt; restoring the previous status below
1310
- // preserves the quote's earlier payments and their retry policy.
1311
- (0, relayr_js_1.relayrPaymentAttemptOutcome)(error, { sending, paid: false }) ===
1312
- "unpaid" &&
1313
- session.payments.length === checked.session.payments.length) {
1363
+ const onSent = async (payments) => {
1364
+ const snapshot = (0, relayr_js_1.relayrSentPaymentsSnapshot)(payments);
1365
+ if (!sending ||
1366
+ !snapshot?.length ||
1367
+ snapshot.some((item) => item.bundleUuid !== session.bundleUuid))
1368
+ throw new Error("The payment does not match the saved Safe bundle.");
1369
+ if (!snapshot.some((item) => !checked.session.payments.some((prior) => prior.hash.toLowerCase() === item.hash.toLowerCase())))
1370
+ throw new Error("The new payment attempt has no new transaction hash.");
1371
+ for (const prior of session.payments) {
1372
+ if (!snapshot.some((item) => item.hash.toLowerCase() === prior.hash.toLowerCase() &&
1373
+ item.chainId === prior.chainId &&
1374
+ item.calldata.toLowerCase() ===
1375
+ prior.calldata.toLowerCase() &&
1376
+ item.amount === prior.amount &&
1377
+ item.deadline === prior.deadline &&
1378
+ item.target.toLowerCase() === prior.target.toLowerCase()))
1379
+ throw new Error("The saved payment history must retain every known payment.");
1380
+ }
1381
+ const details = (0, relayr_js_1.relayrPaymentDetails)(payment, {
1382
+ bundleUuid: session.bundleUuid,
1383
+ destinationChainIds: session.executions.map(({ entry }) => entry.chain),
1384
+ nowSeconds: 0,
1385
+ });
1386
+ for (const item of snapshot.filter((item) => !session.payments.some((prior) => prior.hash === item.hash))) {
1387
+ if (item.chainId !== details.chainId ||
1388
+ BigInt(item.amount) !== details.amount ||
1389
+ BigInt(item.deadline) !== details.deadline ||
1390
+ item.calldata.toLowerCase() !== details.calldata.toLowerCase() ||
1391
+ item.target.toLowerCase() !== details.target.toLowerCase())
1392
+ throw new Error("The funding payment differs from the selected Relayr option.");
1393
+ }
1394
+ paymentPersisted = false;
1314
1395
  session = {
1315
1396
  ...session,
1316
- paymentStatus: checked.session.paymentStatus,
1397
+ payments: structuredClone(payments),
1398
+ paymentStatus: "submitted",
1317
1399
  };
1318
- await store.save(session);
1319
- throw error;
1400
+ const sent = session.payments;
1401
+ await keep(session, (current) => {
1402
+ const missing = sent.filter((item) => !current.payments.some((prior) => prior.hash.toLowerCase() === item.hash.toLowerCase()));
1403
+ return current.bundleUuid === session.bundleUuid &&
1404
+ (missing.length || current.paymentStatus === "sending")
1405
+ ? {
1406
+ ...current,
1407
+ payments: [...current.payments, ...missing],
1408
+ paymentStatus: current.paymentStatus === "sending" ||
1409
+ current.paymentStatus === "unfunded"
1410
+ ? "submitted"
1411
+ : current.paymentStatus,
1412
+ }
1413
+ : undefined;
1414
+ });
1415
+ paymentPersisted = true;
1416
+ phase("payment-confirming", session, signal);
1417
+ };
1418
+ try {
1419
+ phase("payment-review", session, signal);
1420
+ const sent = await options.sendPayment({
1421
+ session: structuredClone(session),
1422
+ payment,
1423
+ beforeSend,
1424
+ onSending,
1425
+ onSent,
1426
+ });
1427
+ if (!sent.payments.some((item) => item.hash.toLowerCase() === sent.hash.toLowerCase()))
1428
+ throw new Error("The funding result omitted its transaction hash.");
1429
+ await onSent(sent.payments);
1320
1430
  }
1321
- if (sending ||
1322
- session.payments.length > checked.session.payments.length) {
1323
- // A saved hash may already have funded or completed the bundle even
1324
- // when the wallet adapter could not prove it. Inspect once, read-only;
1325
- // never turn an uncertain or unsaved attempt into a retry invitation.
1326
- if (paymentPersisted) {
1327
- try {
1328
- const inspected = await inspect(session, signal);
1329
- session = inspected.session;
1330
- if (inspected.state === "complete" ||
1331
- (inspected.state === "pending" &&
1332
- session.paymentStatus === "confirmed"))
1333
- return inspected;
1334
- }
1335
- catch (inspectionError) {
1336
- if (inspectionError instanceof SafeRelayrRecoveryError)
1337
- session = inspectionError.session;
1431
+ catch (error) {
1432
+ if (sending &&
1433
+ // Classify only this attempt; restoring the previous status below
1434
+ // preserves the quote's earlier payments and their retry policy.
1435
+ (0, relayr_js_1.relayrPaymentAttemptOutcome)(error, { sending, paid: false }) ===
1436
+ "unpaid" &&
1437
+ session.payments.length === checked.session.payments.length) {
1438
+ await restoreUnsent();
1439
+ throw error;
1440
+ }
1441
+ if (sending ||
1442
+ session.payments.length > checked.session.payments.length) {
1443
+ // A saved hash may already have funded or completed the bundle even
1444
+ // when the wallet adapter could not prove it. Inspect once, read-only;
1445
+ // never turn an uncertain or unsaved attempt into a retry invitation.
1446
+ if (paymentPersisted) {
1447
+ try {
1448
+ const inspected = await inspect(session, signal);
1449
+ session = inspected.session;
1450
+ if (inspected.state === "complete" ||
1451
+ (inspected.state === "pending" &&
1452
+ session.paymentStatus === "confirmed"))
1453
+ return inspected;
1454
+ }
1455
+ catch (inspectionError) {
1456
+ if (inspectionError instanceof SafeRelayrRecoveryError)
1457
+ session = inspectionError.session;
1458
+ }
1338
1459
  }
1460
+ throw new SafeRelayrRecoveryError(session, error instanceof Error ? error.message : undefined);
1339
1461
  }
1340
- throw new SafeRelayrRecoveryError(session, error instanceof Error ? error.message : undefined);
1462
+ throw error;
1341
1463
  }
1342
- throw error;
1343
- }
1344
- return inspect(session, signal);
1345
- });
1464
+ return inspect(session, signal);
1465
+ });
1466
+ }
1346
1467
  }
1347
1468
  /** Read-only progress polling; each read releases its lock before the wait. */
1348
1469
  async function watch({ account, sessionId, signal, onUpdate, intervalMs = 2_500, timeoutMs = 300_000, }) {
@@ -1361,6 +1482,10 @@ function createSafeRelayrController(options) {
1361
1482
  if (signal?.aborted)
1362
1483
  stop();
1363
1484
  const halted = halt.signal;
1485
+ // Its checks' progress is the watch's own.
1486
+ const call = signal && callOf.get(signal);
1487
+ if (call !== undefined)
1488
+ callOf.set(halted, call);
1364
1489
  try {
1365
1490
  const started = Date.now();
1366
1491
  while (true) {
@@ -1411,6 +1536,78 @@ function createSafeRelayrController(options) {
1411
1536
  return result("released", session);
1412
1537
  });
1413
1538
  }
1414
- return { prepare, check, fund, watch, exit };
1539
+ let calls = 0;
1540
+ /**
1541
+ * Runs one call with its result, error and execution checks shown in the
1542
+ * view: the latest call since the last reset shows, and so does a watch of
1543
+ * the current run throughout, since the run waits on its polls.
1544
+ */
1545
+ const shown = (running, run) => async (args) => {
1546
+ const given = args;
1547
+ const call = ++calls;
1548
+ const shows = () => call === calls || (running === "watch" && call > since);
1549
+ // What still runs once this call ends: the latest call, else a watch of the current run.
1550
+ const still = () => flying.get(calls) ??
1551
+ ([...flying].some(([id, kind]) => kind === "watch" && id > since)
1552
+ ? "watch"
1553
+ : null);
1554
+ flying.set(call, running);
1555
+ // A signal of its own, even when the caller shares one between calls.
1556
+ const signal = given.signal
1557
+ ? AbortSignal.any([given.signal])
1558
+ : new AbortController().signal;
1559
+ callOf.set(signal, call);
1560
+ show({ running, error: null });
1561
+ try {
1562
+ const result = await run({
1563
+ ...args,
1564
+ signal,
1565
+ onStatus: (index, state, execution) => {
1566
+ if (shows())
1567
+ show({ checks: { ...view.checks, [index]: state } });
1568
+ given.onStatus?.(index, state, execution);
1569
+ },
1570
+ ...(running === "watch"
1571
+ ? {
1572
+ onUpdate: (result) => {
1573
+ if (shows())
1574
+ show({ result, error: null });
1575
+ return given.onUpdate?.(result);
1576
+ },
1577
+ }
1578
+ : {}),
1579
+ });
1580
+ flying.delete(call);
1581
+ if (shows())
1582
+ show({ result, error: null, running: still() });
1583
+ return result;
1584
+ }
1585
+ catch (error) {
1586
+ flying.delete(call);
1587
+ if (shows())
1588
+ show({ error, running: still() });
1589
+ throw error;
1590
+ }
1591
+ };
1592
+ return {
1593
+ prepare: shown("prepare", prepare),
1594
+ check: shown("check", check),
1595
+ fund: shown("fund", fund),
1596
+ watch: shown("watch", watch),
1597
+ exit: shown("exit", exit),
1598
+ getState: () => view,
1599
+ subscribe(listener) {
1600
+ listeners.add(listener);
1601
+ return () => {
1602
+ listeners.delete(listener);
1603
+ };
1604
+ },
1605
+ /** Clears the view, as a new run starts. */
1606
+ reset() {
1607
+ since = calls += 1;
1608
+ displayedExecutions.clear();
1609
+ show(SAFE_RELAYR_VIEW);
1610
+ },
1611
+ };
1415
1612
  }
1416
1613
  //# sourceMappingURL=safeRelayr.js.map