@bananapus/nana-sdk-core 2.17.0 → 2.18.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 (197) hide show
  1. package/dist/cjs/constants.d.ts +1 -13
  2. package/dist/cjs/constants.d.ts.map +1 -1
  3. package/dist/cjs/constants.js +4 -22
  4. package/dist/cjs/constants.js.map +1 -1
  5. package/dist/cjs/generated/abi/jbBuybackHookAbi.d.ts +1187 -0
  6. package/dist/cjs/generated/abi/jbBuybackHookAbi.d.ts.map +1 -0
  7. package/dist/cjs/generated/abi/jbBuybackHookAbi.js +841 -0
  8. package/dist/cjs/generated/abi/jbBuybackHookAbi.js.map +1 -0
  9. package/dist/cjs/generated/abi/jbBuybackHookRegistryAbi.d.ts +791 -0
  10. package/dist/cjs/generated/abi/jbBuybackHookRegistryAbi.d.ts.map +1 -0
  11. package/dist/cjs/generated/abi/jbBuybackHookRegistryAbi.js +632 -0
  12. package/dist/cjs/generated/abi/jbBuybackHookRegistryAbi.js.map +1 -0
  13. package/dist/cjs/generated/abi/jbControllerAbi.d.ts +2569 -0
  14. package/dist/cjs/generated/abi/jbControllerAbi.d.ts.map +1 -0
  15. package/dist/cjs/generated/abi/jbControllerAbi.js +1906 -0
  16. package/dist/cjs/generated/abi/jbControllerAbi.js.map +1 -0
  17. package/dist/cjs/generated/abi/jbDirectoryAbi.d.ts +410 -0
  18. package/dist/cjs/generated/abi/jbDirectoryAbi.d.ts.map +1 -0
  19. package/dist/cjs/generated/abi/jbDirectoryAbi.js +365 -0
  20. package/dist/cjs/generated/abi/jbDirectoryAbi.js.map +1 -0
  21. package/dist/cjs/generated/abi/jbMultiTerminalAbi.d.ts +1863 -0
  22. package/dist/cjs/generated/abi/jbMultiTerminalAbi.d.ts.map +1 -0
  23. package/dist/cjs/generated/abi/jbMultiTerminalAbi.js +1459 -0
  24. package/dist/cjs/generated/abi/jbMultiTerminalAbi.js.map +1 -0
  25. package/dist/cjs/generated/abi/jbPermissionsAbi.d.ts +240 -0
  26. package/dist/cjs/generated/abi/jbPermissionsAbi.d.ts.map +1 -0
  27. package/dist/cjs/generated/abi/jbPermissionsAbi.js +164 -0
  28. package/dist/cjs/generated/abi/jbPermissionsAbi.js.map +1 -0
  29. package/dist/cjs/generated/abi/jbProjectsAbi.d.ts +626 -0
  30. package/dist/cjs/generated/abi/jbProjectsAbi.d.ts.map +1 -0
  31. package/dist/cjs/generated/abi/jbProjectsAbi.js +470 -0
  32. package/dist/cjs/generated/abi/jbProjectsAbi.js.map +1 -0
  33. package/dist/cjs/generated/abi/jbRouterTerminalGatewayAbi.d.ts +1503 -0
  34. package/dist/cjs/generated/abi/jbRouterTerminalGatewayAbi.d.ts.map +1 -0
  35. package/dist/cjs/generated/abi/jbRouterTerminalGatewayAbi.js +1037 -0
  36. package/dist/cjs/generated/abi/jbRouterTerminalGatewayAbi.js.map +1 -0
  37. package/dist/cjs/generated/abi/jbRouterTerminalRegistryAbi.d.ts +1119 -0
  38. package/dist/cjs/generated/abi/jbRouterTerminalRegistryAbi.d.ts.map +1 -0
  39. package/dist/cjs/generated/abi/jbRouterTerminalRegistryAbi.js +925 -0
  40. package/dist/cjs/generated/abi/jbRouterTerminalRegistryAbi.js.map +1 -0
  41. package/dist/cjs/generated/abi/jbSplitsAbi.d.ts +244 -0
  42. package/dist/cjs/generated/abi/jbSplitsAbi.d.ts.map +1 -0
  43. package/dist/cjs/generated/abi/jbSplitsAbi.js +198 -0
  44. package/dist/cjs/generated/abi/jbSplitsAbi.js.map +1 -0
  45. package/dist/cjs/generated/abi/jbTokensAbi.d.ts +602 -0
  46. package/dist/cjs/generated/abi/jbTokensAbi.d.ts.map +1 -0
  47. package/dist/cjs/generated/abi/jbTokensAbi.js +501 -0
  48. package/dist/cjs/generated/abi/jbTokensAbi.js.map +1 -0
  49. package/dist/cjs/generated/juicebox.d.ts +25405 -36537
  50. package/dist/cjs/generated/juicebox.d.ts.map +1 -1
  51. package/dist/cjs/generated/juicebox.js +14362 -22761
  52. package/dist/cjs/generated/juicebox.js.map +1 -1
  53. package/dist/cjs/pureConstants.d.ts +15 -0
  54. package/dist/cjs/pureConstants.d.ts.map +1 -0
  55. package/dist/cjs/pureConstants.js +28 -0
  56. package/dist/cjs/pureConstants.js.map +1 -0
  57. package/dist/cjs/review/decode.d.ts.map +1 -1
  58. package/dist/cjs/review/decode.js +35 -51
  59. package/dist/cjs/review/decode.js.map +1 -1
  60. package/dist/cjs/review/relayr.d.ts.map +1 -1
  61. package/dist/cjs/review/relayr.js +11 -31
  62. package/dist/cjs/review/relayr.js.map +1 -1
  63. package/dist/cjs/safe.d.ts +376 -8
  64. package/dist/cjs/safe.d.ts.map +1 -1
  65. package/dist/cjs/safe.js +1088 -29
  66. package/dist/cjs/safe.js.map +1 -1
  67. package/dist/cjs/safeService.d.ts +361 -1
  68. package/dist/cjs/safeService.d.ts.map +1 -1
  69. package/dist/cjs/safeService.js +844 -6
  70. package/dist/cjs/safeService.js.map +1 -1
  71. package/dist/cjs/untrusted.d.ts +8 -0
  72. package/dist/cjs/untrusted.d.ts.map +1 -0
  73. package/dist/cjs/untrusted.js +33 -0
  74. package/dist/cjs/untrusted.js.map +1 -0
  75. package/dist/cjs/v6/distributions.d.ts +90 -0
  76. package/dist/cjs/v6/distributions.d.ts.map +1 -0
  77. package/dist/cjs/v6/distributions.js +287 -0
  78. package/dist/cjs/v6/distributions.js.map +1 -0
  79. package/dist/cjs/v6/index.d.ts +1 -0
  80. package/dist/cjs/v6/index.d.ts.map +1 -1
  81. package/dist/cjs/v6/index.js +1 -0
  82. package/dist/cjs/v6/index.js.map +1 -1
  83. package/dist/cjs/v6/permissionIds.d.ts +62 -0
  84. package/dist/cjs/v6/permissionIds.d.ts.map +1 -0
  85. package/dist/cjs/v6/permissionIds.js +70 -0
  86. package/dist/cjs/v6/permissionIds.js.map +1 -0
  87. package/dist/cjs/v6/permissions.d.ts +3 -62
  88. package/dist/cjs/v6/permissions.d.ts.map +1 -1
  89. package/dist/cjs/v6/permissions.js +6 -67
  90. package/dist/cjs/v6/permissions.js.map +1 -1
  91. package/dist/cjs/v6/splits.d.ts +1 -1
  92. package/dist/cjs/v6/splits.d.ts.map +1 -1
  93. package/dist/cjs/v6/splits.js +3 -3
  94. package/dist/cjs/v6/splits.js.map +1 -1
  95. package/dist/cjs/v6/sticky.d.ts +2 -2
  96. package/dist/cjs/v6/sticky.d.ts.map +1 -1
  97. package/dist/cjs/v6/types.d.ts +1 -1
  98. package/dist/cjs/v6/types.d.ts.map +1 -1
  99. package/dist/esm/constants.d.ts +1 -13
  100. package/dist/esm/constants.d.ts.map +1 -1
  101. package/dist/esm/constants.js +1 -21
  102. package/dist/esm/constants.js.map +1 -1
  103. package/dist/esm/generated/abi/jbBuybackHookAbi.d.ts +1187 -0
  104. package/dist/esm/generated/abi/jbBuybackHookAbi.d.ts.map +1 -0
  105. package/dist/esm/generated/abi/jbBuybackHookAbi.js +838 -0
  106. package/dist/esm/generated/abi/jbBuybackHookAbi.js.map +1 -0
  107. package/dist/esm/generated/abi/jbBuybackHookRegistryAbi.d.ts +791 -0
  108. package/dist/esm/generated/abi/jbBuybackHookRegistryAbi.d.ts.map +1 -0
  109. package/dist/esm/generated/abi/jbBuybackHookRegistryAbi.js +629 -0
  110. package/dist/esm/generated/abi/jbBuybackHookRegistryAbi.js.map +1 -0
  111. package/dist/esm/generated/abi/jbControllerAbi.d.ts +2569 -0
  112. package/dist/esm/generated/abi/jbControllerAbi.d.ts.map +1 -0
  113. package/dist/esm/generated/abi/jbControllerAbi.js +1903 -0
  114. package/dist/esm/generated/abi/jbControllerAbi.js.map +1 -0
  115. package/dist/esm/generated/abi/jbDirectoryAbi.d.ts +410 -0
  116. package/dist/esm/generated/abi/jbDirectoryAbi.d.ts.map +1 -0
  117. package/dist/esm/generated/abi/jbDirectoryAbi.js +362 -0
  118. package/dist/esm/generated/abi/jbDirectoryAbi.js.map +1 -0
  119. package/dist/esm/generated/abi/jbMultiTerminalAbi.d.ts +1863 -0
  120. package/dist/esm/generated/abi/jbMultiTerminalAbi.d.ts.map +1 -0
  121. package/dist/esm/generated/abi/jbMultiTerminalAbi.js +1456 -0
  122. package/dist/esm/generated/abi/jbMultiTerminalAbi.js.map +1 -0
  123. package/dist/esm/generated/abi/jbPermissionsAbi.d.ts +240 -0
  124. package/dist/esm/generated/abi/jbPermissionsAbi.d.ts.map +1 -0
  125. package/dist/esm/generated/abi/jbPermissionsAbi.js +161 -0
  126. package/dist/esm/generated/abi/jbPermissionsAbi.js.map +1 -0
  127. package/dist/esm/generated/abi/jbProjectsAbi.d.ts +626 -0
  128. package/dist/esm/generated/abi/jbProjectsAbi.d.ts.map +1 -0
  129. package/dist/esm/generated/abi/jbProjectsAbi.js +467 -0
  130. package/dist/esm/generated/abi/jbProjectsAbi.js.map +1 -0
  131. package/dist/esm/generated/abi/jbRouterTerminalGatewayAbi.d.ts +1503 -0
  132. package/dist/esm/generated/abi/jbRouterTerminalGatewayAbi.d.ts.map +1 -0
  133. package/dist/esm/generated/abi/jbRouterTerminalGatewayAbi.js +1034 -0
  134. package/dist/esm/generated/abi/jbRouterTerminalGatewayAbi.js.map +1 -0
  135. package/dist/esm/generated/abi/jbRouterTerminalRegistryAbi.d.ts +1119 -0
  136. package/dist/esm/generated/abi/jbRouterTerminalRegistryAbi.d.ts.map +1 -0
  137. package/dist/esm/generated/abi/jbRouterTerminalRegistryAbi.js +922 -0
  138. package/dist/esm/generated/abi/jbRouterTerminalRegistryAbi.js.map +1 -0
  139. package/dist/esm/generated/abi/jbSplitsAbi.d.ts +244 -0
  140. package/dist/esm/generated/abi/jbSplitsAbi.d.ts.map +1 -0
  141. package/dist/esm/generated/abi/jbSplitsAbi.js +195 -0
  142. package/dist/esm/generated/abi/jbSplitsAbi.js.map +1 -0
  143. package/dist/esm/generated/abi/jbTokensAbi.d.ts +602 -0
  144. package/dist/esm/generated/abi/jbTokensAbi.d.ts.map +1 -0
  145. package/dist/esm/generated/abi/jbTokensAbi.js +498 -0
  146. package/dist/esm/generated/abi/jbTokensAbi.js.map +1 -0
  147. package/dist/esm/generated/juicebox.d.ts +25405 -36537
  148. package/dist/esm/generated/juicebox.d.ts.map +1 -1
  149. package/dist/esm/generated/juicebox.js +14634 -23044
  150. package/dist/esm/generated/juicebox.js.map +1 -1
  151. package/dist/esm/pureConstants.d.ts +15 -0
  152. package/dist/esm/pureConstants.d.ts.map +1 -0
  153. package/dist/esm/pureConstants.js +25 -0
  154. package/dist/esm/pureConstants.js.map +1 -0
  155. package/dist/esm/review/decode.d.ts.map +1 -1
  156. package/dist/esm/review/decode.js +21 -37
  157. package/dist/esm/review/decode.js.map +1 -1
  158. package/dist/esm/review/relayr.d.ts.map +1 -1
  159. package/dist/esm/review/relayr.js +5 -25
  160. package/dist/esm/review/relayr.js.map +1 -1
  161. package/dist/esm/safe.d.ts +376 -8
  162. package/dist/esm/safe.d.ts.map +1 -1
  163. package/dist/esm/safe.js +1070 -29
  164. package/dist/esm/safe.js.map +1 -1
  165. package/dist/esm/safeService.d.ts +361 -1
  166. package/dist/esm/safeService.d.ts.map +1 -1
  167. package/dist/esm/safeService.js +818 -5
  168. package/dist/esm/safeService.js.map +1 -1
  169. package/dist/esm/untrusted.d.ts +8 -0
  170. package/dist/esm/untrusted.d.ts.map +1 -0
  171. package/dist/esm/untrusted.js +28 -0
  172. package/dist/esm/untrusted.js.map +1 -0
  173. package/dist/esm/v6/distributions.d.ts +90 -0
  174. package/dist/esm/v6/distributions.d.ts.map +1 -0
  175. package/dist/esm/v6/distributions.js +283 -0
  176. package/dist/esm/v6/distributions.js.map +1 -0
  177. package/dist/esm/v6/index.d.ts +1 -0
  178. package/dist/esm/v6/index.d.ts.map +1 -1
  179. package/dist/esm/v6/index.js +1 -0
  180. package/dist/esm/v6/index.js.map +1 -1
  181. package/dist/esm/v6/permissionIds.d.ts +62 -0
  182. package/dist/esm/v6/permissionIds.d.ts.map +1 -0
  183. package/dist/esm/v6/permissionIds.js +66 -0
  184. package/dist/esm/v6/permissionIds.js.map +1 -0
  185. package/dist/esm/v6/permissions.d.ts +3 -62
  186. package/dist/esm/v6/permissions.d.ts.map +1 -1
  187. package/dist/esm/v6/permissions.js +2 -64
  188. package/dist/esm/v6/permissions.js.map +1 -1
  189. package/dist/esm/v6/splits.d.ts +1 -1
  190. package/dist/esm/v6/splits.d.ts.map +1 -1
  191. package/dist/esm/v6/splits.js +1 -1
  192. package/dist/esm/v6/splits.js.map +1 -1
  193. package/dist/esm/v6/sticky.d.ts +2 -2
  194. package/dist/esm/v6/sticky.d.ts.map +1 -1
  195. package/dist/esm/v6/types.d.ts +1 -1
  196. package/dist/esm/v6/types.d.ts.map +1 -1
  197. package/package.json +1 -1
@@ -1,11 +1,39 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SAFE_NONCE_GUIDANCE = exports.SAFE_SERVICE_PREFIX = exports.SAFE_PREFIX = void 0;
3
+ exports.SAFE_EXEC_ABI = exports.SAFE_TX_TYPES = exports.SAFE_NONCE_GUIDANCE = exports.SAFE_SERVICE_PREFIX = exports.SAFE_PREFIX = void 0;
4
4
  exports.safeServiceBase = safeServiceBase;
5
5
  exports.swapDeadline = swapDeadline;
6
6
  exports.safeQueueUrl = safeQueueUrl;
7
7
  exports.isSafeWalletPeer = isSafeWalletPeer;
8
8
  exports.waitForSafeExecutionHash = waitForSafeExecutionHash;
9
+ exports.safeTransactionMessage = safeTransactionMessage;
10
+ exports.safeTransactionHash = safeTransactionHash;
11
+ exports.canonicalSafeTxHash = canonicalSafeTxHash;
12
+ exports.safeExecutionResult = safeExecutionResult;
13
+ exports.requireSafeExecutionSuccess = requireSafeExecutionSuccess;
14
+ exports.usableSafeConfirmations = usableSafeConfirmations;
15
+ exports.safeExecutionSignatures = safeExecutionSignatures;
16
+ exports.safeExecutionArgs = safeExecutionArgs;
17
+ exports.safeTransactionHasRefund = safeTransactionHasRefund;
18
+ exports.safeProposalFor = safeProposalFor;
19
+ exports.safeBatchProposalFor = safeBatchProposalFor;
20
+ exports.safeTransactionMatchesCall = safeTransactionMatchesCall;
21
+ exports.nextProposalNonce = nextProposalNonce;
22
+ exports.onchainApprovalStep = onchainApprovalStep;
23
+ exports.hasSafeService = hasSafeService;
24
+ exports.safeTransactionUrl = safeTransactionUrl;
25
+ exports.listPendingSafeTransactions = listPendingSafeTransactions;
26
+ exports.findPendingSafeTransaction = findPendingSafeTransaction;
27
+ exports.readSafeTransaction = readSafeTransaction;
28
+ exports.proposeSafeTransaction = proposeSafeTransaction;
29
+ exports.submitSafeConfirmation = submitSafeConfirmation;
30
+ exports.fetchSafesOwnedBy = fetchSafesOwnedBy;
31
+ exports.safeCreationUrl = safeCreationUrl;
32
+ exports.parseSafeCreationPayload = parseSafeCreationPayload;
33
+ exports.fetchSafeCreation = fetchSafeCreation;
34
+ const viem_1 = require("viem");
35
+ const safe_js_1 = require("./safe.js");
36
+ const untrusted_js_1 = require("./untrusted.js");
9
37
  /**
10
38
  * Safe's per-chain app URL prefix. Wider than {@link SAFE_SERVICE_PREFIX}:
11
39
  * Safe app links work on chains without a hosted transaction service.
@@ -93,6 +121,9 @@ function isSafeWalletPeer(url) {
93
121
  function noHostedService(chainId) {
94
122
  return new Error(`Safe does not host a transaction service on chain ${chainId}, so this proposal cannot be tracked here. Execute it from the Safe app; the action takes effect once it is executed there.`);
95
123
  }
124
+ /** A service answer the wait cannot get past: it ends the wait, unlike a network error. */
125
+ class SafeExecutionRecordError extends Error {
126
+ }
96
127
  /**
97
128
  * Resolve a Safe proposal identifier to its actual onchain execution hash.
98
129
  * A safeTxHash is not a transaction hash and must never be receipt-polled.
@@ -107,6 +138,9 @@ function noHostedService(chainId) {
107
138
  * the execution's own hash when the owner executes at once.
108
139
  */
109
140
  async function waitForSafeExecutionHash(chainId, safeTxHash, options = {}) {
141
+ if (!(0, untrusted_js_1.isBytes32)(safeTxHash)) {
142
+ throw new Error(`Invalid Safe proposal hash: ${String(safeTxHash)}.`);
143
+ }
110
144
  const base = safeServiceBase(chainId);
111
145
  const { client } = options;
112
146
  if (!base && !client)
@@ -135,24 +169,25 @@ async function waitForSafeExecutionHash(chainId, safeTxHash, options = {}) {
135
169
  consecutiveNotFound = 0;
136
170
  const transaction = (await response.json());
137
171
  if (transaction.isExecuted && transaction.isSuccessful === false) {
138
- throw new Error("Safe executed the proposal, but the onchain transaction failed.");
172
+ throw new SafeExecutionRecordError("Safe executed the proposal, but the onchain transaction failed.");
139
173
  }
140
174
  if (transaction.isExecuted && transaction.transactionHash) {
175
+ if (!(0, untrusted_js_1.isBytes32)(transaction.transactionHash)) {
176
+ throw new SafeExecutionRecordError(`Safe’s transaction service reported ${safeTxHash} executed in a malformed transaction hash: ${String(transaction.transactionHash)}. Check the proposal in the Safe app.`);
177
+ }
141
178
  return transaction.transactionHash;
142
179
  }
143
180
  }
144
181
  else if (response.status === 404) {
145
182
  consecutiveNotFound += 1;
146
183
  if (consecutiveNotFound >= SAFE_EXECUTION_NOT_FOUND_LIMIT) {
147
- throw new Error("Safe’s transaction service has no record of this proposal. Tracking cannot continue here — check the proposal in the Safe app; if it exists there, it will still take effect once executed.");
184
+ throw new SafeExecutionRecordError("Safe’s transaction service has no record of this proposal. Tracking cannot continue here — check the proposal in the Safe app; if it exists there, it will still take effect once executed.");
148
185
  }
149
186
  }
150
187
  }
151
188
  catch (error) {
152
- if (error instanceof Error &&
153
- /executed the proposal.*failed|no record of this proposal/i.test(error.message)) {
189
+ if (error instanceof SafeExecutionRecordError)
154
190
  throw error;
155
- }
156
191
  // Other service/network failures are transient. Keep the already-created
157
192
  // proposal pending instead of inviting a duplicate submission.
158
193
  }
@@ -172,4 +207,807 @@ async function waitForSafeExecutionHash(chainId, safeTxHash, options = {}) {
172
207
  });
173
208
  }
174
209
  }
210
+ // ── Safe transactions ────────────────────────────────────────────────────────
211
+ /** The EIP-712 types a Safe transaction is signed and hashed with. */
212
+ exports.SAFE_TX_TYPES = {
213
+ SafeTx: [
214
+ { name: "to", type: "address" },
215
+ { name: "value", type: "uint256" },
216
+ { name: "data", type: "bytes" },
217
+ { name: "operation", type: "uint8" },
218
+ { name: "safeTxGas", type: "uint256" },
219
+ { name: "baseGas", type: "uint256" },
220
+ { name: "gasPrice", type: "uint256" },
221
+ { name: "gasToken", type: "address" },
222
+ { name: "refundReceiver", type: "address" },
223
+ { name: "nonce", type: "uint256" },
224
+ ],
225
+ };
226
+ /** `execTransaction` and `approveHash`, and the execution events of Safe 1.4. */
227
+ exports.SAFE_EXEC_ABI = [
228
+ {
229
+ type: "function",
230
+ name: "execTransaction",
231
+ stateMutability: "payable",
232
+ inputs: [
233
+ { name: "to", type: "address" },
234
+ { name: "value", type: "uint256" },
235
+ { name: "data", type: "bytes" },
236
+ { name: "operation", type: "uint8" },
237
+ { name: "safeTxGas", type: "uint256" },
238
+ { name: "baseGas", type: "uint256" },
239
+ { name: "gasPrice", type: "uint256" },
240
+ { name: "gasToken", type: "address" },
241
+ { name: "refundReceiver", type: "address" },
242
+ { name: "signatures", type: "bytes" },
243
+ ],
244
+ outputs: [{ name: "success", type: "bool" }],
245
+ },
246
+ {
247
+ type: "function",
248
+ name: "approveHash",
249
+ stateMutability: "nonpayable",
250
+ inputs: [{ name: "hashToApprove", type: "bytes32" }],
251
+ outputs: [],
252
+ },
253
+ {
254
+ type: "event",
255
+ name: "ExecutionSuccess",
256
+ anonymous: false,
257
+ inputs: [
258
+ { name: "txHash", type: "bytes32", indexed: true },
259
+ { name: "payment", type: "uint256", indexed: false },
260
+ ],
261
+ },
262
+ {
263
+ type: "event",
264
+ name: "ExecutionFailure",
265
+ anonymous: false,
266
+ inputs: [
267
+ { name: "txHash", type: "bytes32", indexed: true },
268
+ { name: "payment", type: "uint256", indexed: false },
269
+ ],
270
+ },
271
+ ];
272
+ function invalidField(name, value) {
273
+ return new Error(`The Safe transaction's ${name} is invalid: ${String(value)}.`);
274
+ }
275
+ function fieldAddress(name, value) {
276
+ if (typeof value !== "string" || !(0, viem_1.isAddress)(value)) {
277
+ throw invalidField(name, value);
278
+ }
279
+ return (0, viem_1.getAddress)(value);
280
+ }
281
+ function fieldUint(name, value) {
282
+ const parsed = (0, untrusted_js_1.uint256)(value);
283
+ if (parsed === null)
284
+ throw invalidField(name, value);
285
+ return parsed;
286
+ }
287
+ /** A nonce as a number: apps order and compare them. */
288
+ function nonceOf(value) {
289
+ const parsed = (0, untrusted_js_1.uint256)(value);
290
+ if (parsed === null || parsed > BigInt(Number.MAX_SAFE_INTEGER)) {
291
+ throw invalidField("nonce", value);
292
+ }
293
+ return Number(parsed);
294
+ }
295
+ /**
296
+ * The exact EIP-712 message of `tx`. Every field is required and read
297
+ * strictly: addresses checksummed, amounts as uint256 in any form, operation 0
298
+ * (CALL) or 1 (DELEGATECALL), null data as empty, and a null gas token or
299
+ * refund receiver as the zero address. Throws naming the first field that is
300
+ * not.
301
+ */
302
+ function safeTransactionMessage(tx) {
303
+ if (!tx || typeof tx !== "object")
304
+ throw invalidField("record", tx);
305
+ const operation = fieldUint("operation", tx.operation);
306
+ if (operation > 1n)
307
+ throw invalidField("operation", tx.operation);
308
+ const data = tx.data ?? "0x";
309
+ if (!(0, untrusted_js_1.isHexBytes)(data))
310
+ throw invalidField("data", tx.data);
311
+ return {
312
+ to: fieldAddress("to", tx.to),
313
+ value: fieldUint("value", tx.value),
314
+ data,
315
+ operation: operation === 1n ? 1 : 0,
316
+ safeTxGas: fieldUint("safeTxGas", tx.safeTxGas),
317
+ baseGas: fieldUint("baseGas", tx.baseGas),
318
+ gasPrice: fieldUint("gasPrice", tx.gasPrice),
319
+ // The service stores an omitted gas token or refund receiver as null and
320
+ // hashes it as the zero address, as Safe does; nothing else may be absent.
321
+ gasToken: tx.gasToken === null
322
+ ? viem_1.zeroAddress
323
+ : fieldAddress("gasToken", tx.gasToken),
324
+ refundReceiver: tx.refundReceiver === null
325
+ ? viem_1.zeroAddress
326
+ : fieldAddress("refundReceiver", tx.refundReceiver),
327
+ nonce: BigInt(nonceOf(tx.nonce)),
328
+ };
329
+ }
330
+ function requireChainId(chainId) {
331
+ if (!Number.isSafeInteger(chainId) || chainId <= 0) {
332
+ throw new Error(`Invalid chain ID: ${String(chainId)}.`);
333
+ }
334
+ return chainId;
335
+ }
336
+ function requireSafe(safe) {
337
+ if (typeof safe !== "string" || !(0, viem_1.isAddress)(safe)) {
338
+ throw new Error(`Invalid Safe address: ${String(safe)}.`);
339
+ }
340
+ return (0, viem_1.getAddress)(safe);
341
+ }
342
+ /** The EIP-712 hash `safe` signs and executes `tx` under on `chainId`. */
343
+ function safeTransactionHash(chainId, safe, tx) {
344
+ return (0, viem_1.hashTypedData)({
345
+ domain: {
346
+ chainId: requireChainId(chainId),
347
+ verifyingContract: requireSafe(safe),
348
+ },
349
+ types: exports.SAFE_TX_TYPES,
350
+ primaryType: "SafeTx",
351
+ message: safeTransactionMessage(tx),
352
+ });
353
+ }
354
+ /**
355
+ * The hash of `tx`'s exact fields, refusing a record that names another Safe,
356
+ * advertises a different `safeTxHash` or `contractTransactionHash`, or, given
357
+ * `expected`, no longer hashes to the reviewed hash.
358
+ */
359
+ function canonicalSafeTxHash(chainId, safe, tx, expected) {
360
+ const address = requireSafe(safe);
361
+ if (tx?.safe !== undefined &&
362
+ tx.safe !== null &&
363
+ (typeof tx.safe !== "string" ||
364
+ !(0, viem_1.isAddress)(tx.safe) ||
365
+ !(0, viem_1.isAddressEqual)(tx.safe, address))) {
366
+ throw new Error(`The Safe transaction belongs to ${String(tx.safe)}, not ${address}.`);
367
+ }
368
+ const computed = safeTransactionHash(chainId, address, tx);
369
+ for (const [name, advertised] of [
370
+ ["safeTxHash", tx.safeTxHash],
371
+ ["contractTransactionHash", tx.contractTransactionHash],
372
+ ]) {
373
+ if (advertised === undefined || advertised === null)
374
+ continue;
375
+ if (!(0, untrusted_js_1.isBytes32)(advertised) ||
376
+ advertised.toLowerCase() !== computed.toLowerCase()) {
377
+ throw new Error(`The Safe transaction's ${name} ${String(advertised)} does not match its fields, which hash to ${computed}.`);
378
+ }
379
+ }
380
+ if (expected !== undefined && !(0, untrusted_js_1.isBytes32)(expected)) {
381
+ throw new Error(`Invalid reviewed Safe transaction hash: ${String(expected)}.`);
382
+ }
383
+ if (expected !== undefined &&
384
+ expected.toLowerCase() !== computed.toLowerCase()) {
385
+ throw new Error(`The Safe transaction changed: it hashes to ${computed}, not the reviewed ${expected}.`);
386
+ }
387
+ return computed;
388
+ }
389
+ // ── Execution results ────────────────────────────────────────────────────────
390
+ /** `ExecutionSuccess(bytes32,uint256)` and `ExecutionFailure(bytes32,uint256)`, the same in Safe 1.3 and 1.4. */
391
+ const EXECUTION_SUCCESS_TOPIC = "0x442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e";
392
+ const EXECUTION_FAILURE_TOPIC = "0x23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23";
393
+ const ZERO_WORD = "0000000000000000000000000000000000000000000000000000000000000000";
394
+ /**
395
+ * A Safe execution event in exactly the Safe 1.4 layout (txHash indexed) or
396
+ * the Safe 1.3 layout (txHash the first data word), with its payment. Null for
397
+ * another event; a string naming what is wrong for a malformed one.
398
+ */
399
+ function safeExecutionEvent(log) {
400
+ const topics = Array.isArray(log.topics) ? log.topics : [];
401
+ const topic = typeof topics[0] === "string" ? topics[0].toLowerCase() : "";
402
+ if (topic !== EXECUTION_SUCCESS_TOPIC && topic !== EXECUTION_FAILURE_TOPIC) {
403
+ return null;
404
+ }
405
+ const failed = topic === EXECUTION_FAILURE_TOPIC;
406
+ const name = failed ? "ExecutionFailure" : "ExecutionSuccess";
407
+ const data = typeof log.data === "string" ? log.data.toLowerCase() : "";
408
+ if (topics.length === 2) {
409
+ if (!(0, untrusted_js_1.isBytes32)(topics[1]) || !/^0x[\da-f]{64}$/.test(data)) {
410
+ return `a malformed ${name} event`;
411
+ }
412
+ return {
413
+ failed,
414
+ txHash: topics[1].toLowerCase(),
415
+ payment: BigInt(data),
416
+ };
417
+ }
418
+ if (topics.length === 1) {
419
+ if (!/^0x[\da-f]{128}$/.test(data))
420
+ return `a malformed ${name} event`;
421
+ return {
422
+ failed,
423
+ txHash: data.slice(0, 66),
424
+ payment: BigInt(`0x${data.slice(66)}`),
425
+ };
426
+ }
427
+ return `a malformed ${name} event`;
428
+ }
429
+ /**
430
+ * What `receipt` proves about the Safe transaction `hash` on `safe`. `hash` is
431
+ * what the wallet returned: the safeTxHash, or, when Safe{Wallet} executed the
432
+ * proposal at once, the execution's own transaction hash. Then the receipt
433
+ * must carry exactly one execution event of `safe`, whatever its hash.
434
+ * Otherwise exactly one event for `hash`, so another proposal in the same
435
+ * batch cannot stand in for this one, and another proposal's event, refund
436
+ * included, never decides this one. Any malformed execution event of `safe`
437
+ * leaves the result unproven, since it cannot be told apart from this one.
438
+ *
439
+ * `receipt` is viem's parsed receipt: a raw RPC receipt (status "0x1") reads
440
+ * unproven.
441
+ *
442
+ * The at-once reading proves only that the Safe ran one transaction in that
443
+ * receipt. A caller who holds the executing transaction's input should also
444
+ * bind it to the reviewed call, as an `execTransaction` with exactly the
445
+ * reviewed fields.
446
+ */
447
+ function safeExecutionResult(receipt, safe, hash) {
448
+ const address = requireSafe(safe).toLowerCase();
449
+ if (!(0, untrusted_js_1.isBytes32)(hash)) {
450
+ throw new Error(`Invalid Safe transaction hash: ${String(hash)}.`);
451
+ }
452
+ if (receipt?.status === "reverted")
453
+ return { status: "reverted" };
454
+ const unproven = (reason) => ({
455
+ status: "unproven",
456
+ reason,
457
+ });
458
+ if (receipt?.status !== "success" || !Array.isArray(receipt.logs)) {
459
+ return unproven(`The receipt has no success status and logs to prove ${hash} on Safe ${safe}.`);
460
+ }
461
+ const atOnce = typeof receipt.transactionHash === "string" &&
462
+ receipt.transactionHash.toLowerCase() === hash.toLowerCase();
463
+ const events = [];
464
+ for (const log of receipt.logs) {
465
+ if (typeof log?.address !== "string" ||
466
+ log.address.toLowerCase() !== address) {
467
+ continue;
468
+ }
469
+ const event = safeExecutionEvent(log);
470
+ if (typeof event === "string") {
471
+ return unproven(`Safe ${safe} logged ${event}.`);
472
+ }
473
+ if (event && (atOnce || event.txHash === hash.toLowerCase())) {
474
+ events.push(event);
475
+ }
476
+ }
477
+ const subject = atOnce ? `transaction ${hash}` : `Safe transaction ${hash}`;
478
+ if (events.length === 0) {
479
+ return unproven(`The receipt has no ExecutionSuccess or ExecutionFailure from Safe ${safe} for ${subject}.`);
480
+ }
481
+ if (events.length > 1) {
482
+ return unproven(`Safe ${safe} logged ${events.length} execution results (${events.map((event) => (event.failed ? "ExecutionFailure" : "ExecutionSuccess")).join(", ")}) for ${subject}, so the receipt proves none of them.`);
483
+ }
484
+ const [event] = events;
485
+ return {
486
+ status: event.failed ? "failed" : "success",
487
+ payment: event.payment,
488
+ };
489
+ }
490
+ /** Throws unless `receipt` proves the Safe transaction `hash` ran and succeeded ({@link safeExecutionResult}). */
491
+ function requireSafeExecutionSuccess(receipt, safe, hash) {
492
+ const result = safeExecutionResult(receipt, safe, hash);
493
+ if (result.status === "success")
494
+ return;
495
+ if (result.status === "reverted") {
496
+ throw new Error(`The transaction executing ${hash} on Safe ${safe} reverted, so the Safe ran nothing.`);
497
+ }
498
+ if (result.status === "failed") {
499
+ throw new Error(`Safe ${safe} ran ${hash}, but its call failed (ExecutionFailure).`);
500
+ }
501
+ throw new Error(result.reason);
502
+ }
503
+ // ── Signatures ───────────────────────────────────────────────────────────────
504
+ /**
505
+ * One confirmation's part of Safe's `signatures` bytes, without 0x:
506
+ *
507
+ * - none at all: the `approveHash` form (v = 1) naming the owner, a head;
508
+ * - an ECDSA, eth_sign or approved-hash signature: exactly 65 bytes, a head;
509
+ * an approved hash (v = 1) must name its own owner;
510
+ * - an EIP-1271 contract signature in the transaction service's standalone
511
+ * form: r = owner, s = 65, v = 0, then a length word and the signature,
512
+ * optionally zero-padded to a word. Its dynamic part becomes a tail that
513
+ * {@link safeExecutionSignatures} places after every head.
514
+ *
515
+ * Null for anything else, which never counts as a confirmation.
516
+ */
517
+ function signaturePart(owner, signature) {
518
+ const word = owner.slice(2).toLowerCase().padStart(64, "0");
519
+ if (signature === undefined || signature === null || signature === "") {
520
+ return { head: `${word}${ZERO_WORD}01` };
521
+ }
522
+ if (!(0, untrusted_js_1.isHexBytes)(signature) || signature.length < 132)
523
+ return null;
524
+ const bytes = signature.slice(2).toLowerCase();
525
+ const v = bytes.slice(128, 130);
526
+ if (v !== "00") {
527
+ if (bytes.length !== 130 || (v === "01" && bytes.slice(0, 64) !== word)) {
528
+ return null;
529
+ }
530
+ return { head: bytes };
531
+ }
532
+ if (bytes.length < 194 ||
533
+ bytes.slice(0, 64) !== word ||
534
+ BigInt(`0x${bytes.slice(64, 128)}`) !== 65n) {
535
+ return null;
536
+ }
537
+ const end = 194n + 2n * BigInt(`0x${bytes.slice(130, 194)}`);
538
+ if (end > BigInt(bytes.length) ||
539
+ BigInt(bytes.length) - end >= 64n ||
540
+ /[^0]/.test(bytes.slice(Number(end)))) {
541
+ return null;
542
+ }
543
+ return { contract: bytes.slice(194, Number(end)) };
544
+ }
545
+ function requireOwners(owners) {
546
+ if (!Array.isArray(owners)) {
547
+ throw new Error(`Invalid Safe owners: ${String(owners)}.`);
548
+ }
549
+ return new Set(owners.map((owner) => {
550
+ if (typeof owner !== "string" || !(0, viem_1.isAddress)(owner)) {
551
+ throw new Error(`Invalid Safe owner: ${String(owner)}.`);
552
+ }
553
+ return owner.toLowerCase();
554
+ }));
555
+ }
556
+ /**
557
+ * The confirmations of `tx` that count: one per current owner in `owners`,
558
+ * with a well-formed signature or none (an onchain approval), a signature
559
+ * preferred, in ascending numeric owner order as Safe requires (a string sort
560
+ * disagrees and reverts with GS026). Owners are checksummed.
561
+ */
562
+ function usableSafeConfirmations(tx, owners) {
563
+ const allowed = requireOwners(owners);
564
+ const byOwner = new Map();
565
+ for (const confirmation of Array.isArray(tx?.confirmations)
566
+ ? tx.confirmations
567
+ : []) {
568
+ const owner = confirmation?.owner;
569
+ if (typeof owner !== "string" ||
570
+ !(0, viem_1.isAddress)(owner) ||
571
+ !allowed.has(owner.toLowerCase()) ||
572
+ signaturePart(owner, confirmation.signature) === null) {
573
+ continue;
574
+ }
575
+ const key = owner.toLowerCase();
576
+ const existing = byOwner.get(key);
577
+ if (!existing || (!existing.signature && confirmation.signature)) {
578
+ byOwner.set(key, {
579
+ owner: (0, viem_1.getAddress)(owner),
580
+ signature: confirmation.signature ?? null,
581
+ });
582
+ }
583
+ }
584
+ // Owners are unique here, so no two compare equal.
585
+ return [...byOwner.values()].sort((left, right) => BigInt(left.owner) < BigInt(right.owner) ? -1 : 1);
586
+ }
587
+ /**
588
+ * The `signatures` bytes for `execTransaction`, as Safe's checkNSignatures
589
+ * reads them: one 65-byte head per {@link usableSafeConfirmations} entry, in
590
+ * owner order, then each contract signature's length and bytes. A contract
591
+ * signature's head points at its tail by byte offset, which is never below
592
+ * the heads' total length, so it passes Safe's GS021 check.
593
+ */
594
+ function safeExecutionSignatures(tx, owners) {
595
+ const parts = usableSafeConfirmations(tx, owners).map(({ owner, signature }) => ({
596
+ owner,
597
+ part: signaturePart(owner, signature),
598
+ }));
599
+ let head = "";
600
+ let tail = "";
601
+ for (const { owner, part } of parts) {
602
+ if ("head" in part) {
603
+ head += part.head;
604
+ continue;
605
+ }
606
+ const offset = parts.length * 65 + tail.length / 2;
607
+ head += `${owner.slice(2).toLowerCase().padStart(64, "0")}${offset.toString(16).padStart(64, "0")}00`;
608
+ tail += `${(part.contract.length / 2).toString(16).padStart(64, "0")}${part.contract}`;
609
+ }
610
+ return `0x${head}${tail}`;
611
+ }
612
+ /** `execTransaction`'s arguments for `tx`, signed by its usable confirmations from `owners`. */
613
+ function safeExecutionArgs(tx, owners) {
614
+ const message = safeTransactionMessage(tx);
615
+ return [
616
+ message.to,
617
+ message.value,
618
+ message.data,
619
+ message.operation,
620
+ message.safeTxGas,
621
+ message.baseGas,
622
+ message.gasPrice,
623
+ message.gasToken,
624
+ message.refundReceiver,
625
+ safeExecutionSignatures(tx, owners),
626
+ ];
627
+ }
628
+ /**
629
+ * Whether executing `tx` pays its executor a refund out of the Safe: Safe pays
630
+ * one only when gasPrice is above zero (in the gas token, or ETH, to the refund
631
+ * receiver or the executor). This SDK never proposes one. A queue card shows
632
+ * or refuses such a transaction before executing it. Throws on a malformed
633
+ * record, as {@link safeTransactionMessage} does.
634
+ */
635
+ function safeTransactionHasRefund(tx) {
636
+ return safeTransactionMessage(tx).gasPrice > 0n;
637
+ }
638
+ // ── Proposals ────────────────────────────────────────────────────────────────
639
+ /** The zero-refund Safe transaction this SDK proposes: one CALL, no gas fields. */
640
+ function safeProposalFor(call, nonce) {
641
+ const tx = {
642
+ to: fieldAddress("to", call?.to),
643
+ value: fieldUint("value", call?.value ?? 0n).toString(),
644
+ data: call?.data,
645
+ operation: 0,
646
+ safeTxGas: "0",
647
+ baseGas: "0",
648
+ gasPrice: "0",
649
+ gasToken: viem_1.zeroAddress,
650
+ refundReceiver: viem_1.zeroAddress,
651
+ nonce: nonceOf(nonce),
652
+ confirmations: [],
653
+ };
654
+ safeTransactionMessage(tx);
655
+ return tx;
656
+ }
657
+ /**
658
+ * One Safe transaction for a batch: a DELEGATECALL into MultiSendCallOnly,
659
+ * which runs each call from the Safe, in order, or reverts them all.
660
+ */
661
+ function safeBatchProposalFor(calls, nonce) {
662
+ return {
663
+ ...safeProposalFor({ to: safe_js_1.MULTI_SEND_CALL_ONLY, data: (0, safe_js_1.encodeMultiSend)(calls) }, nonce),
664
+ operation: 1,
665
+ };
666
+ }
667
+ /**
668
+ * Whether `tx` is exactly `call` as a zero-refund proposal: the same target,
669
+ * value, data and operation, and no safeTxGas, baseGas, gasPrice, gas token or
670
+ * refund receiver. A malformed `tx` never matches.
671
+ */
672
+ function safeTransactionMatchesCall(tx, call) {
673
+ let message;
674
+ try {
675
+ message = safeTransactionMessage(tx);
676
+ }
677
+ catch {
678
+ return false;
679
+ }
680
+ return (typeof call?.to === "string" &&
681
+ (0, viem_1.isAddress)(call.to) &&
682
+ (0, viem_1.isAddressEqual)(message.to, call.to) &&
683
+ message.value === (call.value ?? 0n) &&
684
+ message.data.toLowerCase() === String(call.data).toLowerCase() &&
685
+ message.operation === (call.operation ?? 0) &&
686
+ message.safeTxGas === 0n &&
687
+ message.baseGas === 0n &&
688
+ message.gasPrice === 0n &&
689
+ (0, viem_1.isAddressEqual)(message.gasToken, viem_1.zeroAddress) &&
690
+ (0, viem_1.isAddressEqual)(message.refundReceiver, viem_1.zeroAddress));
691
+ }
692
+ /**
693
+ * The nonce a new proposal takes: after everything already queued, never below
694
+ * the Safe's own. Reusing a queued nonce would offer a replacement instead.
695
+ */
696
+ function nextProposalNonce(currentNonce, pending) {
697
+ return pending.reduce((next, tx) => Math.max(next, nonceOf(tx?.nonce) + 1), nonceOf(currentNonce));
698
+ }
699
+ /**
700
+ * The next onchain step for `account` on a chain with no Safe service. Safe
701
+ * counts the executing owner as a signature, so the owner who would complete
702
+ * the threshold executes instead of approving first.
703
+ */
704
+ function onchainApprovalStep({ account, approved, threshold, }) {
705
+ const others = approved.filter((owner) => !(0, viem_1.isAddressEqual)(owner, account));
706
+ if (others.length + 1 >= threshold) {
707
+ return { kind: "execute", signers: [...others, account] };
708
+ }
709
+ return others.length === approved.length
710
+ ? { kind: "approve" }
711
+ : { kind: "waiting" };
712
+ }
713
+ const PENDING_PAGE_SIZE = 50;
714
+ const MAX_PENDING = 250;
715
+ const SERVICE_DETAIL_CHARACTERS = 200;
716
+ /** Whether Safe hosts a transaction service for `chainId`; without one, owners approve onchain. */
717
+ function hasSafeService(chainId) {
718
+ return safeServiceBase(chainId) !== null;
719
+ }
720
+ /** The Safe app's page for one queued transaction. */
721
+ function safeTransactionUrl(chainId, safe, safeTxHash) {
722
+ const prefix = exports.SAFE_PREFIX[chainId];
723
+ if (!prefix || typeof safe !== "string" || !(0, viem_1.isAddress)(safe))
724
+ return null;
725
+ if (!(0, untrusted_js_1.isBytes32)(safeTxHash))
726
+ return null;
727
+ const address = (0, viem_1.getAddress)(safe);
728
+ return `https://app.safe.global/transactions/tx?safe=${prefix}:${address}&id=multisig_${address}_${safeTxHash}`;
729
+ }
730
+ function serviceOrThrow(chainId) {
731
+ const base = safeServiceBase(chainId);
732
+ if (!base) {
733
+ throw new Error(`Safe does not host a transaction service on chain ${chainId}.`);
734
+ }
735
+ return base;
736
+ }
737
+ /** The optional `jb-safe-api-key` from local storage, as a bearer token. */
738
+ function serviceHeaders(json = false) {
739
+ const headers = { accept: "application/json" };
740
+ if (json)
741
+ headers["content-type"] = "application/json";
742
+ try {
743
+ const key = globalThis.localStorage?.getItem("jb-safe-api-key");
744
+ if (key)
745
+ headers.authorization = `Bearer ${key}`;
746
+ }
747
+ catch {
748
+ // The key is optional.
749
+ }
750
+ return headers;
751
+ }
752
+ /** One service request. A 429 is refused before processing, so it waits and repeats, up to three times. */
753
+ async function serviceFetch(url, init, options) {
754
+ const request = options.fetch ?? fetch;
755
+ for (let attempt = 0;; attempt += 1) {
756
+ const response = await request(url, init);
757
+ if (response.status !== 429 || attempt >= 3)
758
+ return response;
759
+ const retryAfter = Number(response.headers.get("retry-after"));
760
+ await new Promise((resolve) => setTimeout(resolve, Number.isFinite(retryAfter) && retryAfter > 0
761
+ ? retryAfter * 1000
762
+ : 1000 * (attempt + 1)));
763
+ }
764
+ }
765
+ async function serviceDetail(response) {
766
+ const text = await response.text().catch(() => "");
767
+ return text ? `: ${text.slice(0, SERVICE_DETAIL_CHARACTERS)}` : "";
768
+ }
769
+ /** The most confirmations a service row may carry; each must be an owner's. */
770
+ const MAX_ROW_CONFIRMATIONS = 100;
771
+ /**
772
+ * Every field of a service row read strictly, its advertised hash required and
773
+ * checked, its confirmations bounded: the row with its nonce as a number and
774
+ * its own Safe and hash, or a refusal naming what is wrong.
775
+ */
776
+ function checkedRow(chainId, safe, row) {
777
+ try {
778
+ const tx = row;
779
+ if ((tx?.safeTxHash === undefined || tx.safeTxHash === null) &&
780
+ (tx?.contractTransactionHash === undefined ||
781
+ tx.contractTransactionHash === null)) {
782
+ throw new Error("It advertises no safeTxHash.");
783
+ }
784
+ if (tx.confirmations !== undefined &&
785
+ tx.confirmations !== null &&
786
+ (!Array.isArray(tx.confirmations) ||
787
+ tx.confirmations.length > MAX_ROW_CONFIRMATIONS)) {
788
+ throw new Error(`Its confirmations are not a list of at most ${MAX_ROW_CONFIRMATIONS}.`);
789
+ }
790
+ const hash = canonicalSafeTxHash(chainId, safe, tx);
791
+ return { ...tx, safe, safeTxHash: hash, nonce: nonceOf(tx.nonce) };
792
+ }
793
+ catch (error) {
794
+ throw new Error(`Safe's transaction service listed a transaction for Safe ${safe} on chain ${chainId} that cannot be trusted. ${error.message}`);
795
+ }
796
+ }
797
+ /**
798
+ * Every queued, unexecuted transaction of `safe` from nonce `currentNonce`
799
+ * on, read from the chain's transaction service. Each row must belong to
800
+ * `safe` and hash to its advertised safeTxHash. Throws when the chain has no
801
+ * service or the service fails, so an outage never reads as an empty queue,
802
+ * and when more than 250 are queued.
803
+ */
804
+ async function listPendingSafeTransactions(chainId, safe, currentNonce, options = {}) {
805
+ const base = serviceOrThrow(chainId);
806
+ const address = requireSafe(safe);
807
+ const fromNonce = nonceOf(currentNonce);
808
+ const rows = [];
809
+ for (let offset = 0; offset < MAX_PENDING; offset += PENDING_PAGE_SIZE) {
810
+ const url = `${base}/api/v1/safes/${address}/multisig-transactions/` +
811
+ `?executed=false&trusted=true&ordering=nonce&limit=${PENDING_PAGE_SIZE}` +
812
+ `&offset=${offset}&nonce__gte=${fromNonce}`;
813
+ let response = await serviceFetch(url, { headers: serviceHeaders() }, options);
814
+ if (!response.ok) {
815
+ // A failed page is retried once before the listing gives up.
816
+ await new Promise((resolve) => setTimeout(resolve, 500));
817
+ response = await serviceFetch(url, { headers: serviceHeaders() }, options);
818
+ }
819
+ if (!response.ok) {
820
+ throw new Error(`Safe's transaction service answered ${response.status} listing the queue of Safe ${address} on chain ${chainId}${await serviceDetail(response)}`);
821
+ }
822
+ const body = (await response.json());
823
+ if (!body || !Array.isArray(body.results)) {
824
+ throw new Error(`Safe's transaction service returned no transaction list for Safe ${address} on chain ${chainId}.`);
825
+ }
826
+ if (body.results.length > PENDING_PAGE_SIZE) {
827
+ throw new Error(`Safe's transaction service returned ${body.results.length} transactions in a page of ${PENDING_PAGE_SIZE} for Safe ${address} on chain ${chainId}.`);
828
+ }
829
+ rows.push(...body.results.map((row) => checkedRow(chainId, address, row)));
830
+ if (body.results.length < PENDING_PAGE_SIZE || !body.next) {
831
+ return rows.filter((row) => row.isExecuted !== true && row.nonce >= fromNonce);
832
+ }
833
+ }
834
+ throw new Error(`Safe ${address} has more than ${MAX_PENDING} queued transactions on chain ${chainId}. Nothing was proposed; execute or replace older ones first.`);
835
+ }
836
+ /** The queued zero-refund proposal of exactly `call`, so a retry can confirm it instead of proposing it twice. */
837
+ async function findPendingSafeTransaction(chainId, safe, currentNonce, call, options = {}) {
838
+ const pending = await listPendingSafeTransactions(chainId, safe, currentNonce, options);
839
+ return pending.find((tx) => safeTransactionMatchesCall(tx, call)) ?? null;
840
+ }
841
+ /**
842
+ * The service's record of the proposal `safeTxHash`, authenticated: it must
843
+ * belong to `safe` and its fields must hash to `safeTxHash`.
844
+ */
845
+ async function readSafeTransaction(chainId, safe, safeTxHash, options = {}) {
846
+ const base = serviceOrThrow(chainId);
847
+ const address = requireSafe(safe);
848
+ if (!(0, untrusted_js_1.isBytes32)(safeTxHash)) {
849
+ throw new Error(`Invalid Safe transaction hash: ${String(safeTxHash)}.`);
850
+ }
851
+ const response = await serviceFetch(`${base}/api/v1/multisig-transactions/${safeTxHash}/`, { headers: serviceHeaders() }, options);
852
+ if (!response.ok) {
853
+ throw new Error(`Safe's transaction service answered ${response.status} for proposal ${safeTxHash} on chain ${chainId}. Try again when it is available.`);
854
+ }
855
+ const record = (await response.json());
856
+ try {
857
+ if (record?.safe === undefined || record.safe === null) {
858
+ throw new Error("The record names no Safe.");
859
+ }
860
+ canonicalSafeTxHash(chainId, address, record, safeTxHash);
861
+ }
862
+ catch (error) {
863
+ throw new Error(`Safe's record of proposal ${safeTxHash} does not match Safe ${address} on chain ${chainId}. ${error.message}`);
864
+ }
865
+ return record;
866
+ }
867
+ async function postToService(url, body, what, options) {
868
+ const response = await serviceFetch(url, {
869
+ method: "POST",
870
+ headers: serviceHeaders(true),
871
+ body: JSON.stringify(body),
872
+ }, options);
873
+ if (!response.ok && response.status !== 201) {
874
+ throw new Error(`Safe's transaction service refused ${what} (${response.status})${await serviceDetail(response)}`);
875
+ }
876
+ }
877
+ function requireSignature(signature) {
878
+ if (!(0, untrusted_js_1.isHexBytes)(signature) || signature.length < 132) {
879
+ throw new Error(`Invalid Safe signature: ${String(signature)}.`);
880
+ }
881
+ return signature;
882
+ }
883
+ /**
884
+ * Queue `tx`, signed by `sender`, with the transaction service so the Safe's
885
+ * other owners can confirm and execute it. The body is built from the exact
886
+ * fields, checksummed as the service requires, and the hash is recomputed,
887
+ * never taken from the record. Returns the safeTxHash.
888
+ */
889
+ async function proposeSafeTransaction(chainId, safe, tx, { sender, signature, origin, }, options = {}) {
890
+ const base = serviceOrThrow(chainId);
891
+ const address = requireSafe(safe);
892
+ const safeTxHash = canonicalSafeTxHash(chainId, address, tx);
893
+ const message = safeTransactionMessage(tx);
894
+ if (typeof origin !== "string" || !origin.trim()) {
895
+ throw new Error("A Safe proposal needs an origin naming the app.");
896
+ }
897
+ await postToService(`${base}/api/v1/safes/${address}/multisig-transactions/`, {
898
+ to: message.to,
899
+ value: message.value.toString(),
900
+ data: message.data,
901
+ operation: message.operation,
902
+ safeTxGas: message.safeTxGas.toString(),
903
+ baseGas: message.baseGas.toString(),
904
+ gasPrice: message.gasPrice.toString(),
905
+ gasToken: message.gasToken,
906
+ refundReceiver: message.refundReceiver,
907
+ nonce: message.nonce.toString(),
908
+ contractTransactionHash: safeTxHash,
909
+ sender: fieldAddress("sender", sender),
910
+ signature: requireSignature(signature),
911
+ origin,
912
+ }, `the proposal ${safeTxHash} for Safe ${address} on chain ${chainId}`, options);
913
+ return safeTxHash;
914
+ }
915
+ /**
916
+ * Add an owner's signature to the queued `tx`. The confirmation is posted to
917
+ * the hash of `tx`'s exact fields, never to a hash the record advertises.
918
+ */
919
+ async function submitSafeConfirmation(chainId, safe, tx, signature, options = {}) {
920
+ const base = serviceOrThrow(chainId);
921
+ const safeTxHash = canonicalSafeTxHash(chainId, safe, tx);
922
+ await postToService(`${base}/api/v1/multisig-transactions/${safeTxHash}/confirmations/`, { signature: requireSignature(signature) }, `the confirmation of ${safeTxHash} on chain ${chainId}`, options);
923
+ }
924
+ /** The most Safes listed for one owner on one chain; the rest are not read. */
925
+ const MAX_OWNED_SAFES = 200;
926
+ /**
927
+ * Every Safe `owner` signs for on `chainIds`, from each chain's transaction
928
+ * service, at most 200 per chain. A chain without a service, a failed request
929
+ * or a malformed answer contributes nothing; listed Safes are checksummed and
930
+ * others dropped.
931
+ */
932
+ async function fetchSafesOwnedBy(owner, chainIds, options = {}) {
933
+ if (typeof owner !== "string" || !(0, viem_1.isAddress)(owner))
934
+ return [];
935
+ const account = (0, viem_1.getAddress)(owner);
936
+ const perChain = await Promise.all(chainIds.map(async (chainId) => {
937
+ const base = safeServiceBase(chainId);
938
+ if (!base)
939
+ return [];
940
+ try {
941
+ const response = await serviceFetch(`${base}/api/v1/owners/${account}/safes/`, { headers: serviceHeaders() }, options);
942
+ if (!response.ok)
943
+ return [];
944
+ const body = (await response.json());
945
+ return Array.isArray(body?.safes)
946
+ ? body.safes
947
+ .slice(0, MAX_OWNED_SAFES)
948
+ .flatMap((safe) => typeof safe === "string" && (0, viem_1.isAddress)(safe)
949
+ ? [{ chainId, safe: (0, viem_1.getAddress)(safe) }]
950
+ : [])
951
+ : [];
952
+ }
953
+ catch {
954
+ return [];
955
+ }
956
+ }));
957
+ return perChain.flat();
958
+ }
959
+ /** The source chain's service URL for a Safe's creation record, or null. */
960
+ function safeCreationUrl(chainId, safe) {
961
+ const base = safeServiceBase(chainId);
962
+ if (!base || typeof safe !== "string" || !(0, viem_1.isAddress)(safe))
963
+ return null;
964
+ return `${base}/api/v1/safes/${(0, viem_1.getAddress)(safe)}/creation/`;
965
+ }
966
+ /**
967
+ * A service creation record read strictly: the factory and singleton as
968
+ * addresses of one recognized Safe release, whole-hex setup data with at
969
+ * least a selector, and a decimal uint256 salt. Null for anything else.
970
+ */
971
+ function parseSafeCreationPayload(payload) {
972
+ if (!payload || typeof payload !== "object")
973
+ return null;
974
+ const record = payload;
975
+ const { factoryAddress, masterCopy, setupData, saltNonce } = record;
976
+ if (typeof factoryAddress !== "string" ||
977
+ typeof masterCopy !== "string" ||
978
+ !(0, viem_1.isAddress)(factoryAddress) ||
979
+ !(0, viem_1.isAddress)(masterCopy) ||
980
+ !(0, untrusted_js_1.isHexBytes)(setupData) ||
981
+ setupData.length < 10 ||
982
+ typeof saltNonce !== "string" ||
983
+ !/^\d+$/.test(saltNonce) ||
984
+ (0, untrusted_js_1.uint256)(saltNonce) === null ||
985
+ !(0, safe_js_1.isRecognizedSafeDeployment)(factoryAddress, masterCopy)) {
986
+ return null;
987
+ }
988
+ return {
989
+ factory: (0, viem_1.getAddress)(factoryAddress),
990
+ singleton: (0, viem_1.getAddress)(masterCopy),
991
+ initializer: setupData,
992
+ saltNonce: BigInt(saltNonce),
993
+ };
994
+ }
995
+ /**
996
+ * The Safe's creation record from its source chain's service only: a record
997
+ * for the same address from another chain could replay unrelated setup data.
998
+ * Null when the chain has no service, the request fails, or the record is
999
+ * not {@link parseSafeCreationPayload strictly valid}.
1000
+ */
1001
+ async function fetchSafeCreation(safe, sourceChainId, options = {}) {
1002
+ const url = safeCreationUrl(sourceChainId, safe);
1003
+ if (!url)
1004
+ return null;
1005
+ try {
1006
+ const response = await serviceFetch(url, { headers: serviceHeaders() }, options);
1007
+ return response.ok ? parseSafeCreationPayload(await response.json()) : null;
1008
+ }
1009
+ catch {
1010
+ return null;
1011
+ }
1012
+ }
175
1013
  //# sourceMappingURL=safeService.js.map