@bananapus/nana-sdk-core 6.1.0 → 6.3.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 (149) hide show
  1. package/dist/cjs/review/confirming.d.ts +8 -0
  2. package/dist/cjs/review/confirming.d.ts.map +1 -0
  3. package/dist/cjs/review/confirming.js +11 -0
  4. package/dist/cjs/review/confirming.js.map +1 -0
  5. package/dist/cjs/review/heldWrite.d.ts +6 -1
  6. package/dist/cjs/review/heldWrite.d.ts.map +1 -1
  7. package/dist/cjs/review/heldWrite.js +13 -6
  8. package/dist/cjs/review/heldWrite.js.map +1 -1
  9. package/dist/cjs/review/locks.d.ts +141 -2
  10. package/dist/cjs/review/locks.d.ts.map +1 -1
  11. package/dist/cjs/review/locks.js +406 -58
  12. package/dist/cjs/review/locks.js.map +1 -1
  13. package/dist/cjs/review/messages.d.ts +16 -0
  14. package/dist/cjs/review/messages.d.ts.map +1 -1
  15. package/dist/cjs/review/messages.js +17 -1
  16. package/dist/cjs/review/messages.js.map +1 -1
  17. package/dist/cjs/review/react/ownership.d.ts +30 -0
  18. package/dist/cjs/review/react/ownership.d.ts.map +1 -0
  19. package/dist/cjs/review/react/ownership.js +27 -0
  20. package/dist/cjs/review/react/ownership.js.map +1 -0
  21. package/dist/cjs/review/react/useReviewedRun.d.ts +17 -3
  22. package/dist/cjs/review/react/useReviewedRun.d.ts.map +1 -1
  23. package/dist/cjs/review/react/useReviewedRun.js +528 -68
  24. package/dist/cjs/review/react/useReviewedRun.js.map +1 -1
  25. package/dist/cjs/review/react/useSavedAttempt.d.ts.map +1 -1
  26. package/dist/cjs/review/react/useSavedAttempt.js +12 -0
  27. package/dist/cjs/review/react/useSavedAttempt.js.map +1 -1
  28. package/dist/cjs/review/relayrSend.d.ts +20 -0
  29. package/dist/cjs/review/relayrSend.d.ts.map +1 -1
  30. package/dist/cjs/review/relayrSend.js +150 -105
  31. package/dist/cjs/review/relayrSend.js.map +1 -1
  32. package/dist/cjs/review/runStatus.d.ts +6 -2
  33. package/dist/cjs/review/runStatus.d.ts.map +1 -1
  34. package/dist/cjs/review/runStatus.js +3 -1
  35. package/dist/cjs/review/runStatus.js.map +1 -1
  36. package/dist/cjs/review/safeRelayr.d.ts +12 -2
  37. package/dist/cjs/review/safeRelayr.d.ts.map +1 -1
  38. package/dist/cjs/review/safeRelayr.js +226 -156
  39. package/dist/cjs/review/safeRelayr.js.map +1 -1
  40. package/dist/cjs/review/savedAttempt.d.ts.map +1 -1
  41. package/dist/cjs/review/savedAttempt.js +7 -2
  42. package/dist/cjs/review/savedAttempt.js.map +1 -1
  43. package/dist/cjs/review/send.d.ts +46 -5
  44. package/dist/cjs/review/send.d.ts.map +1 -1
  45. package/dist/cjs/review/send.js +251 -58
  46. package/dist/cjs/review/send.js.map +1 -1
  47. package/dist/cjs/review/sequence.d.ts +25 -0
  48. package/dist/cjs/review/sequence.d.ts.map +1 -1
  49. package/dist/cjs/review/sequence.js +45 -7
  50. package/dist/cjs/review/sequence.js.map +1 -1
  51. package/dist/cjs/review/sign.d.ts +6 -1
  52. package/dist/cjs/review/sign.d.ts.map +1 -1
  53. package/dist/cjs/review/sign.js +10 -4
  54. package/dist/cjs/review/sign.js.map +1 -1
  55. package/dist/cjs/review/tabState.d.ts +4 -0
  56. package/dist/cjs/review/tabState.d.ts.map +1 -1
  57. package/dist/cjs/review/tabState.js +4 -0
  58. package/dist/cjs/review/tabState.js.map +1 -1
  59. package/dist/cjs/review/wallet.d.ts +16 -4
  60. package/dist/cjs/review/wallet.d.ts.map +1 -1
  61. package/dist/cjs/review/wallet.js +62 -17
  62. package/dist/cjs/review/wallet.js.map +1 -1
  63. package/dist/cjs/review/writeHold.d.ts +92 -14
  64. package/dist/cjs/review/writeHold.d.ts.map +1 -1
  65. package/dist/cjs/review/writeHold.js +102 -26
  66. package/dist/cjs/review/writeHold.js.map +1 -1
  67. package/dist/cjs/review/writeRecovery.d.ts +42 -7
  68. package/dist/cjs/review/writeRecovery.d.ts.map +1 -1
  69. package/dist/cjs/review/writeRecovery.js +183 -21
  70. package/dist/cjs/review/writeRecovery.js.map +1 -1
  71. package/dist/cjs/testing.d.ts +13 -0
  72. package/dist/cjs/testing.d.ts.map +1 -1
  73. package/dist/cjs/testing.js +21 -0
  74. package/dist/cjs/testing.js.map +1 -1
  75. package/dist/esm/review/confirming.d.ts +8 -0
  76. package/dist/esm/review/confirming.d.ts.map +1 -0
  77. package/dist/esm/review/confirming.js +8 -0
  78. package/dist/esm/review/confirming.js.map +1 -0
  79. package/dist/esm/review/heldWrite.d.ts +6 -1
  80. package/dist/esm/review/heldWrite.d.ts.map +1 -1
  81. package/dist/esm/review/heldWrite.js +13 -6
  82. package/dist/esm/review/heldWrite.js.map +1 -1
  83. package/dist/esm/review/locks.d.ts +141 -2
  84. package/dist/esm/review/locks.d.ts.map +1 -1
  85. package/dist/esm/review/locks.js +396 -58
  86. package/dist/esm/review/locks.js.map +1 -1
  87. package/dist/esm/review/messages.d.ts +16 -0
  88. package/dist/esm/review/messages.d.ts.map +1 -1
  89. package/dist/esm/review/messages.js +16 -0
  90. package/dist/esm/review/messages.js.map +1 -1
  91. package/dist/esm/review/react/ownership.d.ts +30 -0
  92. package/dist/esm/review/react/ownership.d.ts.map +1 -0
  93. package/dist/esm/review/react/ownership.js +24 -0
  94. package/dist/esm/review/react/ownership.js.map +1 -0
  95. package/dist/esm/review/react/useReviewedRun.d.ts +17 -3
  96. package/dist/esm/review/react/useReviewedRun.d.ts.map +1 -1
  97. package/dist/esm/review/react/useReviewedRun.js +529 -69
  98. package/dist/esm/review/react/useReviewedRun.js.map +1 -1
  99. package/dist/esm/review/react/useSavedAttempt.d.ts.map +1 -1
  100. package/dist/esm/review/react/useSavedAttempt.js +12 -0
  101. package/dist/esm/review/react/useSavedAttempt.js.map +1 -1
  102. package/dist/esm/review/relayrSend.d.ts +20 -0
  103. package/dist/esm/review/relayrSend.d.ts.map +1 -1
  104. package/dist/esm/review/relayrSend.js +154 -109
  105. package/dist/esm/review/relayrSend.js.map +1 -1
  106. package/dist/esm/review/runStatus.d.ts +6 -2
  107. package/dist/esm/review/runStatus.d.ts.map +1 -1
  108. package/dist/esm/review/runStatus.js +3 -1
  109. package/dist/esm/review/runStatus.js.map +1 -1
  110. package/dist/esm/review/safeRelayr.d.ts +12 -2
  111. package/dist/esm/review/safeRelayr.d.ts.map +1 -1
  112. package/dist/esm/review/safeRelayr.js +226 -156
  113. package/dist/esm/review/safeRelayr.js.map +1 -1
  114. package/dist/esm/review/savedAttempt.d.ts.map +1 -1
  115. package/dist/esm/review/savedAttempt.js +7 -2
  116. package/dist/esm/review/savedAttempt.js.map +1 -1
  117. package/dist/esm/review/send.d.ts +46 -5
  118. package/dist/esm/review/send.d.ts.map +1 -1
  119. package/dist/esm/review/send.js +252 -59
  120. package/dist/esm/review/send.js.map +1 -1
  121. package/dist/esm/review/sequence.d.ts +25 -0
  122. package/dist/esm/review/sequence.d.ts.map +1 -1
  123. package/dist/esm/review/sequence.js +43 -7
  124. package/dist/esm/review/sequence.js.map +1 -1
  125. package/dist/esm/review/sign.d.ts +6 -1
  126. package/dist/esm/review/sign.d.ts.map +1 -1
  127. package/dist/esm/review/sign.js +10 -4
  128. package/dist/esm/review/sign.js.map +1 -1
  129. package/dist/esm/review/tabState.d.ts +4 -0
  130. package/dist/esm/review/tabState.d.ts.map +1 -1
  131. package/dist/esm/review/tabState.js +4 -0
  132. package/dist/esm/review/tabState.js.map +1 -1
  133. package/dist/esm/review/wallet.d.ts +16 -4
  134. package/dist/esm/review/wallet.d.ts.map +1 -1
  135. package/dist/esm/review/wallet.js +62 -17
  136. package/dist/esm/review/wallet.js.map +1 -1
  137. package/dist/esm/review/writeHold.d.ts +92 -14
  138. package/dist/esm/review/writeHold.d.ts.map +1 -1
  139. package/dist/esm/review/writeHold.js +102 -26
  140. package/dist/esm/review/writeHold.js.map +1 -1
  141. package/dist/esm/review/writeRecovery.d.ts +42 -7
  142. package/dist/esm/review/writeRecovery.d.ts.map +1 -1
  143. package/dist/esm/review/writeRecovery.js +180 -22
  144. package/dist/esm/review/writeRecovery.js.map +1 -1
  145. package/dist/esm/testing.d.ts +13 -0
  146. package/dist/esm/testing.d.ts.map +1 -1
  147. package/dist/esm/testing.js +20 -0
  148. package/dist/esm/testing.js.map +1 -1
  149. package/package.json +1 -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;
@@ -342,14 +343,28 @@ function createSafeRelayrController(options) {
342
343
  * `fundingObservedAt` another flow saved. Nothing ever clears them; an
343
344
  * exit keeps them for reconciliation.
344
345
  */
346
+ /** The locks each account's `withLock` holds, while it runs. */
347
+ const storeLocks = new Map();
345
348
  const store = {
346
349
  get scope() {
347
350
  return options.store.scope;
348
351
  },
349
352
  list: (account) => options.store.list(account),
350
- 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
+ }),
351
364
  async save(next) {
352
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()));
353
368
  const at = [saved?.fundingObservedAt, next.fundingObservedAt].filter((value) => typeof value === "number");
354
369
  // Once seen, funding stays, and its clock only moves forward.
355
370
  return options.store.save(saved?.fundingObserved || next.fundingObserved
@@ -1215,179 +1230,234 @@ function createSafeRelayrController(options) {
1215
1230
  });
1216
1231
  }
1217
1232
  async function fund({ account, sessionId, paymentChainId, signal, onStatus, }) {
1218
- return store.withLock(account, async () => {
1219
- checkAccount(account, signal);
1220
- let session = await load(account, sessionId);
1221
- const checked = await inspect(session, signal);
1222
- session = checked.session;
1223
- if (checked.state !== "ready")
1224
- throw new SafeRelayrRecoveryError(session, checked.recovery?.message, checked.recovery);
1225
- if (session.payments.length >= relayr_js_1.MAX_RELAYR_SENT_PAYMENTS)
1226
- throw new SafeRelayrRecoveryError(session, "This quote has reached its saved payment attempt limit. Check the existing bundle.");
1227
- const payment = checked.payments.find((option) => option.chain === paymentChainId);
1228
- if (!payment)
1229
- throw new Error("Choose a current payment option from this saved quote.");
1230
- await assertUnreserved(account, session.executions, session.id);
1231
- if (!reviewedIntents.has(reviewKey(session))) {
1232
- phase("reviewing", session, signal);
1233
- await options.review(session.executions, { resumed: true });
1234
- checkAccount(account, signal);
1235
- rememberReview(session);
1236
- }
1237
- let verified = false;
1238
- let sending = false;
1239
- let paymentPersisted = false;
1240
- const requireOpenPayment = () => (0, relayr_js_1.relayrPaymentDetails)(payment, {
1241
- bundleUuid: session.bundleUuid,
1242
- destinationChainIds: session.executions.map(({ entry }) => entry.chain),
1233
+ // Once this attempt saved "sending", every later save goes through
1234
+ // `keep` (`keepOutcome`): one refused for a lost lock leaves what it
1235
+ // would have saved (the attempt's outcome) to `settle`, applied once the
1236
+ // store's locks are taken back, to the saved record only while it is
1237
+ // still this attempt's. The caller still gets a `LockLostError`.
1238
+ const key = account.toLowerCase();
1239
+ const keep = async (next, settle) => {
1240
+ const locks = storeLocks.get(key);
1241
+ const saved = await (0, locks_js_1.keepOutcome)(locks, () => store.save(next), async () => {
1242
+ // The save path checks the same locks, taken back.
1243
+ const before = storeLocks.get(key);
1244
+ storeLocks.set(key, locks);
1245
+ try {
1246
+ const settled = settle(await load(account, sessionId));
1247
+ if (settled)
1248
+ await store.save(settled);
1249
+ }
1250
+ finally {
1251
+ storeLocks.set(key, before);
1252
+ }
1243
1253
  });
1244
- const beforeSend = async () => {
1245
- phase("payment-checking", session, signal);
1246
- await revalidate(session.executions, account, onStatus, true, signal);
1254
+ if (!saved)
1255
+ throw new locks_js_1.LockLostError(locks[0].name);
1256
+ };
1257
+ return funding();
1258
+ async function funding() {
1259
+ return store.withLock(account, async () => {
1260
+ checkAccount(account, signal);
1261
+ let session = await load(account, sessionId);
1262
+ const checked = await inspect(session, signal);
1263
+ session = checked.session;
1264
+ if (checked.state !== "ready")
1265
+ throw new SafeRelayrRecoveryError(session, checked.recovery?.message, checked.recovery);
1266
+ if (session.payments.length >= relayr_js_1.MAX_RELAYR_SENT_PAYMENTS)
1267
+ throw new SafeRelayrRecoveryError(session, "This quote has reached its saved payment attempt limit. Check the existing bundle.");
1268
+ const payment = checked.payments.find((option) => option.chain === paymentChainId);
1269
+ if (!payment)
1270
+ throw new Error("Choose a current payment option from this saved quote.");
1247
1271
  await assertUnreserved(account, session.executions, session.id);
1248
- await statusRead(() => session.payments.length
1249
- ? (0, relayr_js_1.requireRelayrRetry)(options.clientFor, {
1250
- payments: session.payments,
1251
- from: account,
1252
- bundleUuid: session.bundleUuid,
1253
- }, { fetch: fetchRelayr })
1254
- : (0, relayr_js_1.requireRelayrBundleUnpaid)(session.bundleUuid, {
1255
- fetch: fetchRelayr,
1256
- })).catch(async (error) => {
1257
- if (statusUnknown(error))
1258
- throw unknownStatus(session);
1259
- if (fundingReported(error))
1260
- session = await observed(session);
1261
- throw error;
1272
+ if (!reviewedIntents.has(reviewKey(session))) {
1273
+ phase("reviewing", session, signal);
1274
+ await options.review(session.executions, { resumed: true });
1275
+ checkAccount(account, signal);
1276
+ rememberReview(session);
1277
+ }
1278
+ let verified = false;
1279
+ let sending = false;
1280
+ let paymentPersisted = false;
1281
+ const requireOpenPayment = () => (0, relayr_js_1.relayrPaymentDetails)(payment, {
1282
+ bundleUuid: session.bundleUuid,
1283
+ destinationChainIds: session.executions.map(({ entry }) => entry.chain),
1262
1284
  });
1263
- checkAccount(account, signal);
1264
- requireOpenPayment();
1265
- verified = true;
1266
- };
1267
- const onSending = async () => {
1268
- checkAccount(account, signal);
1269
- if (!verified || sending)
1270
- throw new Error("Recheck the Safe execution before funding it.");
1271
- // The saved record, read again just before the wallet opens: funding
1272
- // another flow reported, or any change to its payment, refuses.
1273
- const stored = await load(account, session.id);
1274
- if (hasObservedFunding(stored) ||
1275
- stored.state !== session.state ||
1276
- stored.paymentStatus !== session.paymentStatus ||
1277
- stored.bundleUuid !== session.bundleUuid ||
1278
- JSON.stringify(stored.payments) !== JSON.stringify(session.payments))
1279
- throw new SafeRelayrRecoveryError(stored, hasObservedFunding(stored)
1280
- ? exports.SAFE_RELAYR_FUNDING_EXIT_LINE
1281
- : "This saved selection changed while it was being paid. Check it again before paying.");
1282
- session = { ...session, paymentStatus: "sending" };
1283
- await store.save(session);
1284
- try {
1285
+ const beforeSend = async () => {
1286
+ phase("payment-checking", session, signal);
1287
+ await revalidate(session.executions, account, onStatus, true, signal);
1288
+ await assertUnreserved(account, session.executions, session.id);
1289
+ await statusRead(() => session.payments.length
1290
+ ? (0, relayr_js_1.requireRelayrRetry)(options.clientFor, {
1291
+ payments: session.payments,
1292
+ from: account,
1293
+ bundleUuid: session.bundleUuid,
1294
+ }, { fetch: fetchRelayr })
1295
+ : (0, relayr_js_1.requireRelayrBundleUnpaid)(session.bundleUuid, {
1296
+ fetch: fetchRelayr,
1297
+ })).catch(async (error) => {
1298
+ if (statusUnknown(error))
1299
+ throw unknownStatus(session);
1300
+ if (fundingReported(error))
1301
+ session = await observed(session);
1302
+ throw error;
1303
+ });
1285
1304
  checkAccount(account, signal);
1286
1305
  requireOpenPayment();
1287
- }
1288
- catch (error) {
1306
+ verified = true;
1307
+ };
1308
+ /**
1309
+ * Nothing was sent after "sending" was saved: the status before this
1310
+ * attempt again. A lost lock leaves it to the fresh-lock reconcile,
1311
+ * and the caller still gets the reason nothing was sent.
1312
+ */
1313
+ const restoreUnsent = async () => {
1289
1314
  session = {
1290
1315
  ...session,
1291
1316
  paymentStatus: checked.session.paymentStatus,
1292
1317
  };
1318
+ const restored = session;
1319
+ await keep(restored, (current) => current.paymentStatus === "sending" &&
1320
+ current.bundleUuid === restored.bundleUuid &&
1321
+ JSON.stringify(current.payments) ===
1322
+ JSON.stringify(restored.payments)
1323
+ ? { ...current, paymentStatus: restored.paymentStatus }
1324
+ : undefined).catch((saveError) => {
1325
+ if (!(saveError instanceof locks_js_1.LockLostError))
1326
+ throw saveError;
1327
+ });
1328
+ };
1329
+ const onSending = async () => {
1330
+ checkAccount(account, signal);
1331
+ if (!verified || sending)
1332
+ throw new Error("Recheck the Safe execution before funding it.");
1333
+ // The saved record, read again just before the wallet opens: funding
1334
+ // another flow reported, or any change to its payment, refuses.
1335
+ const stored = await load(account, session.id);
1336
+ if (hasObservedFunding(stored) ||
1337
+ stored.state !== session.state ||
1338
+ stored.paymentStatus !== session.paymentStatus ||
1339
+ stored.bundleUuid !== session.bundleUuid ||
1340
+ JSON.stringify(stored.payments) !== JSON.stringify(session.payments))
1341
+ throw new SafeRelayrRecoveryError(stored, hasObservedFunding(stored)
1342
+ ? exports.SAFE_RELAYR_FUNDING_EXIT_LINE
1343
+ : "This saved selection changed while it was being paid. Check it again before paying.");
1344
+ session = { ...session, paymentStatus: "sending" };
1293
1345
  await store.save(session);
1294
- throw error;
1295
- }
1296
- sending = true;
1297
- phase("payment-submitting", session, signal);
1298
- };
1299
- const onSent = async (payments) => {
1300
- const snapshot = (0, relayr_js_1.relayrSentPaymentsSnapshot)(payments);
1301
- if (!sending ||
1302
- !snapshot?.length ||
1303
- snapshot.some((item) => item.bundleUuid !== session.bundleUuid))
1304
- throw new Error("The payment does not match the saved Safe bundle.");
1305
- if (!snapshot.some((item) => !checked.session.payments.some((prior) => prior.hash.toLowerCase() === item.hash.toLowerCase())))
1306
- throw new Error("The new payment attempt has no new transaction hash.");
1307
- for (const prior of session.payments) {
1308
- if (!snapshot.some((item) => item.hash.toLowerCase() === prior.hash.toLowerCase() &&
1309
- item.chainId === prior.chainId &&
1310
- item.calldata.toLowerCase() === prior.calldata.toLowerCase() &&
1311
- item.amount === prior.amount &&
1312
- item.deadline === prior.deadline &&
1313
- item.target.toLowerCase() === prior.target.toLowerCase()))
1314
- throw new Error("The saved payment history must retain every known payment.");
1315
- }
1316
- const details = (0, relayr_js_1.relayrPaymentDetails)(payment, {
1317
- bundleUuid: session.bundleUuid,
1318
- destinationChainIds: session.executions.map(({ entry }) => entry.chain),
1319
- nowSeconds: 0,
1320
- });
1321
- for (const item of snapshot.filter((item) => !session.payments.some((prior) => prior.hash === item.hash))) {
1322
- if (item.chainId !== details.chainId ||
1323
- BigInt(item.amount) !== details.amount ||
1324
- BigInt(item.deadline) !== details.deadline ||
1325
- item.calldata.toLowerCase() !== details.calldata.toLowerCase() ||
1326
- item.target.toLowerCase() !== details.target.toLowerCase())
1327
- throw new Error("The funding payment differs from the selected Relayr option.");
1328
- }
1329
- paymentPersisted = false;
1330
- session = {
1331
- ...session,
1332
- payments: structuredClone(payments),
1333
- paymentStatus: "submitted",
1346
+ try {
1347
+ checkAccount(account, signal);
1348
+ requireOpenPayment();
1349
+ }
1350
+ catch (error) {
1351
+ await restoreUnsent();
1352
+ throw error;
1353
+ }
1354
+ sending = true;
1355
+ phase("payment-submitting", session, signal);
1334
1356
  };
1335
- await store.save(session);
1336
- paymentPersisted = true;
1337
- phase("payment-confirming", session, signal);
1338
- };
1339
- try {
1340
- phase("payment-review", session, signal);
1341
- const sent = await options.sendPayment({
1342
- session: structuredClone(session),
1343
- payment,
1344
- beforeSend,
1345
- onSending,
1346
- onSent,
1347
- });
1348
- if (!sent.payments.some((item) => item.hash.toLowerCase() === sent.hash.toLowerCase()))
1349
- throw new Error("The funding result omitted its transaction hash.");
1350
- await onSent(sent.payments);
1351
- }
1352
- catch (error) {
1353
- if (sending &&
1354
- // Classify only this attempt; restoring the previous status below
1355
- // preserves the quote's earlier payments and their retry policy.
1356
- (0, relayr_js_1.relayrPaymentAttemptOutcome)(error, { sending, paid: false }) ===
1357
- "unpaid" &&
1358
- session.payments.length === checked.session.payments.length) {
1357
+ const onSent = async (payments) => {
1358
+ const snapshot = (0, relayr_js_1.relayrSentPaymentsSnapshot)(payments);
1359
+ if (!sending ||
1360
+ !snapshot?.length ||
1361
+ snapshot.some((item) => item.bundleUuid !== session.bundleUuid))
1362
+ throw new Error("The payment does not match the saved Safe bundle.");
1363
+ if (!snapshot.some((item) => !checked.session.payments.some((prior) => prior.hash.toLowerCase() === item.hash.toLowerCase())))
1364
+ throw new Error("The new payment attempt has no new transaction hash.");
1365
+ for (const prior of session.payments) {
1366
+ if (!snapshot.some((item) => item.hash.toLowerCase() === prior.hash.toLowerCase() &&
1367
+ item.chainId === prior.chainId &&
1368
+ item.calldata.toLowerCase() ===
1369
+ prior.calldata.toLowerCase() &&
1370
+ item.amount === prior.amount &&
1371
+ item.deadline === prior.deadline &&
1372
+ item.target.toLowerCase() === prior.target.toLowerCase()))
1373
+ throw new Error("The saved payment history must retain every known payment.");
1374
+ }
1375
+ const details = (0, relayr_js_1.relayrPaymentDetails)(payment, {
1376
+ bundleUuid: session.bundleUuid,
1377
+ destinationChainIds: session.executions.map(({ entry }) => entry.chain),
1378
+ nowSeconds: 0,
1379
+ });
1380
+ for (const item of snapshot.filter((item) => !session.payments.some((prior) => prior.hash === item.hash))) {
1381
+ if (item.chainId !== details.chainId ||
1382
+ BigInt(item.amount) !== details.amount ||
1383
+ BigInt(item.deadline) !== details.deadline ||
1384
+ item.calldata.toLowerCase() !== details.calldata.toLowerCase() ||
1385
+ item.target.toLowerCase() !== details.target.toLowerCase())
1386
+ throw new Error("The funding payment differs from the selected Relayr option.");
1387
+ }
1388
+ paymentPersisted = false;
1359
1389
  session = {
1360
1390
  ...session,
1361
- paymentStatus: checked.session.paymentStatus,
1391
+ payments: structuredClone(payments),
1392
+ paymentStatus: "submitted",
1362
1393
  };
1363
- await store.save(session);
1364
- throw error;
1394
+ const sent = session.payments;
1395
+ await keep(session, (current) => {
1396
+ const missing = sent.filter((item) => !current.payments.some((prior) => prior.hash.toLowerCase() === item.hash.toLowerCase()));
1397
+ return current.bundleUuid === session.bundleUuid &&
1398
+ (missing.length || current.paymentStatus === "sending")
1399
+ ? {
1400
+ ...current,
1401
+ payments: [...current.payments, ...missing],
1402
+ paymentStatus: current.paymentStatus === "sending" ||
1403
+ current.paymentStatus === "unfunded"
1404
+ ? "submitted"
1405
+ : current.paymentStatus,
1406
+ }
1407
+ : undefined;
1408
+ });
1409
+ paymentPersisted = true;
1410
+ phase("payment-confirming", session, signal);
1411
+ };
1412
+ try {
1413
+ phase("payment-review", session, signal);
1414
+ const sent = await options.sendPayment({
1415
+ session: structuredClone(session),
1416
+ payment,
1417
+ beforeSend,
1418
+ onSending,
1419
+ onSent,
1420
+ });
1421
+ if (!sent.payments.some((item) => item.hash.toLowerCase() === sent.hash.toLowerCase()))
1422
+ throw new Error("The funding result omitted its transaction hash.");
1423
+ await onSent(sent.payments);
1365
1424
  }
1366
- if (sending ||
1367
- session.payments.length > checked.session.payments.length) {
1368
- // A saved hash may already have funded or completed the bundle even
1369
- // when the wallet adapter could not prove it. Inspect once, read-only;
1370
- // never turn an uncertain or unsaved attempt into a retry invitation.
1371
- if (paymentPersisted) {
1372
- try {
1373
- const inspected = await inspect(session, signal);
1374
- session = inspected.session;
1375
- if (inspected.state === "complete" ||
1376
- (inspected.state === "pending" &&
1377
- session.paymentStatus === "confirmed"))
1378
- return inspected;
1379
- }
1380
- catch (inspectionError) {
1381
- if (inspectionError instanceof SafeRelayrRecoveryError)
1382
- session = inspectionError.session;
1425
+ catch (error) {
1426
+ if (sending &&
1427
+ // Classify only this attempt; restoring the previous status below
1428
+ // preserves the quote's earlier payments and their retry policy.
1429
+ (0, relayr_js_1.relayrPaymentAttemptOutcome)(error, { sending, paid: false }) ===
1430
+ "unpaid" &&
1431
+ session.payments.length === checked.session.payments.length) {
1432
+ await restoreUnsent();
1433
+ throw error;
1434
+ }
1435
+ if (sending ||
1436
+ session.payments.length > checked.session.payments.length) {
1437
+ // A saved hash may already have funded or completed the bundle even
1438
+ // when the wallet adapter could not prove it. Inspect once, read-only;
1439
+ // never turn an uncertain or unsaved attempt into a retry invitation.
1440
+ if (paymentPersisted) {
1441
+ try {
1442
+ const inspected = await inspect(session, signal);
1443
+ session = inspected.session;
1444
+ if (inspected.state === "complete" ||
1445
+ (inspected.state === "pending" &&
1446
+ session.paymentStatus === "confirmed"))
1447
+ return inspected;
1448
+ }
1449
+ catch (inspectionError) {
1450
+ if (inspectionError instanceof SafeRelayrRecoveryError)
1451
+ session = inspectionError.session;
1452
+ }
1383
1453
  }
1454
+ throw new SafeRelayrRecoveryError(session, error instanceof Error ? error.message : undefined);
1384
1455
  }
1385
- throw new SafeRelayrRecoveryError(session, error instanceof Error ? error.message : undefined);
1456
+ throw error;
1386
1457
  }
1387
- throw error;
1388
- }
1389
- return inspect(session, signal);
1390
- });
1458
+ return inspect(session, signal);
1459
+ });
1460
+ }
1391
1461
  }
1392
1462
  /** Read-only progress polling; each read releases its lock before the wait. */
1393
1463
  async function watch({ account, sessionId, signal, onUpdate, intervalMs = 2_500, timeoutMs = 300_000, }) {