@bananapus/nana-sdk-core 3.5.2 → 5.0.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 (304) hide show
  1. package/bin/wallet-check.mjs +495 -0
  2. package/dist/cjs/actions/suckerPairs.d.ts +5 -3
  3. package/dist/cjs/actions/suckerPairs.d.ts.map +1 -1
  4. package/dist/cjs/actions/suckerPairs.js +6 -26
  5. package/dist/cjs/actions/suckerPairs.js.map +1 -1
  6. package/dist/cjs/pause.d.ts +2 -0
  7. package/dist/cjs/pause.d.ts.map +1 -1
  8. package/dist/cjs/pause.js +17 -0
  9. package/dist/cjs/pause.js.map +1 -1
  10. package/dist/cjs/review/confirm.d.ts +56 -0
  11. package/dist/cjs/review/confirm.d.ts.map +1 -0
  12. package/dist/cjs/review/confirm.js +99 -0
  13. package/dist/cjs/review/confirm.js.map +1 -0
  14. package/dist/cjs/review/contractWrite.d.ts +5 -3
  15. package/dist/cjs/review/contractWrite.d.ts.map +1 -1
  16. package/dist/cjs/review/contractWrite.js +10 -8
  17. package/dist/cjs/review/contractWrite.js.map +1 -1
  18. package/dist/cjs/review/decode.d.ts +19 -1
  19. package/dist/cjs/review/decode.d.ts.map +1 -1
  20. package/dist/cjs/review/decode.js +30 -11
  21. package/dist/cjs/review/decode.js.map +1 -1
  22. package/dist/cjs/review/decodeCall.d.ts +21 -0
  23. package/dist/cjs/review/decodeCall.d.ts.map +1 -0
  24. package/dist/cjs/review/decodeCall.js +183 -0
  25. package/dist/cjs/review/decodeCall.js.map +1 -0
  26. package/dist/cjs/review/heldWrite.d.ts +4 -0
  27. package/dist/cjs/review/heldWrite.d.ts.map +1 -1
  28. package/dist/cjs/review/heldWrite.js +35 -31
  29. package/dist/cjs/review/heldWrite.js.map +1 -1
  30. package/dist/cjs/review/index.d.ts +12 -1
  31. package/dist/cjs/review/index.d.ts.map +1 -1
  32. package/dist/cjs/review/index.js +29 -1
  33. package/dist/cjs/review/index.js.map +1 -1
  34. package/dist/cjs/review/intents.d.ts +10 -0
  35. package/dist/cjs/review/intents.d.ts.map +1 -1
  36. package/dist/cjs/review/intents.js +13 -0
  37. package/dist/cjs/review/intents.js.map +1 -1
  38. package/dist/cjs/review/lineError.d.ts +72 -0
  39. package/dist/cjs/review/lineError.d.ts.map +1 -0
  40. package/dist/cjs/review/lineError.js +76 -0
  41. package/dist/cjs/review/lineError.js.map +1 -0
  42. package/dist/cjs/review/messages.d.ts +15 -3
  43. package/dist/cjs/review/messages.d.ts.map +1 -1
  44. package/dist/cjs/review/messages.js +16 -4
  45. package/dist/cjs/review/messages.js.map +1 -1
  46. package/dist/cjs/review/model.d.ts +149 -0
  47. package/dist/cjs/review/model.d.ts.map +1 -0
  48. package/dist/cjs/review/model.js +750 -0
  49. package/dist/cjs/review/model.js.map +1 -0
  50. package/dist/cjs/review/plan.d.ts +61 -0
  51. package/dist/cjs/review/plan.d.ts.map +1 -0
  52. package/dist/cjs/review/plan.js +143 -0
  53. package/dist/cjs/review/plan.js.map +1 -0
  54. package/dist/cjs/review/react/index.d.ts +1 -0
  55. package/dist/cjs/review/react/index.d.ts.map +1 -1
  56. package/dist/cjs/review/react/index.js +1 -0
  57. package/dist/cjs/review/react/index.js.map +1 -1
  58. package/dist/cjs/review/react/useHeldSend.js +3 -4
  59. package/dist/cjs/review/react/useHeldSend.js.map +1 -1
  60. package/dist/cjs/review/react/useHeldWrite.d.ts.map +1 -1
  61. package/dist/cjs/review/react/useHeldWrite.js +2 -1
  62. package/dist/cjs/review/react/useHeldWrite.js.map +1 -1
  63. package/dist/cjs/review/react/useReviewedRun.d.ts +122 -0
  64. package/dist/cjs/review/react/useReviewedRun.d.ts.map +1 -0
  65. package/dist/cjs/review/react/useReviewedRun.js +443 -0
  66. package/dist/cjs/review/react/useReviewedRun.js.map +1 -0
  67. package/dist/cjs/review/receipt.d.ts +37 -1
  68. package/dist/cjs/review/receipt.d.ts.map +1 -1
  69. package/dist/cjs/review/receipt.js +76 -3
  70. package/dist/cjs/review/receipt.js.map +1 -1
  71. package/dist/cjs/review/relayr.d.ts +13 -4
  72. package/dist/cjs/review/relayr.d.ts.map +1 -1
  73. package/dist/cjs/review/relayr.js +52 -8
  74. package/dist/cjs/review/relayr.js.map +1 -1
  75. package/dist/cjs/review/relayrSend.d.ts +106 -0
  76. package/dist/cjs/review/relayrSend.d.ts.map +1 -0
  77. package/dist/cjs/review/relayrSend.js +795 -0
  78. package/dist/cjs/review/relayrSend.js.map +1 -0
  79. package/dist/cjs/review/reviewQueue.d.ts +10 -9
  80. package/dist/cjs/review/reviewQueue.d.ts.map +1 -1
  81. package/dist/cjs/review/reviewQueue.js +19 -29
  82. package/dist/cjs/review/reviewQueue.js.map +1 -1
  83. package/dist/cjs/review/reviewRequests.d.ts +86 -0
  84. package/dist/cjs/review/reviewRequests.d.ts.map +1 -0
  85. package/dist/cjs/review/reviewRequests.js +266 -0
  86. package/dist/cjs/review/reviewRequests.js.map +1 -0
  87. package/dist/cjs/review/safeRelayr.d.ts +3 -1
  88. package/dist/cjs/review/safeRelayr.d.ts.map +1 -1
  89. package/dist/cjs/review/safeRelayr.js +9 -9
  90. package/dist/cjs/review/safeRelayr.js.map +1 -1
  91. package/dist/cjs/review/safeSend.d.ts +69 -0
  92. package/dist/cjs/review/safeSend.d.ts.map +1 -0
  93. package/dist/cjs/review/safeSend.js +306 -0
  94. package/dist/cjs/review/safeSend.js.map +1 -0
  95. package/dist/cjs/review/send.d.ts +272 -0
  96. package/dist/cjs/review/send.d.ts.map +1 -0
  97. package/dist/cjs/review/send.js +1163 -0
  98. package/dist/cjs/review/send.js.map +1 -0
  99. package/dist/cjs/review/sequence.d.ts +77 -0
  100. package/dist/cjs/review/sequence.d.ts.map +1 -0
  101. package/dist/cjs/review/sequence.js +232 -0
  102. package/dist/cjs/review/sequence.js.map +1 -0
  103. package/dist/cjs/review/sign.d.ts +111 -0
  104. package/dist/cjs/review/sign.d.ts.map +1 -0
  105. package/dist/cjs/review/sign.js +378 -0
  106. package/dist/cjs/review/sign.js.map +1 -0
  107. package/dist/cjs/review/simulation.d.ts +41 -2
  108. package/dist/cjs/review/simulation.d.ts.map +1 -1
  109. package/dist/cjs/review/simulation.js +99 -29
  110. package/dist/cjs/review/simulation.js.map +1 -1
  111. package/dist/cjs/review/transaction-message.d.ts +13 -0
  112. package/dist/cjs/review/transaction-message.d.ts.map +1 -1
  113. package/dist/cjs/review/transaction-message.js +25 -0
  114. package/dist/cjs/review/transaction-message.js.map +1 -1
  115. package/dist/cjs/review/transactionReview.d.ts +27 -19
  116. package/dist/cjs/review/transactionReview.d.ts.map +1 -1
  117. package/dist/cjs/review/transactionReview.js +22 -63
  118. package/dist/cjs/review/transactionReview.js.map +1 -1
  119. package/dist/cjs/review/wallet.d.ts +98 -0
  120. package/dist/cjs/review/wallet.d.ts.map +1 -0
  121. package/dist/cjs/review/wallet.js +234 -0
  122. package/dist/cjs/review/wallet.js.map +1 -0
  123. package/dist/cjs/review/writeHold.d.ts +138 -13
  124. package/dist/cjs/review/writeHold.d.ts.map +1 -1
  125. package/dist/cjs/review/writeHold.js +98 -18
  126. package/dist/cjs/review/writeHold.js.map +1 -1
  127. package/dist/cjs/review/writeRecovery.d.ts +17 -1
  128. package/dist/cjs/review/writeRecovery.d.ts.map +1 -1
  129. package/dist/cjs/review/writeRecovery.js +146 -45
  130. package/dist/cjs/review/writeRecovery.js.map +1 -1
  131. package/dist/cjs/safeService.d.ts +27 -4
  132. package/dist/cjs/safeService.d.ts.map +1 -1
  133. package/dist/cjs/safeService.js +120 -3
  134. package/dist/cjs/safeService.js.map +1 -1
  135. package/dist/cjs/testing.d.ts +24 -0
  136. package/dist/cjs/testing.d.ts.map +1 -1
  137. package/dist/cjs/testing.js +26 -1
  138. package/dist/cjs/testing.js.map +1 -1
  139. package/dist/cjs/v6/defifa.d.ts.map +1 -1
  140. package/dist/cjs/v6/defifa.js +24 -23
  141. package/dist/cjs/v6/defifa.js.map +1 -1
  142. package/dist/cjs/v6/executionErrors.d.ts.map +1 -1
  143. package/dist/cjs/v6/executionErrors.js +3 -2
  144. package/dist/cjs/v6/executionErrors.js.map +1 -1
  145. package/dist/cjs/v6/pay.d.ts +25 -8
  146. package/dist/cjs/v6/pay.d.ts.map +1 -1
  147. package/dist/cjs/v6/pay.js +117 -23
  148. package/dist/cjs/v6/pay.js.map +1 -1
  149. package/dist/cjs/v6/suckers.d.ts +30 -0
  150. package/dist/cjs/v6/suckers.d.ts.map +1 -1
  151. package/dist/cjs/v6/suckers.js +69 -5
  152. package/dist/cjs/v6/suckers.js.map +1 -1
  153. package/dist/esm/actions/suckerPairs.d.ts +5 -3
  154. package/dist/esm/actions/suckerPairs.d.ts.map +1 -1
  155. package/dist/esm/actions/suckerPairs.js +7 -27
  156. package/dist/esm/actions/suckerPairs.js.map +1 -1
  157. package/dist/esm/pause.d.ts +2 -0
  158. package/dist/esm/pause.d.ts.map +1 -1
  159. package/dist/esm/pause.js +16 -0
  160. package/dist/esm/pause.js.map +1 -1
  161. package/dist/esm/review/confirm.d.ts +56 -0
  162. package/dist/esm/review/confirm.d.ts.map +1 -0
  163. package/dist/esm/review/confirm.js +93 -0
  164. package/dist/esm/review/confirm.js.map +1 -0
  165. package/dist/esm/review/contractWrite.d.ts +5 -3
  166. package/dist/esm/review/contractWrite.d.ts.map +1 -1
  167. package/dist/esm/review/contractWrite.js +10 -8
  168. package/dist/esm/review/contractWrite.js.map +1 -1
  169. package/dist/esm/review/decode.d.ts +19 -1
  170. package/dist/esm/review/decode.d.ts.map +1 -1
  171. package/dist/esm/review/decode.js +29 -14
  172. package/dist/esm/review/decode.js.map +1 -1
  173. package/dist/esm/review/decodeCall.d.ts +21 -0
  174. package/dist/esm/review/decodeCall.d.ts.map +1 -0
  175. package/dist/esm/review/decodeCall.js +180 -0
  176. package/dist/esm/review/decodeCall.js.map +1 -0
  177. package/dist/esm/review/heldWrite.d.ts +4 -0
  178. package/dist/esm/review/heldWrite.d.ts.map +1 -1
  179. package/dist/esm/review/heldWrite.js +34 -30
  180. package/dist/esm/review/heldWrite.js.map +1 -1
  181. package/dist/esm/review/index.d.ts +12 -1
  182. package/dist/esm/review/index.d.ts.map +1 -1
  183. package/dist/esm/review/index.js +12 -1
  184. package/dist/esm/review/index.js.map +1 -1
  185. package/dist/esm/review/intents.d.ts +10 -0
  186. package/dist/esm/review/intents.d.ts.map +1 -1
  187. package/dist/esm/review/intents.js +12 -0
  188. package/dist/esm/review/intents.js.map +1 -1
  189. package/dist/esm/review/lineError.d.ts +72 -0
  190. package/dist/esm/review/lineError.d.ts.map +1 -0
  191. package/dist/esm/review/lineError.js +68 -0
  192. package/dist/esm/review/lineError.js.map +1 -0
  193. package/dist/esm/review/messages.d.ts +15 -3
  194. package/dist/esm/review/messages.d.ts.map +1 -1
  195. package/dist/esm/review/messages.js +15 -3
  196. package/dist/esm/review/messages.js.map +1 -1
  197. package/dist/esm/review/model.d.ts +149 -0
  198. package/dist/esm/review/model.d.ts.map +1 -0
  199. package/dist/esm/review/model.js +746 -0
  200. package/dist/esm/review/model.js.map +1 -0
  201. package/dist/esm/review/plan.d.ts +61 -0
  202. package/dist/esm/review/plan.d.ts.map +1 -0
  203. package/dist/esm/review/plan.js +136 -0
  204. package/dist/esm/review/plan.js.map +1 -0
  205. package/dist/esm/review/react/index.d.ts +1 -0
  206. package/dist/esm/review/react/index.d.ts.map +1 -1
  207. package/dist/esm/review/react/index.js +1 -0
  208. package/dist/esm/review/react/index.js.map +1 -1
  209. package/dist/esm/review/react/useHeldSend.js +3 -4
  210. package/dist/esm/review/react/useHeldSend.js.map +1 -1
  211. package/dist/esm/review/react/useHeldWrite.d.ts.map +1 -1
  212. package/dist/esm/review/react/useHeldWrite.js +2 -1
  213. package/dist/esm/review/react/useHeldWrite.js.map +1 -1
  214. package/dist/esm/review/react/useReviewedRun.d.ts +122 -0
  215. package/dist/esm/review/react/useReviewedRun.d.ts.map +1 -0
  216. package/dist/esm/review/react/useReviewedRun.js +439 -0
  217. package/dist/esm/review/react/useReviewedRun.js.map +1 -0
  218. package/dist/esm/review/receipt.d.ts +37 -1
  219. package/dist/esm/review/receipt.d.ts.map +1 -1
  220. package/dist/esm/review/receipt.js +72 -2
  221. package/dist/esm/review/receipt.js.map +1 -1
  222. package/dist/esm/review/relayr.d.ts +13 -4
  223. package/dist/esm/review/relayr.d.ts.map +1 -1
  224. package/dist/esm/review/relayr.js +51 -8
  225. package/dist/esm/review/relayr.js.map +1 -1
  226. package/dist/esm/review/relayrSend.d.ts +106 -0
  227. package/dist/esm/review/relayrSend.d.ts.map +1 -0
  228. package/dist/esm/review/relayrSend.js +787 -0
  229. package/dist/esm/review/relayrSend.js.map +1 -0
  230. package/dist/esm/review/reviewQueue.d.ts +10 -9
  231. package/dist/esm/review/reviewQueue.d.ts.map +1 -1
  232. package/dist/esm/review/reviewQueue.js +17 -26
  233. package/dist/esm/review/reviewQueue.js.map +1 -1
  234. package/dist/esm/review/reviewRequests.d.ts +86 -0
  235. package/dist/esm/review/reviewRequests.d.ts.map +1 -0
  236. package/dist/esm/review/reviewRequests.js +263 -0
  237. package/dist/esm/review/reviewRequests.js.map +1 -0
  238. package/dist/esm/review/safeRelayr.d.ts +3 -1
  239. package/dist/esm/review/safeRelayr.d.ts.map +1 -1
  240. package/dist/esm/review/safeRelayr.js +9 -9
  241. package/dist/esm/review/safeRelayr.js.map +1 -1
  242. package/dist/esm/review/safeSend.d.ts +69 -0
  243. package/dist/esm/review/safeSend.d.ts.map +1 -0
  244. package/dist/esm/review/safeSend.js +301 -0
  245. package/dist/esm/review/safeSend.js.map +1 -0
  246. package/dist/esm/review/send.d.ts +272 -0
  247. package/dist/esm/review/send.d.ts.map +1 -0
  248. package/dist/esm/review/send.js +1156 -0
  249. package/dist/esm/review/send.js.map +1 -0
  250. package/dist/esm/review/sequence.d.ts +77 -0
  251. package/dist/esm/review/sequence.d.ts.map +1 -0
  252. package/dist/esm/review/sequence.js +223 -0
  253. package/dist/esm/review/sequence.js.map +1 -0
  254. package/dist/esm/review/sign.d.ts +111 -0
  255. package/dist/esm/review/sign.d.ts.map +1 -0
  256. package/dist/esm/review/sign.js +372 -0
  257. package/dist/esm/review/sign.js.map +1 -0
  258. package/dist/esm/review/simulation.d.ts +41 -2
  259. package/dist/esm/review/simulation.d.ts.map +1 -1
  260. package/dist/esm/review/simulation.js +95 -29
  261. package/dist/esm/review/simulation.js.map +1 -1
  262. package/dist/esm/review/transaction-message.d.ts +13 -0
  263. package/dist/esm/review/transaction-message.d.ts.map +1 -1
  264. package/dist/esm/review/transaction-message.js +23 -0
  265. package/dist/esm/review/transaction-message.js.map +1 -1
  266. package/dist/esm/review/transactionReview.d.ts +27 -19
  267. package/dist/esm/review/transactionReview.d.ts.map +1 -1
  268. package/dist/esm/review/transactionReview.js +19 -56
  269. package/dist/esm/review/transactionReview.js.map +1 -1
  270. package/dist/esm/review/wallet.d.ts +98 -0
  271. package/dist/esm/review/wallet.d.ts.map +1 -0
  272. package/dist/esm/review/wallet.js +227 -0
  273. package/dist/esm/review/wallet.js.map +1 -0
  274. package/dist/esm/review/writeHold.d.ts +138 -13
  275. package/dist/esm/review/writeHold.d.ts.map +1 -1
  276. package/dist/esm/review/writeHold.js +96 -19
  277. package/dist/esm/review/writeHold.js.map +1 -1
  278. package/dist/esm/review/writeRecovery.d.ts +17 -1
  279. package/dist/esm/review/writeRecovery.d.ts.map +1 -1
  280. package/dist/esm/review/writeRecovery.js +149 -48
  281. package/dist/esm/review/writeRecovery.js.map +1 -1
  282. package/dist/esm/safeService.d.ts +27 -4
  283. package/dist/esm/safeService.d.ts.map +1 -1
  284. package/dist/esm/safeService.js +119 -4
  285. package/dist/esm/safeService.js.map +1 -1
  286. package/dist/esm/testing.d.ts +24 -0
  287. package/dist/esm/testing.d.ts.map +1 -1
  288. package/dist/esm/testing.js +23 -1
  289. package/dist/esm/testing.js.map +1 -1
  290. package/dist/esm/v6/defifa.d.ts.map +1 -1
  291. package/dist/esm/v6/defifa.js +24 -23
  292. package/dist/esm/v6/defifa.js.map +1 -1
  293. package/dist/esm/v6/executionErrors.d.ts.map +1 -1
  294. package/dist/esm/v6/executionErrors.js +3 -2
  295. package/dist/esm/v6/executionErrors.js.map +1 -1
  296. package/dist/esm/v6/pay.d.ts +25 -8
  297. package/dist/esm/v6/pay.d.ts.map +1 -1
  298. package/dist/esm/v6/pay.js +115 -22
  299. package/dist/esm/v6/pay.js.map +1 -1
  300. package/dist/esm/v6/suckers.d.ts +30 -0
  301. package/dist/esm/v6/suckers.d.ts.map +1 -1
  302. package/dist/esm/v6/suckers.js +67 -5
  303. package/dist/esm/v6/suckers.js.map +1 -1
  304. package/package.json +11 -2
@@ -0,0 +1,787 @@
1
+ import { concat, decodeFunctionData, decodeFunctionResult, encodeFunctionData, } from "viem";
2
+ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
3
+ import { erc2771ForwarderAbi, jbV6ContractAddress, } from "../generated/juicebox.js";
4
+ import { multiSendCallsOf } from "../safe.js";
5
+ import { findPendingSafeAppProposal, hasSafeService, SAFE_EXEC_ABI, } from "../safeService.js";
6
+ import { promptReview } from "./confirm.js";
7
+ import { SubmittedContractWriteError, submitReviewedContractWrite, } from "./contractWrite.js";
8
+ import { decodeReviewCall } from "./decodeCall.js";
9
+ import { buildReviewModel } from "./model.js";
10
+ import { spendPlan, spendPlannedRelayrRound, } from "./plan.js";
11
+ import { heldReceipt, waitForTrackedReceipt } from "./receipt.js";
12
+ import { bindRelayrQuote, FORWARD_REQUEST_TYPES, MAX_RELAYR_SENT_PAYMENTS, quoteExpired, readRelayrBundle, RELAYR_API, RELAYR_PAYMENT_ADDRESS, RELAYR_PAYMENT_GAS, RELAYR_PAYMENT_SELECTOR, relayrBundleRequest, relayrForwarder, relayrForwarderDeadline, relayrForwardRequest, relayrPaymentChains, relayrPaymentDetails, relayrPaymentOptions, RelayrPaymentNotSentError, RelayrProofError, RelayrProofUnavailableError, relayrWalletPaymentError, relayrStateIsFailed, relayrStateIsSuccess, relayrSupportsChains, requireRelayrBundleUnpaid, requireRelayrPaymentRuntime, requireRelayrRetry, sentRelayrPayment, simulateRelayrPayment, TRUSTED_FORWARDER_ABI, verifyRelayrPayment, } from "./relayr.js";
13
+ import { ReviewedCallRevertedError } from "./lineError.js";
14
+ import { prepareSignature, signPrepared, } from "./sign.js";
15
+ import { simulateStateChangingTransaction } from "./simulation.js";
16
+ import { unsupportedWalletRequest, walletReady, } from "./wallet.js";
17
+ import { openStepHold, WriteHeldError } from "./writeHold.js";
18
+ import { browserLocks } from "./writeRecovery.js";
19
+ import { pause, timeout } from "../pause.js";
20
+ import { FOLLOW_MS, RECEIPT_RETRY_MS } from "./heldWrite.js";
21
+ /**
22
+ * The margin over an estimated Relayr fee that the review shows as its cap,
23
+ * in basis points (11,000 = the estimate plus 10%). Identical requests quoted
24
+ * minutes apart on every Relayr chain differed by at most 1.4%, so a signed
25
+ * quote above the cap, which asks again, should be rare.
26
+ */
27
+ export const RELAYR_FEE_MARGIN_BPS = 11000n;
28
+ /**
29
+ * Relayr's fee over a request's gas limit at the chain's gas price, in basis
30
+ * points, for bounding a fee when no stand-in quote can be had: the highest
31
+ * ratio measured on a Relayr chain (7.95, Ethereum at 100,000 gas), rounded
32
+ * up. Most of a fee is fixed per chain, so the ratio falls as gas rises.
33
+ */
34
+ export const RELAYR_FEE_MARKUP_BPS = 80000n;
35
+ /** The gas a forwarder's `execute` adds around the request's own. */
36
+ const FORWARDER_OVERHEAD_GAS = 100000n;
37
+ const QUOTE_TIMEOUT_MS = 45_000;
38
+ /** How long the review waits on a stand-in quote before it bounds the fee locally. */
39
+ const STAND_IN_TIMEOUT_MS = 10_000;
40
+ const POLL_INTERVAL_MS = 2_500;
41
+ const POLL_TIMEOUT_MS = 5 * 60_000;
42
+ const BALANCE_OF_ABI = [
43
+ {
44
+ type: "function",
45
+ name: "balanceOf",
46
+ stateMutability: "view",
47
+ inputs: [{ name: "owner", type: "address" }],
48
+ outputs: [{ type: "uint256" }],
49
+ },
50
+ ];
51
+ const RELAYR_PAYMENT_ABI = [
52
+ {
53
+ type: "function",
54
+ name: "prepayment",
55
+ stateMutability: "payable",
56
+ inputs: [
57
+ { name: "payment_uuid", type: "bytes16" },
58
+ { name: "deadline", type: "uint40" },
59
+ ],
60
+ outputs: [],
61
+ },
62
+ ];
63
+ const ACCOUNT_CHANGED = "The connected account changed. Review again.";
64
+ const ORDINARY_WALLET = "Pay for relayed transactions from an ordinary wallet. Nothing was sent.";
65
+ /** The quote's payment deadline passed before the wallet opened. */
66
+ class QuoteExpiredError extends Error {
67
+ constructor() {
68
+ super("This Relayr quote expired. Review the action again for a new quote.");
69
+ }
70
+ }
71
+ function forwarderOn(chainId) {
72
+ const forwarder = relayrForwarder(chainId);
73
+ if (!forwarder) {
74
+ throw new Error(`There is no Juicebox forwarder on chain ${chainId}.`);
75
+ }
76
+ return forwarder;
77
+ }
78
+ /** The connected wallet, as long as it is an ordinary one on `account` (or, with `safe`, a Safe app). */
79
+ async function requireOrdinaryWallet(adapter, account, safe = false) {
80
+ await walletReady(adapter);
81
+ if (adapter.kind !== "wallet")
82
+ throw unsupportedWalletRequest();
83
+ const connected = adapter.account();
84
+ if (!connected)
85
+ throw new Error("Connect a wallet first.");
86
+ if (connected.toLowerCase() !== account.toLowerCase()) {
87
+ throw new Error(ACCOUNT_CHANGED);
88
+ }
89
+ // Signed requests and their proof need an ordinary wallet's own transaction.
90
+ if (adapter.isSafe() && !safe)
91
+ throw new Error(ORDINARY_WALLET);
92
+ }
93
+ /** Whether `target` on `chainId` trusts the chain's canonical forwarder, so the real sender is kept. */
94
+ async function trustsForwarder(client, chainId, target) {
95
+ const forwarder = relayrForwarder(chainId);
96
+ if (!forwarder)
97
+ return false;
98
+ try {
99
+ const data = await simulateStateChangingTransaction(client, {
100
+ from: forwarder,
101
+ to: target,
102
+ data: encodeFunctionData({
103
+ abi: TRUSTED_FORWARDER_ABI,
104
+ functionName: "isTrustedForwarder",
105
+ args: [forwarder],
106
+ }),
107
+ gas: 100000n,
108
+ });
109
+ return decodeFunctionResult({
110
+ abi: TRUSTED_FORWARDER_ABI,
111
+ functionName: "isTrustedForwarder",
112
+ data,
113
+ });
114
+ }
115
+ catch {
116
+ return false;
117
+ }
118
+ }
119
+ /**
120
+ * The forward request each call is signed as: from `account`, with the
121
+ * forwarder nonce read now and the default deadline. Refuses more than one
122
+ * call per chain, chains outside one Relayr network family, a gas limit that
123
+ * is not positive, and a destination that does not trust the forwarder.
124
+ */
125
+ async function forwardRequests(calls, account, clientFor) {
126
+ const read = [...calls].map(({ chainId, target, data, value, gas }) => ({
127
+ chainId,
128
+ target,
129
+ data,
130
+ value: value ?? 0n,
131
+ gas,
132
+ }));
133
+ if (!read.length)
134
+ throw new Error("Choose at least one chain.");
135
+ const chainIds = read.map((call) => call.chainId);
136
+ if (!relayrSupportsChains(chainIds)) {
137
+ throw new Error("Relayr runs one call per supported destination, all mainnets or all testnets.");
138
+ }
139
+ if (read.some((call) => typeof call.gas !== "bigint" || call.gas <= 0n)) {
140
+ throw new Error("Each relayed call needs a positive gas limit.");
141
+ }
142
+ const deadline = relayrForwarderDeadline();
143
+ return Promise.all(read.map(async (call) => {
144
+ const client = clientFor(call.chainId);
145
+ if (!(await trustsForwarder(client, call.chainId, call.target))) {
146
+ throw new Error(`The target on chain ${call.chainId} does not trust the canonical forwarder. Send this action directly.`);
147
+ }
148
+ const nonce = await client.readContract({
149
+ address: forwarderOn(call.chainId),
150
+ abi: erc2771ForwarderAbi,
151
+ functionName: "nonces",
152
+ args: [account],
153
+ });
154
+ return {
155
+ chainId: call.chainId,
156
+ request: {
157
+ from: account,
158
+ to: call.target,
159
+ value: call.value,
160
+ gas: call.gas,
161
+ nonce,
162
+ deadline,
163
+ data: call.data,
164
+ },
165
+ };
166
+ }));
167
+ }
168
+ /** The bundle entry that runs `request` with `signature` through the chain's forwarder. */
169
+ function entryOf(chainId, request, signature) {
170
+ const { from, to, value, gas, deadline, data } = request;
171
+ return {
172
+ chain: chainId,
173
+ target: forwarderOn(chainId),
174
+ data: encodeFunctionData({
175
+ abi: erc2771ForwarderAbi,
176
+ functionName: "execute",
177
+ args: [{ from, to, value, gas, deadline, data, signature }],
178
+ }),
179
+ value: value.toString(),
180
+ };
181
+ }
182
+ /** Post `entries` to Relayr and bind its quote to them (see {@link bindRelayrQuote}). */
183
+ async function postBundle(entries) {
184
+ const request = relayrBundleRequest(entries);
185
+ const response = await globalThis.fetch(`${RELAYR_API}/v1/bundle/prepaid`, {
186
+ method: "POST",
187
+ headers: { "Content-Type": "application/json" },
188
+ body: JSON.stringify(request),
189
+ signal: AbortSignal.timeout(QUOTE_TIMEOUT_MS),
190
+ });
191
+ return bindRelayrQuote(response, request);
192
+ }
193
+ /** Refuses while a payment held under `intent` may still land, or, for `paidOnce`, once one succeeded. */
194
+ function requireIntentClear(deps, account, intent) {
195
+ for (const chainId of intent.funding) {
196
+ const state = openStepHold(deps.hold, account, false, {
197
+ chainId,
198
+ to: RELAYR_PAYMENT_ADDRESS,
199
+ data: "0x",
200
+ intent: intent.key,
201
+ }).peek();
202
+ if (state.status !== "clear")
203
+ throw new WriteHeldError(state);
204
+ if (intent.paidOnce && state.settled?.result === "success") {
205
+ throw new Error("This Relayr payment already succeeded. Do not pay again.");
206
+ }
207
+ }
208
+ }
209
+ /** The quote's authenticated payment on `chainId`. */
210
+ function paymentOn(quote, chainId) {
211
+ const destinationChainIds = quote.expectedTransactions.map((binding) => binding.chain);
212
+ const option = relayrPaymentOptions(quote, destinationChainIds).find((payment) => payment.chain === chainId);
213
+ if (!option &&
214
+ relayrPaymentOptions(quote, destinationChainIds, 0).some((payment) => payment.chain === chainId)) {
215
+ // Offered, but already too close to its deadline to pay.
216
+ throw new QuoteExpiredError();
217
+ }
218
+ if (!option) {
219
+ throw new Error(`Relayr returned no payment option on chain ${chainId}. Nothing was paid.`);
220
+ }
221
+ return relayrPaymentDetails(option, {
222
+ bundleUuid: quote.bundle_uuid,
223
+ destinationChainIds,
224
+ expectedTransactions: quote.expectedTransactions,
225
+ });
226
+ }
227
+ /** The calls a quote's bundle runs, as a plan holds them; a raw value that can't be read matches none. */
228
+ function quotedCalls(quote) {
229
+ return quote.expectedTransactions.map(({ chain, entry }) => {
230
+ const request = relayrForwardRequest(entry);
231
+ if (request) {
232
+ const { from, to, data, value, gas } = request;
233
+ return { chainId: chain, from, to, data, value, gas };
234
+ }
235
+ return {
236
+ chainId: chain,
237
+ from: "relayr",
238
+ to: entry.target,
239
+ data: entry.data,
240
+ value: /^\d+$/u.test(entry.value) ? BigInt(entry.value) : -1n,
241
+ };
242
+ });
243
+ }
244
+ function withMargin(fee) {
245
+ return (fee * RELAYR_FEE_MARGIN_BPS + 9999n) / 10000n;
246
+ }
247
+ /**
248
+ * Relayr's fee for `calls` signed by a key made for this quote alone: the
249
+ * same chains, gas limits and values, so Relayr prices the same work. The key
250
+ * lives only in memory for this call and leaves it only inside the requests'
251
+ * signatures.
252
+ */
253
+ async function standInFee(calls, paymentChainId, deadline) {
254
+ const signer = privateKeyToAccount(generatePrivateKey());
255
+ const entries = await Promise.all(calls.map(async ({ chainId, target, data, value, gas, raw }) => {
256
+ // Anyone may run a raw call: it is its own stand-in.
257
+ if (raw)
258
+ return { chain: chainId, target, data, value: value.toString() };
259
+ const request = {
260
+ from: signer.address,
261
+ to: target,
262
+ value,
263
+ gas,
264
+ deadline,
265
+ data,
266
+ };
267
+ const signature = await signer.signTypedData({
268
+ domain: {
269
+ name: "Juicebox",
270
+ version: "1",
271
+ chainId,
272
+ verifyingContract: forwarderOn(chainId),
273
+ },
274
+ types: FORWARD_REQUEST_TYPES,
275
+ primaryType: "ForwardRequest",
276
+ // A key made now has used no forwarder nonce.
277
+ message: { ...request, nonce: 0n },
278
+ });
279
+ return entryOf(chainId, request, signature);
280
+ }));
281
+ return paymentOn(await postBundle(entries), paymentChainId).amount;
282
+ }
283
+ /**
284
+ * The fee cap the review shows: Relayr's quote for stand-ins of `calls`, plus
285
+ * {@link RELAYR_FEE_MARGIN_BPS}. A call that runs only for the real sender
286
+ * (an owner's action) can't be stood in, so a call with the same chain, gas
287
+ * and calldata length that anyone can run (JBProjects' `balanceOf`) is quoted
288
+ * instead, with the calls' values added. Without either quote in time, the
289
+ * fee is bounded locally: each call's gas estimate through the forwarder as
290
+ * `account`, at the chain's gas price, times {@link RELAYR_FEE_MARKUP_BPS}.
291
+ */
292
+ export async function relayrFeeCap(calls, account, paymentChainId, clientFor) {
293
+ const read = calls.map((call) => ({ ...call, value: call.value ?? 0n }));
294
+ const deadline = relayrForwarderDeadline();
295
+ const values = read.reduce((sum, call) => sum + call.value, 0n);
296
+ const quoted = await timeout(standInFee(read, paymentChainId, deadline).catch(async () => {
297
+ const filler = privateKeyToAccount(generatePrivateKey()).address;
298
+ return ((await standInFee(read.map((call) => {
299
+ const probe = encodeFunctionData({
300
+ abi: BALANCE_OF_ABI,
301
+ functionName: "balanceOf",
302
+ args: [filler],
303
+ });
304
+ const bytes = Math.max((call.data.length - probe.length) / 2, 0);
305
+ return {
306
+ ...call,
307
+ raw: false,
308
+ target: jbV6ContractAddress.JBProjects[call.chainId],
309
+ data: `${probe}${"00".repeat(bytes)}`,
310
+ value: 0n,
311
+ };
312
+ }), paymentChainId, deadline)) + values);
313
+ }), STAND_IN_TIMEOUT_MS).catch(() => null);
314
+ if (quoted !== null)
315
+ return withMargin(quoted);
316
+ let bound = values;
317
+ for (const call of read) {
318
+ const client = clientFor(call.chainId);
319
+ const estimate = await client
320
+ .estimateGas(call.raw
321
+ ? { to: call.target, data: call.data, value: call.value }
322
+ : {
323
+ account: forwarderOn(call.chainId),
324
+ to: call.target,
325
+ // ERC-2771: the forwarder appends the real sender.
326
+ data: concat([call.data, account]),
327
+ value: call.value,
328
+ })
329
+ .catch(() => call.gas);
330
+ const gas = (estimate > call.gas ? estimate : call.gas) +
331
+ (call.raw ? 0n : FORWARDER_OVERHEAD_GAS);
332
+ bound +=
333
+ (gas * (await client.getGasPrice()) * RELAYR_FEE_MARKUP_BPS) / 10000n;
334
+ }
335
+ return withMargin(bound);
336
+ }
337
+ /** The payment as the review shows it: one call to Relayr's pinned payment contract. */
338
+ function paymentReviewCall(details, account, safe = false) {
339
+ const { functionName, args } = decodeFunctionData({
340
+ abi: RELAYR_PAYMENT_ABI,
341
+ data: details.calldata,
342
+ });
343
+ return {
344
+ chainId: details.chainId,
345
+ from: account,
346
+ to: RELAYR_PAYMENT_ADDRESS,
347
+ data: details.calldata,
348
+ value: details.amount,
349
+ // A Safe signs the sent gas as safeTxGas; 0 makes a failed payment revert.
350
+ ...(safe ? { safeTxGas: 0n } : { gas: RELAYR_PAYMENT_GAS }),
351
+ abi: RELAYR_PAYMENT_ABI,
352
+ functionName,
353
+ args,
354
+ label: "Pay network fee",
355
+ contractName: "Relayr payment",
356
+ };
357
+ }
358
+ async function reviewPayment(details, account, action, safe = false) {
359
+ const model = buildReviewModel({
360
+ calls: [paymentReviewCall(details, account, safe)],
361
+ title: action.title ?? "Review network fee",
362
+ ...(action.context ? { context: action.context } : {}),
363
+ });
364
+ await promptReview(model, Promise.resolve({ status: "passed" }), Promise.resolve(model));
365
+ }
366
+ /**
367
+ * Send one reviewed payment without asking again: Relayr's pinned contract
368
+ * and selector only, from `account`, after the runtime and simulation checks,
369
+ * under the payment's write hold (or `intent`'s). The payment must be proven
370
+ * from the chain before the hash it was mined under comes back.
371
+ */
372
+ async function sendPayment(details, account, deps, hooks = {}) {
373
+ const { adapter } = deps;
374
+ const safe = hooks.safe ?? false;
375
+ await requireOrdinaryWallet(adapter, account, safe);
376
+ const client = deps.clientFor(details.chainId);
377
+ const call = {
378
+ chainId: details.chainId,
379
+ to: RELAYR_PAYMENT_ADDRESS,
380
+ data: details.calldata,
381
+ value: details.amount,
382
+ ...(hooks.intent ? { intent: hooks.intent.key } : {}),
383
+ };
384
+ const handle = openStepHold(deps.hold, account, safe, call);
385
+ /** The attempt is saved (`onSending`), and the wallet not yet asked. */
386
+ let saved = false;
387
+ let invoked = false;
388
+ /** The reservation this payment's send took. */
389
+ let reserved;
390
+ /** A proposal of exactly this payment its Safe already queued, sent in its place. */
391
+ let queued = null;
392
+ const attempt = () => {
393
+ const held = handle.peek();
394
+ if (held.status !== "clear")
395
+ throw new WriteHeldError(held);
396
+ return handle.send(client, (gate) => submitReviewedContractWrite({
397
+ request: call,
398
+ expectedAccount: account,
399
+ review: () => Promise.resolve(),
400
+ switchChain: async (chainId) => {
401
+ if (adapter.chainId() !== chainId)
402
+ await adapter.switchChain(chainId);
403
+ },
404
+ currentAccount: () => adapter.account(),
405
+ reverify: async () => {
406
+ await hooks.recheck?.();
407
+ // A lapsed payment would only revert in simulation.
408
+ if (quoteExpired(details.deadline))
409
+ throw new QuoteExpiredError();
410
+ await requireRelayrPaymentRuntime(client);
411
+ },
412
+ simulate: async () => {
413
+ const [, found] = await Promise.all([
414
+ simulateRelayrPayment(client, {
415
+ from: account,
416
+ payment: details,
417
+ }),
418
+ // A Safe never proposes again a payment it already queued exactly.
419
+ safe && hasSafeService(details.chainId)
420
+ ? findPendingSafeAppProposal(client, details.chainId, account, {
421
+ to: RELAYR_PAYMENT_ADDRESS,
422
+ data: details.calldata,
423
+ value: details.amount,
424
+ }, deps.hold.safeService, true)
425
+ : null,
426
+ ]);
427
+ queued = found;
428
+ return details;
429
+ },
430
+ beforeWrite: async () => {
431
+ const { intent } = hooks;
432
+ // Only the check and the reservation are under the lock: never a
433
+ // chain switch, simulation or wallet that might not answer.
434
+ await (intent
435
+ ? browserLocks().request(`nana-sdk:relayr-payment:${account.toLowerCase()}:${intent.key}`, { mode: "exclusive" }, async () => {
436
+ requireIntentClear(deps, account, intent);
437
+ await gate.beforeSend();
438
+ })
439
+ : gate.beforeSend());
440
+ reserved = handle.peek().reservation;
441
+ try {
442
+ await hooks.onSending?.();
443
+ }
444
+ catch (error) {
445
+ gate.onRejected();
446
+ throw error;
447
+ }
448
+ saved = true;
449
+ },
450
+ beforeSend: () => {
451
+ try {
452
+ if (details.target.toLowerCase() !== RELAYR_PAYMENT_ADDRESS ||
453
+ details.calldata.slice(0, 10).toLowerCase() !==
454
+ RELAYR_PAYMENT_SELECTOR) {
455
+ throw new Error("The Relayr payment is not to its pinned contract.");
456
+ }
457
+ if (quoteExpired(details.deadline))
458
+ throw new QuoteExpiredError();
459
+ if (adapter.isSafe() !== safe)
460
+ throw new Error(safe
461
+ ? "Wallet connection changed. Review the payment again."
462
+ : ORDINARY_WALLET);
463
+ if (adapter.chainId() !== details.chainId) {
464
+ throw new Error("Your wallet changed networks. Review the payment again.");
465
+ }
466
+ // Last, after every callback above could have run.
467
+ if (adapter.account()?.toLowerCase() !== account.toLowerCase()) {
468
+ throw new Error(ACCOUNT_CHANGED);
469
+ }
470
+ }
471
+ catch (error) {
472
+ throw new RelayrPaymentNotSentError(error);
473
+ }
474
+ },
475
+ onBeforeWriteAborted: () => gate.onRejected(),
476
+ onWriteRejected: () => gate.onRejected(),
477
+ onWriteSubmitted: async (submitted) => {
478
+ gate.afterSubmit(submitted, queued?.tx);
479
+ await hooks.onSent?.(submitted);
480
+ },
481
+ // The pinned contract and gas, never the quote's word.
482
+ write: async ({ chainId, calldata, amount }) => {
483
+ // An adopted proposal is already with the Safe: never "not sent".
484
+ invoked = true;
485
+ if (queued)
486
+ return queued.proposalHash;
487
+ try {
488
+ return await adapter.sendTransaction({
489
+ chainId,
490
+ to: RELAYR_PAYMENT_ADDRESS,
491
+ data: calldata,
492
+ value: amount,
493
+ gas: safe ? 0n : RELAYR_PAYMENT_GAS,
494
+ });
495
+ }
496
+ catch (error) {
497
+ // Only the wallet's own 4001 says it declined.
498
+ throw relayrWalletPaymentError(error);
499
+ }
500
+ },
501
+ }));
502
+ };
503
+ let hash;
504
+ try {
505
+ if (hooks.intent)
506
+ requireIntentClear(deps, account, hooks.intent);
507
+ hash = await attempt();
508
+ }
509
+ catch (error) {
510
+ // Saved as sending, refused by a final gate, never given to the wallet.
511
+ if (saved && !invoked && !(error instanceof RelayrPaymentNotSentError)) {
512
+ throw new RelayrPaymentNotSentError(error);
513
+ }
514
+ throw error;
515
+ }
516
+ // A proposal: its hold follows it to the Safe's execution.
517
+ if (safe)
518
+ return hash;
519
+ // From here the payment was sent: it ends proven, reverted or
520
+ // contradicted as the chain shows it, or unreadable with its hash, never
521
+ // as a bare read failure.
522
+ try {
523
+ // The watcher's receipt only says when to ask the hold: the hold checks
524
+ // the receipt itself and settles, so a reverted payment can be reviewed
525
+ // and sent again without waiting on a view. Its settlement is the only
526
+ // word on a revert; a success is proven below either way.
527
+ const seen = await waitForTrackedReceipt(client, hash);
528
+ const state = await handle.state(client).catch(() => undefined);
529
+ const settled = state?.status === "clear" &&
530
+ reserved !== undefined &&
531
+ state.reservation === reserved &&
532
+ state.settled &&
533
+ "hash" in state.settled
534
+ ? state.settled
535
+ : undefined;
536
+ // A wallet that sped the payment up mined it under another hash.
537
+ const mined = settled?.hash ?? seen.transactionHash;
538
+ if (mined.toLowerCase() !== hash.toLowerCase()) {
539
+ try {
540
+ await hooks.onSent?.(mined);
541
+ }
542
+ catch (error) {
543
+ throw new SubmittedContractWriteError(mined, error);
544
+ }
545
+ }
546
+ if (settled?.result === "failed")
547
+ throw new ReviewedCallRevertedError(0, mined, await heldReceipt(settled, client, seen));
548
+ if (!settled && seen.status === "reverted")
549
+ throw new RelayrProofUnavailableError(`Could not read Relayr payment ${mined} on chain ${details.chainId}. Do not pay again; check it later.`);
550
+ // A proof the node can't read yet is read again, as long as `follow` would
551
+ // follow a step, a read that stalls included; a contradicting proof is
552
+ // never retried. Past that, it is unreadable, its hash kept in the line.
553
+ const deadline = Date.now() + FOLLOW_MS;
554
+ let unread;
555
+ for (;;) {
556
+ const left = deadline - Date.now();
557
+ let timer;
558
+ try {
559
+ const proven = await Promise.race([
560
+ verifyRelayrPayment(client, {
561
+ hash: mined,
562
+ from: account,
563
+ payment: details,
564
+ }).then(() => true),
565
+ new Promise((resolve) => {
566
+ timer = setTimeout(() => resolve(false), Math.max(left, 0));
567
+ }),
568
+ ]);
569
+ if (proven)
570
+ return mined;
571
+ throw Object.assign(new RelayrProofUnavailableError(`Could not read Relayr payment ${mined} on chain ${details.chainId}. Do not pay again; check it later.`), { cause: unread });
572
+ }
573
+ catch (error) {
574
+ if (!(error instanceof RelayrProofUnavailableError) ||
575
+ deadline - Date.now() <= 0)
576
+ throw error;
577
+ unread = error;
578
+ await pause(Math.min(RECEIPT_RETRY_MS, deadline - Date.now()), undefined, () => null);
579
+ }
580
+ finally {
581
+ clearTimeout(timer);
582
+ }
583
+ }
584
+ }
585
+ catch (error) {
586
+ if (error instanceof ReviewedCallRevertedError ||
587
+ error instanceof RelayrProofError ||
588
+ error instanceof RelayrProofUnavailableError ||
589
+ error instanceof SubmittedContractWriteError)
590
+ throw error;
591
+ throw Object.assign(new RelayrProofUnavailableError(`Could not read Relayr payment ${hash} on chain ${details.chainId}. Do not pay again; check it later.`), { cause: error });
592
+ }
593
+ }
594
+ /**
595
+ * Signs `calls` as ERC-2771 forward requests after one review of the
596
+ * requests, decoded, and returns the Relayr bundle entries that run them.
597
+ * Each destination must trust the chain's canonical forwarder. Nothing is
598
+ * posted or paid.
599
+ */
600
+ export async function signReviewedForwardRequests(calls, action, deps) {
601
+ const { account } = action;
602
+ await requireOrdinaryWallet(deps.adapter, account);
603
+ const requests = await forwardRequests(calls, account, deps.clientFor);
604
+ const prepared = requests.map(({ chainId, request }) => prepareSignature({ kind: "forward-request", chainId, request }, account));
605
+ const model = buildReviewModel({
606
+ calls: [],
607
+ signatures: prepared.map(({ review }) => review),
608
+ title: action.title ?? "Review relayed transactions",
609
+ ...(action.context ? { context: action.context } : {}),
610
+ });
611
+ await promptReview(model, Promise.resolve({ status: "passed" }), Promise.resolve(model));
612
+ const signatures = await signPrepared(prepared, account, deps.adapter);
613
+ return requests.map(({ chainId, request }, index) => entryOf(chainId, request, signatures[index]));
614
+ }
615
+ /**
616
+ * Pays a Relayr quote after one review of the payment: the option on
617
+ * `chainId`, authenticated against the quote, sent to Relayr's pinned
618
+ * contract and proven from the chain. For a payment resumed apart from its
619
+ * signatures. A Safe app pays by proposal: `safeTxHash` comes back, held
620
+ * until the Safe runs it. `onSending` runs after the hold reserves, right
621
+ * before the wallet opens (a throw sends nothing); `onSent` gets every hash
622
+ * the payment is known by. With `plan` and `relayrRound`, a quote of exactly
623
+ * that round's calls spends the round's reviewed fee bound once: at most
624
+ * the bound pays with no prompt, and a higher fee, a spent round or another
625
+ * chain is asked.
626
+ */
627
+ export async function payRelayrReviewed(quote, chainId, action, deps) {
628
+ const { account } = action;
629
+ await walletReady(deps.adapter);
630
+ const safe = deps.adapter.isSafe();
631
+ await requireOrdinaryWallet(deps.adapter, account, safe);
632
+ const details = paymentOn(quote, chainId);
633
+ const intent = {
634
+ key: `relayr:${details.bundleUuid}`,
635
+ funding: relayrPaymentChains(quote.expectedTransactions.map((binding) => binding.chain)),
636
+ paidOnce: true,
637
+ };
638
+ requireIntentClear(deps, account, intent);
639
+ if (action.plan && action.relayrRound !== undefined) {
640
+ const cap = spendPlannedRelayrRound(action.plan, account, action.relayrRound, quotedCalls(quote), details.chainId);
641
+ if (cap === undefined || details.amount > cap)
642
+ await reviewPayment(details, account, {
643
+ title: cap ? "The network fee went up" : "Review network fee",
644
+ ...(action.context ? { context: action.context } : {}),
645
+ }, safe);
646
+ }
647
+ else if (action.plan)
648
+ spendPlan(action.plan, account, [
649
+ {
650
+ chainId: details.chainId,
651
+ from: account,
652
+ to: RELAYR_PAYMENT_ADDRESS,
653
+ data: details.calldata,
654
+ value: details.amount,
655
+ },
656
+ ]);
657
+ else
658
+ await reviewPayment(details, account, action, safe);
659
+ const hash = await sendPayment(details, account, deps, {
660
+ intent,
661
+ // Relayr's own word too, whatever this browser kept of earlier payments.
662
+ recheck: () => requireRelayrBundleUnpaid(details.bundleUuid),
663
+ ...(action.onSending ? { onSending: action.onSending } : {}),
664
+ ...(action.onSent ? { onSent: action.onSent } : {}),
665
+ safe,
666
+ });
667
+ return safe ? { safeTxHash: hash } : { hash };
668
+ }
669
+ /**
670
+ * Relayr's bundle once every one of its `expectedCount` transactions reports
671
+ * success. Only `failed`, in any case, is final; every other state, and a
672
+ * read that fails, is polled again every 2.5 seconds, for up to 5 minutes.
673
+ * Relayr's word is not proof: verify the destinations from the chain.
674
+ */
675
+ export async function pollRelayrBundle(uuid, expectedCount, options = {}) {
676
+ if (!Number.isSafeInteger(expectedCount) || expectedCount < 1) {
677
+ throw new Error("Relayr polling requires the exact destination count.");
678
+ }
679
+ const { signal } = options;
680
+ const until = Date.now() + POLL_TIMEOUT_MS;
681
+ for (;;) {
682
+ signal?.throwIfAborted();
683
+ const bundle = await readRelayrBundle(uuid, {
684
+ ...(options.fetch ? { fetch: options.fetch } : {}),
685
+ }).catch(() => null);
686
+ const records = Array.isArray(bundle?.transactions)
687
+ ? bundle.transactions
688
+ : [];
689
+ const failed = records.filter((record) => relayrStateIsFailed(record?.status?.state)).length;
690
+ if (failed) {
691
+ throw new Error(`Relayr could not run this on ${failed} chain${failed === 1 ? "" : "s"}.`);
692
+ }
693
+ if (bundle &&
694
+ records.length === expectedCount &&
695
+ records.every((record) => relayrStateIsSuccess(record?.status?.state))) {
696
+ return bundle;
697
+ }
698
+ if (Date.now() >= until) {
699
+ throw new Error(`Relayr is still processing paid bundle ${uuid}. Do not submit this action again; check it later.`);
700
+ }
701
+ await new Promise((resolve, reject) => {
702
+ const timer = setTimeout(resolve, POLL_INTERVAL_MS);
703
+ signal?.addEventListener("abort", () => {
704
+ clearTimeout(timer);
705
+ reject(signal.reason);
706
+ }, { once: true });
707
+ });
708
+ }
709
+ }
710
+ /** A Safe execution as the review shows it: the Safe's `execTransaction`, with the call or MultiSend inside. */
711
+ function safeExecutionCall(entry) {
712
+ const decoded = decodeFunctionData({ abi: SAFE_EXEC_ABI, data: entry.data });
713
+ if (decoded.functionName !== "execTransaction") {
714
+ throw new Error("The saved call is not a Safe execution.");
715
+ }
716
+ const [to, value, data, operation] = decoded.args;
717
+ const inner = multiSendCallsOf({ to, data, operation }) ?? [
718
+ { to, value, data },
719
+ ];
720
+ return {
721
+ chainId: entry.chain,
722
+ to: entry.target,
723
+ data: entry.data,
724
+ value: BigInt(entry.value),
725
+ abi: SAFE_EXEC_ABI,
726
+ functionName: "execTransaction",
727
+ args: decoded.args,
728
+ contractName: "Safe",
729
+ calls: inner.map((call) => decodeReviewCall({ chainId: entry.chain, from: entry.target, ...call })),
730
+ };
731
+ }
732
+ /**
733
+ * The wallet side of {@link createSafeRelayrController}: `review` shows the
734
+ * frozen Safe executions in core's review, and `sendPayment` reviews and
735
+ * sends the selected payment as {@link payRelayrReviewed} does, with the
736
+ * controller's checks and journal callbacks around the wallet. A quote paid
737
+ * before is paid again only when {@link requireRelayrRetry} clears every
738
+ * earlier payment.
739
+ */
740
+ export function safeRelayrWalletDeps(deps) {
741
+ return {
742
+ review: async (executions, { resumed }) => {
743
+ const model = buildReviewModel({
744
+ calls: executions.map(({ entry }) => safeExecutionCall(entry)),
745
+ title: `Review ${executions.length} Safe execution${executions.length === 1 ? "" : "s"}`,
746
+ confirmLabel: resumed
747
+ ? "Use saved quote"
748
+ : "Review & get payment options",
749
+ });
750
+ await promptReview(model, Promise.resolve({ status: "passed" }), Promise.resolve(model));
751
+ },
752
+ sendPayment: async ({ session, payment, beforeSend, onSending, onSent, }) => {
753
+ const { account } = session;
754
+ await requireOrdinaryWallet(deps.adapter, account);
755
+ if (session.payments.length >= MAX_RELAYR_SENT_PAYMENTS) {
756
+ throw new Error("This Relayr quote was paid too many times to pay again. Keep it pending; do not pay again.");
757
+ }
758
+ if (!session.quote)
759
+ throw new Error("This Safe action has no quote to pay.");
760
+ const details = relayrPaymentDetails(payment, {
761
+ bundleUuid: session.quote.bundle_uuid,
762
+ destinationChainIds: session.executions.map(({ entry }) => entry.chain),
763
+ });
764
+ await reviewPayment(details, account, {});
765
+ const payments = [...session.payments];
766
+ const hash = await sendPayment(details, account, deps, {
767
+ recheck: async () => {
768
+ await beforeSend();
769
+ if (session.payments.length) {
770
+ await requireRelayrRetry(deps.clientFor, {
771
+ payments: session.payments,
772
+ from: account,
773
+ bundleUuid: details.bundleUuid,
774
+ });
775
+ }
776
+ },
777
+ onSending,
778
+ onSent: async (sent) => {
779
+ payments.push(sentRelayrPayment(details, sent));
780
+ await onSent([...payments]);
781
+ },
782
+ });
783
+ return { hash, payments };
784
+ },
785
+ };
786
+ }
787
+ //# sourceMappingURL=relayrSend.js.map