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