upshift-config 0.5.14

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 (316) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +349 -0
  3. package/lib/abis/AddressResolver.d.ts +28 -0
  4. package/lib/abis/AddressResolver.js +23 -0
  5. package/lib/abis/ChainlinkV3.d.ts +87 -0
  6. package/lib/abis/ChainlinkV3.js +117 -0
  7. package/lib/abis/ERC20.d.ts +168 -0
  8. package/lib/abis/ERC20.js +226 -0
  9. package/lib/abis/ERC20_Bytes32.d.ts +139 -0
  10. package/lib/abis/ERC20_Bytes32.js +196 -0
  11. package/lib/abis/ERC4626.d.ts +364 -0
  12. package/lib/abis/ERC4626.js +507 -0
  13. package/lib/abis/ERC721.d.ts +231 -0
  14. package/lib/abis/ERC721.js +321 -0
  15. package/lib/abis/FeeOracle.d.ts +120 -0
  16. package/lib/abis/FeeOracle.js +162 -0
  17. package/lib/abis/LendingPool.d.ts +1393 -0
  18. package/lib/abis/LendingPool.js +1807 -0
  19. package/lib/abis/LendingPoolV2.d.ts +1413 -0
  20. package/lib/abis/LendingPoolV2.js +1833 -0
  21. package/lib/abis/LendingPoolV3.d.ts +1677 -0
  22. package/lib/abis/LendingPoolV3.js +1160 -0
  23. package/lib/abis/Loan.d.ts +837 -0
  24. package/lib/abis/Loan.js +1080 -0
  25. package/lib/abis/MultiAssetNativeDepositWrapper.d.ts +137 -0
  26. package/lib/abis/MultiAssetNativeDepositWrapper.js +125 -0
  27. package/lib/abis/Multicall3.d.ts +30 -0
  28. package/lib/abis/Multicall3.js +97 -0
  29. package/lib/abis/OFT.d.ts +116 -0
  30. package/lib/abis/OFT.js +85 -0
  31. package/lib/abis/PoolAdapter.d.ts +36 -0
  32. package/lib/abis/PoolAdapter.js +51 -0
  33. package/lib/abis/RewardDistributor.d.ts +267 -0
  34. package/lib/abis/RewardDistributor.js +352 -0
  35. package/lib/abis/RwaRedeemSubaccount.d.ts +747 -0
  36. package/lib/abis/RwaRedeemSubaccount.js +548 -0
  37. package/lib/abis/SmartAccount.d.ts +17 -0
  38. package/lib/abis/SmartAccount.js +19 -0
  39. package/lib/abis/SwapRouter.d.ts +1043 -0
  40. package/lib/abis/SwapRouter.js +740 -0
  41. package/lib/abis/TextResolver.d.ts +16 -0
  42. package/lib/abis/TextResolver.js +16 -0
  43. package/lib/abis/TokenizedVaultV2.d.ts +1364 -0
  44. package/lib/abis/TokenizedVaultV2.js +1041 -0
  45. package/lib/abis/TokenizedVaultV2DepositWithPermit.d.ts +1456 -0
  46. package/lib/abis/TokenizedVaultV2DepositWithPermit.js +1878 -0
  47. package/lib/abis/TokenizedVaultV2Receipt.d.ts +1568 -0
  48. package/lib/abis/TokenizedVaultV2Receipt.js +1061 -0
  49. package/lib/abis/TokenizedVaultV2SenderAllocationWhitelist.d.ts +454 -0
  50. package/lib/abis/TokenizedVaultV2SenderAllocationWhitelist.js +327 -0
  51. package/lib/abis/TokenizedVaultV2WhitelistedAllocation.d.ts +1466 -0
  52. package/lib/abis/TokenizedVaultV2WhitelistedAllocation.js +1092 -0
  53. package/lib/abis/TokenizedVaultV2WhitelistedAssets.d.ts +274 -0
  54. package/lib/abis/TokenizedVaultV2WhitelistedAssets.js +167 -0
  55. package/lib/abis/UniversalResolverResolve.d.ts +69 -0
  56. package/lib/abis/UniversalResolverResolve.js +35 -0
  57. package/lib/abis/UniversalSignatureValidator.d.ts +17 -0
  58. package/lib/abis/UniversalSignatureValidator.js +30 -0
  59. package/lib/abis/WrapperAdapter.d.ts +71 -0
  60. package/lib/abis/WrapperAdapter.js +77 -0
  61. package/lib/abis/index.d.ts +34 -0
  62. package/lib/abis/index.js +51 -0
  63. package/lib/adapters/evm/getters.d.ts +19 -0
  64. package/lib/adapters/evm/getters.js +209 -0
  65. package/lib/adapters/evm/index.d.ts +353 -0
  66. package/lib/adapters/evm/index.js +434 -0
  67. package/lib/adapters/evm/utils.d.ts +8 -0
  68. package/lib/adapters/evm/utils.js +51 -0
  69. package/lib/adapters/solana/constants.d.ts +33 -0
  70. package/lib/adapters/solana/constants.js +52 -0
  71. package/lib/adapters/solana/getters.d.ts +11 -0
  72. package/lib/adapters/solana/getters.js +165 -0
  73. package/lib/adapters/solana/idl/vault-idl.d.ts +272 -0
  74. package/lib/adapters/solana/idl/vault-idl.js +1084 -0
  75. package/lib/adapters/solana/index.d.ts +233 -0
  76. package/lib/adapters/solana/index.js +291 -0
  77. package/lib/adapters/solana/types.d.ts +67 -0
  78. package/lib/adapters/solana/types.js +3 -0
  79. package/lib/adapters/solana/utils.d.ts +141 -0
  80. package/lib/adapters/solana/utils.js +595 -0
  81. package/lib/adapters/solana/vault.actions.d.ts +57 -0
  82. package/lib/adapters/solana/vault.actions.js +379 -0
  83. package/lib/adapters/stellar/actions.d.ts +28 -0
  84. package/lib/adapters/stellar/actions.js +77 -0
  85. package/lib/adapters/stellar/constants.d.ts +43 -0
  86. package/lib/adapters/stellar/constants.js +53 -0
  87. package/lib/adapters/stellar/getters.d.ts +68 -0
  88. package/lib/adapters/stellar/getters.js +290 -0
  89. package/lib/adapters/stellar/index.d.ts +114 -0
  90. package/lib/adapters/stellar/index.js +175 -0
  91. package/lib/adapters/stellar/soroban.d.ts +123 -0
  92. package/lib/adapters/stellar/soroban.js +613 -0
  93. package/lib/adapters/stellar/submit.d.ts +34 -0
  94. package/lib/adapters/stellar/submit.js +149 -0
  95. package/lib/adapters/stellar/types.d.ts +58 -0
  96. package/lib/adapters/stellar/types.js +6 -0
  97. package/lib/adapters/stellar/utils.d.ts +24 -0
  98. package/lib/adapters/stellar/utils.js +34 -0
  99. package/lib/adapters/sui/constants.d.ts +14 -0
  100. package/lib/adapters/sui/constants.js +29 -0
  101. package/lib/adapters/sui/getters.d.ts +9 -0
  102. package/lib/adapters/sui/getters.js +60 -0
  103. package/lib/adapters/sui/index.d.ts +45 -0
  104. package/lib/adapters/sui/index.js +101 -0
  105. package/lib/adapters/sui/transformer.d.ts +10 -0
  106. package/lib/adapters/sui/transformer.js +107 -0
  107. package/lib/adapters/sui/types.d.ts +66 -0
  108. package/lib/adapters/sui/types.js +3 -0
  109. package/lib/adapters/sui/utils.d.ts +10 -0
  110. package/lib/adapters/sui/utils.js +29 -0
  111. package/lib/core/analytics/chain-name.d.ts +9 -0
  112. package/lib/core/analytics/chain-name.js +34 -0
  113. package/lib/core/analytics/constants.d.ts +5 -0
  114. package/lib/core/analytics/constants.js +9 -0
  115. package/lib/core/analytics/env.d.ts +29 -0
  116. package/lib/core/analytics/env.js +59 -0
  117. package/lib/core/analytics/index.d.ts +36 -0
  118. package/lib/core/analytics/index.js +79 -0
  119. package/lib/core/analytics/instrumentation.d.ts +29 -0
  120. package/lib/core/analytics/instrumentation.js +277 -0
  121. package/lib/core/analytics/method-taxonomy.d.ts +19 -0
  122. package/lib/core/analytics/method-taxonomy.js +128 -0
  123. package/lib/core/analytics/metrics.d.ts +33 -0
  124. package/lib/core/analytics/metrics.js +116 -0
  125. package/lib/core/analytics/sanitize.d.ts +44 -0
  126. package/lib/core/analytics/sanitize.js +260 -0
  127. package/lib/core/analytics/sentry-runtime.d.ts +15 -0
  128. package/lib/core/analytics/sentry-runtime.js +97 -0
  129. package/lib/core/analytics/sentry.d.ts +67 -0
  130. package/lib/core/analytics/sentry.js +613 -0
  131. package/lib/core/analytics/types.d.ts +48 -0
  132. package/lib/core/analytics/types.js +3 -0
  133. package/lib/core/analytics/user-identity.d.ts +41 -0
  134. package/lib/core/analytics/user-identity.js +141 -0
  135. package/lib/core/analytics/version.d.ts +6 -0
  136. package/lib/core/analytics/version.js +10 -0
  137. package/lib/core/attribution.d.ts +111 -0
  138. package/lib/core/attribution.js +142 -0
  139. package/lib/core/auth/index.d.ts +1 -0
  140. package/lib/core/auth/index.js +18 -0
  141. package/lib/core/auth/verify.d.ts +2 -0
  142. package/lib/core/auth/verify.js +31 -0
  143. package/lib/core/base.class.d.ts +152 -0
  144. package/lib/core/base.class.js +172 -0
  145. package/lib/core/cache.d.ts +9 -0
  146. package/lib/core/cache.js +31 -0
  147. package/lib/core/constants/adapters.d.ts +103 -0
  148. package/lib/core/constants/adapters.js +180 -0
  149. package/lib/core/constants/core.d.ts +133 -0
  150. package/lib/core/constants/core.js +209 -0
  151. package/lib/core/constants/swap-router.d.ts +150 -0
  152. package/lib/core/constants/swap-router.js +169 -0
  153. package/lib/core/constants/vaults.d.ts +93 -0
  154. package/lib/core/constants/vaults.js +273 -0
  155. package/lib/core/constants/web3.d.ts +91 -0
  156. package/lib/core/constants/web3.js +229 -0
  157. package/lib/core/errors/index.d.ts +114 -0
  158. package/lib/core/errors/index.js +183 -0
  159. package/lib/core/fetcher.d.ts +198 -0
  160. package/lib/core/fetcher.js +903 -0
  161. package/lib/core/helpers/adapters.d.ts +13 -0
  162. package/lib/core/helpers/adapters.js +39 -0
  163. package/lib/core/helpers/chain-address.d.ts +13 -0
  164. package/lib/core/helpers/chain-address.js +47 -0
  165. package/lib/core/helpers/chain-error.d.ts +207 -0
  166. package/lib/core/helpers/chain-error.js +682 -0
  167. package/lib/core/helpers/chain-support.d.ts +80 -0
  168. package/lib/core/helpers/chain-support.js +115 -0
  169. package/lib/core/helpers/core.d.ts +68 -0
  170. package/lib/core/helpers/core.js +285 -0
  171. package/lib/core/helpers/explorer-link.d.ts +16 -0
  172. package/lib/core/helpers/explorer-link.js +26 -0
  173. package/lib/core/helpers/multicall.d.ts +68 -0
  174. package/lib/core/helpers/multicall.js +103 -0
  175. package/lib/core/helpers/revert-decode.d.ts +248 -0
  176. package/lib/core/helpers/revert-decode.js +515 -0
  177. package/lib/core/helpers/signer.d.ts +52 -0
  178. package/lib/core/helpers/signer.js +145 -0
  179. package/lib/core/helpers/swap-router.d.ts +211 -0
  180. package/lib/core/helpers/swap-router.js +480 -0
  181. package/lib/core/helpers/vault-version.d.ts +23 -0
  182. package/lib/core/helpers/vault-version.js +75 -0
  183. package/lib/core/helpers/vaults.d.ts +89 -0
  184. package/lib/core/helpers/vaults.js +235 -0
  185. package/lib/core/helpers/web3.d.ts +353 -0
  186. package/lib/core/helpers/web3.js +992 -0
  187. package/lib/core/index.d.ts +23 -0
  188. package/lib/core/index.js +40 -0
  189. package/lib/core/logger/index.d.ts +98 -0
  190. package/lib/core/logger/index.js +144 -0
  191. package/lib/core/logger/slack.d.ts +16 -0
  192. package/lib/core/logger/slack.js +57 -0
  193. package/lib/core/vault-metadata.d.ts +12 -0
  194. package/lib/core/vault-metadata.js +42 -0
  195. package/lib/core/version-check.d.ts +58 -0
  196. package/lib/core/version-check.js +182 -0
  197. package/lib/evm/index.d.ts +2 -0
  198. package/lib/evm/index.js +19 -0
  199. package/lib/evm/methods/crossChainVault.d.ts +128 -0
  200. package/lib/evm/methods/crossChainVault.js +853 -0
  201. package/lib/evm/methods/crossChainVaultRegistry.d.ts +93 -0
  202. package/lib/evm/methods/crossChainVaultRegistry.js +240 -0
  203. package/lib/evm/methods/index.d.ts +2 -0
  204. package/lib/evm/methods/index.js +19 -0
  205. package/lib/evm/types/crossChain.d.ts +363 -0
  206. package/lib/evm/types/crossChain.js +20 -0
  207. package/lib/evm/types/index.d.ts +1 -0
  208. package/lib/evm/types/index.js +18 -0
  209. package/lib/index.d.ts +30 -0
  210. package/lib/index.js +52 -0
  211. package/lib/main.d.ts +527 -0
  212. package/lib/main.js +601 -0
  213. package/lib/modules/api/fetcher.d.ts +82 -0
  214. package/lib/modules/api/fetcher.js +150 -0
  215. package/lib/modules/api/index.d.ts +1 -0
  216. package/lib/modules/api/index.js +6 -0
  217. package/lib/modules/api/main.d.ts +313 -0
  218. package/lib/modules/api/main.js +479 -0
  219. package/lib/modules/sub-accounts/fetcher.d.ts +53 -0
  220. package/lib/modules/sub-accounts/fetcher.js +120 -0
  221. package/lib/modules/sub-accounts/index.d.ts +2 -0
  222. package/lib/modules/sub-accounts/index.js +19 -0
  223. package/lib/modules/sub-accounts/main.d.ts +243 -0
  224. package/lib/modules/sub-accounts/main.js +205 -0
  225. package/lib/modules/sub-accounts/utils.d.ts +106 -0
  226. package/lib/modules/sub-accounts/utils.js +112 -0
  227. package/lib/modules/vaults/adapter.helpers.d.ts +64 -0
  228. package/lib/modules/vaults/adapter.helpers.js +184 -0
  229. package/lib/modules/vaults/fetcher.d.ts +147 -0
  230. package/lib/modules/vaults/fetcher.js +368 -0
  231. package/lib/modules/vaults/getters.d.ts +570 -0
  232. package/lib/modules/vaults/getters.js +3051 -0
  233. package/lib/modules/vaults/index.d.ts +20 -0
  234. package/lib/modules/vaults/index.js +44 -0
  235. package/lib/modules/vaults/main.d.ts +601 -0
  236. package/lib/modules/vaults/main.js +1623 -0
  237. package/lib/modules/vaults/prefetch.d.ts +65 -0
  238. package/lib/modules/vaults/prefetch.js +120 -0
  239. package/lib/modules/vaults/read.actions.d.ts +225 -0
  240. package/lib/modules/vaults/read.actions.js +596 -0
  241. package/lib/modules/vaults/types.d.ts +71 -0
  242. package/lib/modules/vaults/types.js +3 -0
  243. package/lib/modules/vaults/utils/call-data-decoder.d.ts +61 -0
  244. package/lib/modules/vaults/utils/call-data-decoder.js +194 -0
  245. package/lib/modules/vaults/utils/date-utils.d.ts +50 -0
  246. package/lib/modules/vaults/utils/date-utils.js +84 -0
  247. package/lib/modules/vaults/utils.d.ts +140 -0
  248. package/lib/modules/vaults/utils.js +799 -0
  249. package/lib/modules/vaults/write.actions.d.ts +529 -0
  250. package/lib/modules/vaults/write.actions.js +1749 -0
  251. package/lib/polyfills.d.ts +1 -0
  252. package/lib/polyfills.js +12 -0
  253. package/lib/sdk.d.ts +26721 -0
  254. package/lib/services/coingecko/fetcher.d.ts +15 -0
  255. package/lib/services/coingecko/fetcher.js +67 -0
  256. package/lib/services/coingecko/index.d.ts +2 -0
  257. package/lib/services/coingecko/index.js +19 -0
  258. package/lib/services/coingecko/utils.d.ts +1 -0
  259. package/lib/services/coingecko/utils.js +24 -0
  260. package/lib/services/debank/fetcher.d.ts +126 -0
  261. package/lib/services/debank/fetcher.js +47 -0
  262. package/lib/services/debank/index.d.ts +2 -0
  263. package/lib/services/debank/index.js +19 -0
  264. package/lib/services/debank/utils.d.ts +38 -0
  265. package/lib/services/debank/utils.js +297 -0
  266. package/lib/services/layerzero/deposits.d.ts +49 -0
  267. package/lib/services/layerzero/deposits.js +166 -0
  268. package/lib/services/layerzero/redeems.d.ts +20 -0
  269. package/lib/services/layerzero/redeems.js +92 -0
  270. package/lib/services/layerzero/utils.d.ts +9 -0
  271. package/lib/services/layerzero/utils.js +22 -0
  272. package/lib/services/octavfi/fetcher.d.ts +9 -0
  273. package/lib/services/octavfi/fetcher.js +103 -0
  274. package/lib/services/octavfi/index.d.ts +3 -0
  275. package/lib/services/octavfi/index.js +20 -0
  276. package/lib/services/octavfi/types.d.ts +34 -0
  277. package/lib/services/octavfi/types.js +3 -0
  278. package/lib/services/octavfi/utils.d.ts +16 -0
  279. package/lib/services/octavfi/utils.js +212 -0
  280. package/lib/services/subgraph/fetcher.d.ts +2 -0
  281. package/lib/services/subgraph/fetcher.js +61 -0
  282. package/lib/services/subgraph/index.d.ts +2 -0
  283. package/lib/services/subgraph/index.js +19 -0
  284. package/lib/services/subgraph/schema.d.ts +45 -0
  285. package/lib/services/subgraph/schema.js +75 -0
  286. package/lib/services/subgraph/vaults.d.ts +25 -0
  287. package/lib/services/subgraph/vaults.js +1106 -0
  288. package/lib/services/swap-quotes/index.d.ts +105 -0
  289. package/lib/services/swap-quotes/index.js +77 -0
  290. package/lib/services/swap-quotes/paraswap.d.ts +51 -0
  291. package/lib/services/swap-quotes/paraswap.js +138 -0
  292. package/lib/types/api.d.ts +280 -0
  293. package/lib/types/api.js +3 -0
  294. package/lib/types/index.d.ts +12 -0
  295. package/lib/types/index.js +28 -0
  296. package/lib/types/points.d.ts +28 -0
  297. package/lib/types/points.js +3 -0
  298. package/lib/types/pools.d.ts +144 -0
  299. package/lib/types/pools.js +3 -0
  300. package/lib/types/staking.d.ts +28 -0
  301. package/lib/types/staking.js +3 -0
  302. package/lib/types/sub-accounts.d.ts +140 -0
  303. package/lib/types/sub-accounts.js +3 -0
  304. package/lib/types/subgraph.d.ts +74 -0
  305. package/lib/types/subgraph.js +3 -0
  306. package/lib/types/typed-contract.d.ts +102 -0
  307. package/lib/types/typed-contract.js +3 -0
  308. package/lib/types/user.d.ts +1 -0
  309. package/lib/types/user.js +3 -0
  310. package/lib/types/vaults.d.ts +714 -0
  311. package/lib/types/vaults.js +24 -0
  312. package/lib/types/web3.d.ts +51 -0
  313. package/lib/types/web3.js +14 -0
  314. package/lib/types/webserver.d.ts +698 -0
  315. package/lib/types/webserver.js +3 -0
  316. package/package.json +78 -0
@@ -0,0 +1,1749 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.safeBigInt = safeBigInt;
4
+ exports.resolveDepositTokenDecimals = resolveDepositTokenDecimals;
5
+ exports.resolveSpender = resolveSpender;
6
+ exports.validateAmountPrecision = validateAmountPrecision;
7
+ exports.isNonceParsing = isNonceParsing;
8
+ exports.safeWaitForTx = safeWaitForTx;
9
+ exports.safeSendTx = safeSendTx;
10
+ exports.tryRecoverTxHash = tryRecoverTxHash;
11
+ exports.vaultApprove = vaultApprove;
12
+ exports.approve = approve;
13
+ exports.vaultDeposit = vaultDeposit;
14
+ exports.vaultRequestRedeem = vaultRequestRedeem;
15
+ exports.vaultRedeem = vaultRedeem;
16
+ exports.depositNative = depositNative;
17
+ exports.rwaRedeemAsset = rwaRedeemAsset;
18
+ exports.swapAndDeposit = swapAndDeposit;
19
+ exports.depositViaSwapRouter = depositViaSwapRouter;
20
+ exports.depositNativeViaSwapRouter = depositNativeViaSwapRouter;
21
+ exports.swapRouterDeposit = swapRouterDeposit;
22
+ const ethers_1 = require("ethers");
23
+ const abis_1 = require("../../abis");
24
+ const core_1 = require("../../core");
25
+ const swap_quotes_1 = require("../../services/swap-quotes");
26
+ const adapter_helpers_1 = require("./adapter.helpers");
27
+ const utils_1 = require("./utils");
28
+ // Treat unparseable RPC payloads (bare "0x", etc.) as 0n with a warn log —
29
+ // the defensive default is "approve again," and the log surfaces flaky RPCs
30
+ // before they cause silent gas waste from spurious approvals.
31
+ /** @internal */
32
+ function safeBigInt(value, context = 'safeBigInt') {
33
+ if (value === null || value === undefined)
34
+ return 0n;
35
+ const s = String(value);
36
+ if (s === '' || s === '0x') {
37
+ if (s === '0x') {
38
+ core_1.Logger.log.warn(context, 'RPC returned bare "0x"; treating as 0', {
39
+ raw: s,
40
+ });
41
+ }
42
+ return 0n;
43
+ }
44
+ try {
45
+ return BigInt(s);
46
+ }
47
+ catch (err) {
48
+ core_1.Logger.log.warn(context, 'Could not parse RPC value as BigInt; treating as 0', { raw: s, error: String(err) });
49
+ return 0n;
50
+ }
51
+ }
52
+ // EVM-0/1 single-asset underlying deposits reuse vaultDecimals (vault and
53
+ // underlying decimals match under ERC-4626 inheritance), saving one RPC.
54
+ // Multi-asset / adapter / non-underlying paths must read from the deposit
55
+ // asset's own ERC-20 — using share decimals there silently misencodes amounts.
56
+ /** @internal */
57
+ async function resolveDepositTokenDecimals(args) {
58
+ const { actualDepositAsset, underlyingAsset, isNativeToken, isMultiAssetVault, vaultDecimals, readErc20Decimals, } = args;
59
+ if (isNativeToken)
60
+ return 18;
61
+ const isUnderlying = actualDepositAsset.toLowerCase() === underlyingAsset.toLowerCase();
62
+ if (isUnderlying && !isMultiAssetVault)
63
+ return vaultDecimals;
64
+ return readErc20Decimals(actualDepositAsset);
65
+ }
66
+ /** @internal */
67
+ function resolveSpender(args) {
68
+ if (args.isMultiAssetVault)
69
+ return args.target;
70
+ if (args.isAdapterDeposit && args.adapterWrapperAddress)
71
+ return args.adapterWrapperAddress;
72
+ return args.target;
73
+ }
74
+ // Reject JS-number amounts past Number.MAX_SAFE_INTEGER — any larger
75
+ // integer the caller passes is already wrong before we hit toNormalizedBn.
76
+ /** @internal */
77
+ function validateAmountPrecision(amount) {
78
+ if (typeof amount === 'string' || typeof amount === 'bigint')
79
+ return;
80
+ if (!Number.isFinite(amount)) {
81
+ throw new core_1.AugustValidationError('INVALID_INPUT', `amount must be finite, got ${amount}`);
82
+ }
83
+ if (amount < 0) {
84
+ throw new core_1.AugustValidationError('INVALID_INPUT', `amount must be non-negative, got ${amount}`);
85
+ }
86
+ if (Number.isInteger(amount) && !Number.isSafeInteger(amount)) {
87
+ throw new core_1.AugustValidationError('INVALID_INPUT', `amount ${amount} exceeds Number.MAX_SAFE_INTEGER; pass a string or bigint to preserve precision`);
88
+ }
89
+ }
90
+ // Tries tx.wait() first (preserves replacement detection & revert checks), then falls back to
91
+ // provider.waitForTransaction() for RPCs (e.g. Monad) that return malformed pending tx fields.
92
+ const SAFE_WAIT_TIMEOUT_MS = 120_000;
93
+ /** @internal */
94
+ function isNonceParsing(error) {
95
+ const code = error?.code;
96
+ const msg = String(error).toLowerCase();
97
+ return ((code === 'INVALID_ARGUMENT' || code === 'BAD_DATA') &&
98
+ msg.includes('nonce'));
99
+ }
100
+ /**
101
+ * Own-properties stamped onto an error describing a transaction that already
102
+ * reached the network. Prefixed to avoid colliding with the `hash` /
103
+ * `transaction` / `receipt` fields ethers puts on its own errors.
104
+ *
105
+ * These are an internal wire format between the tx-wait helpers and the write
106
+ * paths' catch blocks — consumers read the *sanitized* form
107
+ * (`txHash` / `broadcast` / `confirmationUnknown`) off `AugustSDKError.context`,
108
+ * never these.
109
+ * @internal
110
+ */
111
+ const BROADCAST_HASH_KEY = 'augustBroadcastTxHash';
112
+ /** @internal */
113
+ const BROADCAST_UNKNOWN_KEY = 'augustConfirmationUnknown';
114
+ /**
115
+ * Stamp "this transaction was broadcast" onto an error, in place.
116
+ *
117
+ * Mutating rather than wrapping is deliberate: callers up the stack compare
118
+ * error identity (and ethers' own typed fields matter for downstream
119
+ * classification), so replacing the error object would lose information.
120
+ *
121
+ * Annotating is strictly best-effort. A frozen or sealed error, or one with a
122
+ * getter-only property of the same name, must never turn a *recoverable
123
+ * transport failure* into a `TypeError` thrown from inside the error handler —
124
+ * that would be a strictly worse outcome than losing the hash. Each write is
125
+ * therefore attempted independently and failures are swallowed; the caller then
126
+ * simply reports the transaction as un-annotated (no `txHash` in the error
127
+ * context), which is the same conservative state as "never broadcast".
128
+ *
129
+ * @param error - The error about to be thrown. Non-objects pass through
130
+ * untouched — there is nowhere to put the marker.
131
+ * @param txHash - Hash of the transaction that reached the network.
132
+ * @param confirmationUnknown - `true` when the transaction is on the wire but
133
+ * its outcome could not be determined (transport retries exhausted, or the
134
+ * wait timed out). `false` when the chain gave a definitive answer, e.g. a
135
+ * receipt with `status === 0`.
136
+ * @returns The same error instance (identity preserved), for
137
+ * `throw markBroadcast(...)`.
138
+ * @internal
139
+ */
140
+ function markBroadcast(error, txHash, confirmationUnknown) {
141
+ if (!error || typeof error !== 'object')
142
+ return error;
143
+ // `defineProperty` with configurable/writable keeps the marker invisible to
144
+ // JSON.stringify and to spreads of the error, so it can't leak into a
145
+ // serialized crash report as a mystery field.
146
+ const stamp = (key, value) => {
147
+ try {
148
+ Object.defineProperty(error, key, {
149
+ value,
150
+ enumerable: false,
151
+ writable: true,
152
+ configurable: true,
153
+ });
154
+ }
155
+ catch {
156
+ // Frozen/sealed error, or a non-configurable same-named property. The
157
+ // hash is a nice-to-have; never let annotation failure mask the real
158
+ // transport error we are in the middle of reporting.
159
+ }
160
+ };
161
+ stamp(BROADCAST_HASH_KEY, txHash);
162
+ stamp(BROADCAST_UNKNOWN_KEY, confirmationUnknown);
163
+ return error;
164
+ }
165
+ /**
166
+ * Read back the markers {@link markBroadcast} stamped, if any.
167
+ *
168
+ * @param error - The caught value, of unknown type.
169
+ * @returns The recovered hash and confirmation state; both `undefined` when the
170
+ * error never passed through a tx-wait helper.
171
+ * @internal
172
+ */
173
+ function readBroadcastMarkers(error) {
174
+ if (!error || typeof error !== 'object')
175
+ return {};
176
+ const e = error;
177
+ const txHash = e[BROADCAST_HASH_KEY];
178
+ const confirmationUnknown = e[BROADCAST_UNKNOWN_KEY];
179
+ return {
180
+ txHash: typeof txHash === 'string' ? txHash : undefined,
181
+ confirmationUnknown: typeof confirmationUnknown === 'boolean'
182
+ ? confirmationUnknown
183
+ : undefined,
184
+ };
185
+ }
186
+ /**
187
+ * Build the broadcast half of a failed write's structured error context, for a
188
+ * path that holds its main transaction in a local variable.
189
+ *
190
+ * The distinction this encodes, and which consumers branch on:
191
+ *
192
+ * - **no `txHash` key at all** — the write never reached the network. Nothing
193
+ * is pending; it is safe to tell the user it failed and safe to retry.
194
+ * - **`txHash` + `broadcast: true` + `confirmationUnknown: true`** — the
195
+ * transaction *is* on-chain (or in the mempool) and only the confirmation was
196
+ * lost. Render a pending state against `txHash`; **do not** prompt a retry,
197
+ * that is exactly how users double-spend a redeem.
198
+ * - **`txHash` + `broadcast: true` + `confirmationUnknown: false`** — the
199
+ * transaction reached the chain and definitively failed (reverted). A real
200
+ * error, with a hash the user can inspect on an explorer.
201
+ *
202
+ * `tx` is the gate on purpose. Several write paths broadcast an inner approval
203
+ * before the main call; if that approval's wait fails, the caught error carries
204
+ * *its* marker. Falling back to the error's marker here would make
205
+ * "Request redeem failed" report the approve hash and claim the redeem is
206
+ * pending when it was never sent.
207
+ *
208
+ * @param tx - The path's main transaction, or `undefined`/`null` when it was
209
+ * never sent.
210
+ * @param error - The caught value, used only for the confirmation state.
211
+ * @returns A context fragment to spread into the thrown error's `context`.
212
+ * Empty when nothing was broadcast.
213
+ * @internal
214
+ */
215
+ function localTxBroadcastContext(tx, error) {
216
+ const txHash = tx?.hash;
217
+ if (!txHash)
218
+ return {};
219
+ const markers = readBroadcastMarkers(error);
220
+ return {
221
+ txHash,
222
+ broadcast: true,
223
+ // Only trust the marker when it describes *this* transaction.
224
+ confirmationUnknown: markers.txHash === txHash && markers.confirmationUnknown === true,
225
+ };
226
+ }
227
+ /**
228
+ * Same contract as {@link localTxBroadcastContext}, for write paths whose only
229
+ * transaction is sent through {@link safeSendTx} — there the hash never comes
230
+ * back to the caller on the failure path, so the error's own marker is the only
231
+ * source. Safe precisely because those paths send exactly one transaction, so
232
+ * the marker cannot belong to a different one.
233
+ *
234
+ * @param error - The caught value, of unknown type.
235
+ * @returns A context fragment to spread into the thrown error's `context`.
236
+ * Empty when nothing was broadcast.
237
+ * @internal
238
+ */
239
+ function errorTxBroadcastContext(error) {
240
+ const { txHash, confirmationUnknown } = readBroadcastMarkers(error);
241
+ if (!txHash)
242
+ return {};
243
+ return {
244
+ txHash,
245
+ broadcast: true,
246
+ confirmationUnknown: confirmationUnknown === true,
247
+ };
248
+ }
249
+ /**
250
+ * Await a broadcast transaction's receipt, surviving both malformed-nonce RPCs
251
+ * and transient transport faults while still reporting genuine on-chain
252
+ * failures.
253
+ *
254
+ * `tx.wait()` is tried first because it preserves ethers' replacement-tx
255
+ * detection and revert checks. Two recoverable failure modes are handled:
256
+ *
257
+ * 1. **Malformed nonce** ({@link isNonceParsing}) — Monad-style RPCs return
258
+ * `nonce: "undefined"` on the pending tx and ethers throws while parsing.
259
+ * One direct `provider.waitForTransaction()` recovers it (unchanged
260
+ * behaviour).
261
+ * 2. **Transient transport fault** ({@link isRetryableRpcError}) — the provider
262
+ * hiccups while polling `eth_getTransactionReceipt` and ethers surfaces
263
+ * `could not coalesce error (… "code": -32603 …)`. This used to propagate
264
+ * and make every write path report a **successfully mined** transaction as
265
+ * failed, driving users to retry into `ERC20InsufficientBalance`. The hash
266
+ * is already on the wire and receipt polling is idempotent, so we re-poll
267
+ * with bounded exponential backoff instead of rethrowing.
268
+ *
269
+ * Everything else — notably a real revert — is rethrown untouched.
270
+ *
271
+ * Every error this function throws is stamped by {@link markBroadcast}: by the
272
+ * time `tx.wait()` can fail, the transaction is already on the wire, so the
273
+ * caller must never report it as "never sent". See
274
+ * {@link localTxBroadcastContext} for how that reaches
275
+ * `AugustSDKError.context`.
276
+ *
277
+ * @param tx - The broadcast transaction response to await.
278
+ * @returns The mined receipt (never `null` on the fallback paths; `tx.wait()`
279
+ * itself may return `null` when the caller configured 0 confirmations).
280
+ * @throws {Error} `Transaction <hash> reverted on-chain` when the receipt
281
+ * reports `status === 0`. Marked `confirmationUnknown: false` — the chain
282
+ * answered.
283
+ * @throws {Error} `Transaction <hash> was not confirmed within <n>s` when the
284
+ * overall wait times out with no receipt. Marked
285
+ * `confirmationUnknown: true`.
286
+ * @throws The underlying transport error once receipt-poll retries are
287
+ * exhausted, marked `confirmationUnknown: true`.
288
+ * @internal
289
+ */
290
+ async function safeWaitForTx(tx) {
291
+ try {
292
+ return await tx.wait();
293
+ }
294
+ catch (error) {
295
+ const nonceParse = isNonceParsing(error);
296
+ // Not a shape we know how to recover from — surface it unchanged, but the
297
+ // tx is still on-chain (ethers reports a revert here), so keep the hash.
298
+ if (!nonceParse && !(0, core_1.isRetryableRpcError)(error)) {
299
+ throw markBroadcast(error, tx.hash, false);
300
+ }
301
+ const msg = error instanceof Error ? error.message : '';
302
+ core_1.Logger.log.warn(nonceParse
303
+ ? 'safeWaitForTx:fallback'
304
+ : 'safeWaitForTx:transport-fallback', msg, { hash: tx.hash });
305
+ // The nonce path keeps its original single-shot behaviour: the RPC is
306
+ // responsive, it just mis-serialized the pending tx. The transport path
307
+ // re-polls, because the provider itself is the thing that failed.
308
+ let receipt;
309
+ try {
310
+ receipt = nonceParse
311
+ ? await tx.provider.waitForTransaction(tx.hash, 1, SAFE_WAIT_TIMEOUT_MS)
312
+ : await (0, core_1.retryOnTransientRpc)('safeWaitForTx:transport-retry', () => tx.provider.waitForTransaction(tx.hash, 1, SAFE_WAIT_TIMEOUT_MS), { hash: tx.hash });
313
+ }
314
+ catch (pollError) {
315
+ // Retries exhausted. The transaction is on the wire and very likely
316
+ // mined; we simply could not read its receipt.
317
+ throw markBroadcast(pollError, tx.hash, true);
318
+ }
319
+ if (!receipt) {
320
+ throw markBroadcast(new Error(`Transaction ${tx.hash} was not confirmed within ${SAFE_WAIT_TIMEOUT_MS / 1000}s`), tx.hash, true);
321
+ }
322
+ if (receipt.status === 0) {
323
+ // Definitive: the chain executed it and it failed. Not pending.
324
+ throw markBroadcast(new Error(`Transaction ${tx.hash} reverted on-chain`), tx.hash, false);
325
+ }
326
+ return receipt;
327
+ }
328
+ }
329
+ /**
330
+ * Wraps an ethers contract call that returns a TransactionResponse.
331
+ * Some RPCs (e.g. Monad) return malformed pending tx fields like `nonce: "undefined"`,
332
+ * which causes ethers.js to throw during response parsing before safeWaitForTx runs.
333
+ * This wrapper catches that error, extracts the tx hash from the error context, and
334
+ * falls back to provider.waitForTransaction().
335
+ */
336
+ /** @internal */
337
+ async function safeSendTx(contractCall, provider, shouldWait) {
338
+ try {
339
+ const tx = await contractCall();
340
+ if (shouldWait)
341
+ await safeWaitForTx(tx);
342
+ return { tx, hash: tx.hash };
343
+ }
344
+ catch (error) {
345
+ if (!isNonceParsing(error))
346
+ throw error;
347
+ // The tx was sent but ethers couldn't parse the response.
348
+ // Try to extract the hash from the error payload.
349
+ const errStr = String(error);
350
+ const hashMatch = errStr.match(/"hash"\s*:\s*"(0x[0-9a-fA-F]{64})"/);
351
+ if (!hashMatch?.[1])
352
+ throw error;
353
+ const txHash = hashMatch[1];
354
+ core_1.Logger.log.warn('safeSendTx:nonce-fallback', { txHash });
355
+ if (shouldWait) {
356
+ const signerProvider = 'provider' in provider ? provider.provider : provider;
357
+ const receipt = await signerProvider.waitForTransaction(txHash, 1, SAFE_WAIT_TIMEOUT_MS);
358
+ if (!receipt) {
359
+ throw markBroadcast(new Error(`Transaction ${txHash} was not confirmed within ${SAFE_WAIT_TIMEOUT_MS / 1000}s`), txHash, true);
360
+ }
361
+ if (receipt.status === 0) {
362
+ throw markBroadcast(new Error(`Transaction ${txHash} reverted on-chain`), txHash, false);
363
+ }
364
+ }
365
+ return { tx: null, hash: txHash };
366
+ }
367
+ }
368
+ /**
369
+ * Shared nonce-recovery helper for catch blocks. When an RPC returns a
370
+ * malformed nonce and ethers throws, try to extract the tx hash from the
371
+ * error payload. Optionally waits for confirmation (with status check).
372
+ *
373
+ * Returns the recovered hash, or null if this error is not a nonce-parse error
374
+ * or if no hash could be extracted (caller should re-throw the original error).
375
+ */
376
+ /** @internal */
377
+ async function tryRecoverTxHash(error, signer, wait) {
378
+ if (!isNonceParsing(error))
379
+ return null;
380
+ const hashMatch = String(error).match(/"hash"\s*:\s*"(0x[0-9a-fA-F]{64})"/);
381
+ if (!hashMatch?.[1])
382
+ return null;
383
+ const txHash = hashMatch[1];
384
+ if (wait) {
385
+ const provider = 'provider' in signer ? signer.provider : signer;
386
+ const receipt = await provider.waitForTransaction(txHash, 1, SAFE_WAIT_TIMEOUT_MS);
387
+ if (!receipt) {
388
+ throw markBroadcast(new Error(`Transaction ${txHash} was not confirmed within ${SAFE_WAIT_TIMEOUT_MS / 1000}s`), txHash, true);
389
+ }
390
+ if (receipt.status === 0) {
391
+ throw markBroadcast(new Error(`Transaction ${txHash} reverted on-chain`), txHash, false);
392
+ }
393
+ }
394
+ return txHash;
395
+ }
396
+ /**
397
+ * Rethrow a caught write-path error as a typed {@link AugustValidationError}
398
+ * when it is the chain node reporting the sender can't afford the transaction's
399
+ * gas/fee (see {@link isInsufficientFundsError}).
400
+ *
401
+ * Why: for an underfunded account ethers surfaces an opaque `CALL_EXCEPTION` /
402
+ * "missing revert data" that gives the caller no stable way to tell "user needs
403
+ * gas" apart from a genuine on-chain failure. Converting it to the
404
+ * `ACCOUNT_NOT_FUNDED` code lets a consuming UI branch on `err.code` (e.g. show
405
+ * a "top up to cover network fees" prompt) instead of string-matching. Every
406
+ * write path shares this so the classification is identical across
407
+ * deposit/redeem/approve rather than drifting per call site.
408
+ *
409
+ * A no-op when `isInsufficient` is `false`, so callers fall through to their
410
+ * existing handling unchanged. The caller passes the already-computed
411
+ * classification (it also feeds the telemetry-demotion decision) so
412
+ * {@link isInsufficientFundsError} runs once per catch, not twice.
413
+ *
414
+ * @param isInsufficient - Result of {@link isInsufficientFundsError} for
415
+ * `error`, computed once by the caller and shared with the benign-log flag.
416
+ * @param operation - Human-readable label for the attempted write (e.g.
417
+ * `'Request redeem'`), used verbatim in the thrown error's message.
418
+ * @param error - The caught value, of unknown type, preserved as the thrown
419
+ * error's `cause`.
420
+ * @param context - Structured context attached to the thrown error for logging.
421
+ * @throws {AugustValidationError} with code `ACCOUNT_NOT_FUNDED` when
422
+ * `isInsufficient` is `true`.
423
+ */
424
+ function throwIfInsufficientFunds(isInsufficient, operation, error, context) {
425
+ if (!isInsufficient)
426
+ return;
427
+ throw new core_1.AugustValidationError('ACCOUNT_NOT_FUNDED', `${operation} failed: insufficient native balance to cover gas/network fees`, { cause: error, context });
428
+ }
429
+ async function approveCore(signer, options) {
430
+ const { wallet, target, wait, amount, depositAsset } = options;
431
+ const [goodWallet, goodPool] = [
432
+ (0, core_1.checkAddress)(wallet, console, 'wallet'),
433
+ (0, core_1.checkAddress)(target, console),
434
+ ];
435
+ if (!goodWallet || !goodPool) {
436
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', `vaultApprove: invalid ${!goodWallet ? 'wallet' : 'target'} address`);
437
+ }
438
+ if (amount === undefined || amount === null) {
439
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'vaultApprove: amount is required');
440
+ }
441
+ validateAmountPrecision(amount);
442
+ try {
443
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(target))?.[0];
444
+ const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
445
+ const adapterConfig = (0, core_1.getVaultAdapterConfig)(target);
446
+ const isMultiAssetVault = vaultVersion === 'evm-2';
447
+ const poolContract = isMultiAssetVault
448
+ ? (0, core_1.createContract)({
449
+ address: target,
450
+ provider: signer,
451
+ abi: abis_1.ABI_TOKENIZED_VAULT_V2,
452
+ })
453
+ : (0, core_1.createContract)({
454
+ address: target,
455
+ provider: signer,
456
+ abi: abis_1.ABI_LENDING_POOLS,
457
+ });
458
+ let underlyingAsset;
459
+ let vaultDecimals;
460
+ if (isMultiAssetVault) {
461
+ const [asset, receiptTokenAddr] = await Promise.all([
462
+ poolContract.asset(),
463
+ // Retried on transport blips only; a misroute still throws. See
464
+ // getReceiptTokenAddressOrThrow.
465
+ (0, core_1.getReceiptTokenAddressOrThrow)(signer, target, 'vaultApprove:receiptToken'),
466
+ ]);
467
+ underlyingAsset = asset;
468
+ vaultDecimals = await (0, core_1.getDecimalsOrThrow)(signer, receiptTokenAddr, 'vaultApprove:receiptDecimals');
469
+ }
470
+ else {
471
+ const [asset, rawDecimals] = await Promise.all([
472
+ poolContract.asset(),
473
+ (0, core_1.getDecimalsOrThrow)(signer, target, 'vaultApprove:poolDecimals'),
474
+ ]);
475
+ underlyingAsset = asset;
476
+ vaultDecimals = rawDecimals;
477
+ }
478
+ const actualDepositAsset = (depositAsset || underlyingAsset);
479
+ const isNativeToken = actualDepositAsset === ethers_1.ZeroAddress ||
480
+ actualDepositAsset === '0x0000000000000000000000000000000000000000';
481
+ // Native tokens travel as msg.value — no ERC-20 allowance to grant.
482
+ if (isNativeToken)
483
+ return { kind: 'native' };
484
+ const isAdapterDeposit = actualDepositAsset.toLowerCase() !== underlyingAsset.toLowerCase();
485
+ const spenderAddress = resolveSpender({
486
+ target,
487
+ isMultiAssetVault,
488
+ isAdapterDeposit,
489
+ adapterWrapperAddress: adapterConfig?.wrapperAddress,
490
+ });
491
+ const depositTokenDecimals = await resolveDepositTokenDecimals({
492
+ actualDepositAsset,
493
+ underlyingAsset,
494
+ isNativeToken: false,
495
+ isMultiAssetVault,
496
+ vaultDecimals,
497
+ readErc20Decimals: async (addr) => (0, core_1.getDecimalsOrThrow)(signer, addr, 'vaultApprove:depositTokenDecimals'),
498
+ });
499
+ const normalizedAmt = (0, core_1.toNormalizedBn)(amount, depositTokenDecimals);
500
+ const needed = BigInt(normalizedAmt.raw);
501
+ const tokenContract = (0, core_1.createContract)({
502
+ address: actualDepositAsset,
503
+ abi: abis_1.ABI_ERC20,
504
+ provider: signer,
505
+ });
506
+ const currentAllowance = await tokenContract.allowance(wallet, spenderAddress);
507
+ const existing = safeBigInt(currentAllowance, 'vaultApprove:allowance');
508
+ if (existing >= needed) {
509
+ core_1.Logger.log.info('approve:allowance-sufficient', {
510
+ target,
511
+ spender: spenderAddress,
512
+ amount,
513
+ });
514
+ return { kind: 'sufficient', existing };
515
+ }
516
+ const { hash: approvalHash } = await safeSendTx(() => tokenContract
517
+ .connect(signer)
518
+ .approve(spenderAddress, normalizedAmt.raw), signer, wait);
519
+ core_1.Logger.log.info('approve:tx_hash', approvalHash);
520
+ return { kind: 'sent', hash: approvalHash };
521
+ }
522
+ catch (e) {
523
+ if (e instanceof core_1.AugustSDKError)
524
+ throw e;
525
+ // Only one tx is sent on this path, so any broadcast marker on the error
526
+ // necessarily describes the approval itself.
527
+ const broadcast = errorTxBroadcastContext(e);
528
+ // A user declining the approval in their wallet is product behaviour, not
529
+ // an SDK fault — demote it to a breadcrumb so it doesn't bill as an issue.
530
+ const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
531
+ (0, core_1.logChainError)('vaultApprove', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
532
+ target,
533
+ amount,
534
+ ...broadcast,
535
+ });
536
+ throwIfInsufficientFunds(insufficientFunds, 'Approval', e, {
537
+ target,
538
+ amount,
539
+ ...broadcast,
540
+ });
541
+ throw new core_1.AugustSDKError('UNKNOWN', `Approval failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, amount, ...broadcast } });
542
+ }
543
+ }
544
+ /**
545
+ * Approve a vault (or the appropriate adapter wrapper) to spend a token
546
+ * ahead of a separate deposit call. Spender routing mirrors
547
+ * {@link vaultDeposit} (multi-asset → vault, adapter → wrapper, standard →
548
+ * vault). Returns `undefined` when the existing allowance already covers
549
+ * `amount` **or** when the deposit asset is native.
550
+ *
551
+ * @returns Transaction hash when an approval was sent, `undefined` when
552
+ * the existing allowance already covers `amount` or the deposit asset is
553
+ * native (no ERC-20 allowance applies). Use {@link approve} for a
554
+ * discriminated `ApproveResult` if you need to tell those two cases apart.
555
+ * @throws AugustValidationError on invalid wallet/target/amount.
556
+ *
557
+ * @example
558
+ * ```ts
559
+ * const hash = await augustSdk.evm.vaultApprove({
560
+ * target: '0x...', // vault contract address
561
+ * wallet: walletAddress,
562
+ * amount: '100', // 100 of the deposit token (UI units)
563
+ * wait: true, // wait for the approval to confirm
564
+ * });
565
+ * if (hash) console.log('approve tx', hash);
566
+ * // hash === undefined means the on-chain allowance already covers the
567
+ * // amount, or the deposit asset is native (msg.value, no allowance).
568
+ * ```
569
+ */
570
+ async function vaultApprove(signer, options) {
571
+ const result = await approveCore(signer, options);
572
+ return result.kind === 'sent' ? result.hash : undefined;
573
+ }
574
+ /**
575
+ * Same approval logic as {@link vaultApprove} but returns a discriminated
576
+ * union so callers can tell `sent` apart from `sufficient` and `native`
577
+ * without re-reading on-chain state. Preferred shape for new integrations.
578
+ *
579
+ * @example
580
+ * ```ts
581
+ * const r = await augustSdk.evm.approve({
582
+ * target: vaultAddress,
583
+ * wallet: walletAddress,
584
+ * amount: '100',
585
+ * wait: true,
586
+ * });
587
+ * switch (r.kind) {
588
+ * case 'sent':
589
+ * showToast('Approval submitted', r.hash);
590
+ * break;
591
+ * case 'sufficient':
592
+ * // r.existing is the existing on-chain allowance (raw bigint)
593
+ * break;
594
+ * case 'native':
595
+ * // No allowance needed; native token will travel as msg.value
596
+ * break;
597
+ * }
598
+ * ```
599
+ *
600
+ * @throws AugustValidationError on invalid wallet/target/amount.
601
+ * @throws AugustSDKError when the approval submission fails.
602
+ */
603
+ async function approve(signer, options) {
604
+ return approveCore(signer, options);
605
+ }
606
+ /**
607
+ * Deposit underlying token into the specified vault. Auto-routes to the
608
+ * correct contract path (single-asset ERC-4626, EVM-2 multi-asset,
609
+ * Paraswap adapter, native-token wrapper, depositWithPermit) based on the
610
+ * vault version and `depositAsset`.
611
+ *
612
+ * Amounts are encoded against the **deposit token's** decimals, not the
613
+ * vault's share decimals. The SDK reads the appropriate decimals via the
614
+ * vault metadata and the asset's ERC-20.
615
+ *
616
+ * Approval is handled automatically: if the existing allowance doesn't
617
+ * cover `amount`, an `approve` is sent and **always waited for** (regardless
618
+ * of the caller's `wait` flag) so the deposit tx can't be re-ordered ahead
619
+ * of the approval on Monad-style RPCs.
620
+ *
621
+ * @param signer ethers Signer / Wallet (or viem WalletClient via {@link setSigner})
622
+ * @param options Vault address, wallet, amount, optional `depositAsset` for
623
+ * adapter routes, optional `wait` to wait for the deposit receipt.
624
+ * @returns Deposit transaction hash.
625
+ * @throws AugustValidationError on invalid wallet/target/amount or missing
626
+ * adapter config when `depositAsset` differs from the vault underlying.
627
+ * @throws AugustSDKError when the deposit submission or on-chain call fails.
628
+ *
629
+ * @example
630
+ * ```ts
631
+ * const hash = await augustSdk.evm.vaultDeposit({
632
+ * target: '0xE9B725010A9E419412ed67d0fA5f3A5f40159D32', // vault
633
+ * wallet: walletAddress,
634
+ * amount: '100', // 100 USDC (UI units)
635
+ * wait: true, // wait for the deposit receipt
636
+ * });
637
+ * ```
638
+ *
639
+ * @example Adapter deposit (different deposit asset than the vault's underlying)
640
+ * ```ts
641
+ * await augustSdk.evm.vaultDeposit({
642
+ * target: vaultAddress,
643
+ * wallet: walletAddress,
644
+ * amount: '0.5',
645
+ * depositAsset: WETH_ADDRESS, // swap WETH -> vault underlying via the adapter
646
+ * chainId: 1,
647
+ * });
648
+ * ```
649
+ */
650
+ async function vaultDeposit(signer, options) {
651
+ const { wallet, target, wait, amount, depositAsset, chainId, poolName } = options;
652
+ const [goodWallet, goodPool] = [
653
+ (0, core_1.checkAddress)(wallet, console, 'wallet'),
654
+ (0, core_1.checkAddress)(target, console),
655
+ ];
656
+ if (!goodWallet || !goodPool) {
657
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', `vaultDeposit: invalid ${!goodWallet ? 'wallet' : 'target'} address`);
658
+ }
659
+ if (amount === undefined || amount === null) {
660
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'vaultDeposit: amount is required');
661
+ }
662
+ validateAmountPrecision(amount);
663
+ // Hoisted out of the try so the catch can tell "the deposit was broadcast and
664
+ // we lost the confirmation" from "the deposit never left". Any inner approval
665
+ // tx is deliberately NOT tracked here — see localTxBroadcastContext.
666
+ let depositTx;
667
+ try {
668
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(target))?.[0];
669
+ const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
670
+ const adapterConfig = (0, core_1.getVaultAdapterConfig)(target);
671
+ let poolContract;
672
+ let underlyingAsset;
673
+ let vaultDecimals;
674
+ if (vaultVersion === 'evm-2') {
675
+ poolContract = (0, core_1.createContract)({
676
+ address: target,
677
+ provider: signer,
678
+ abi: options?.isDepositWithPermit
679
+ ? abis_1.ABI_TOKENIZED_VAULT_V2_DEPOSIT_WITH_PERMIT
680
+ : abis_1.ABI_TOKENIZED_VAULT_V2,
681
+ });
682
+ const [asset, receiptTokenAddr] = await Promise.all([
683
+ poolContract.asset(),
684
+ // Retried on transport blips only; a misroute still throws. See
685
+ // getReceiptTokenAddressOrThrow.
686
+ (0, core_1.getReceiptTokenAddressOrThrow)(signer, target, 'vaultDeposit:receiptToken'),
687
+ ]);
688
+ underlyingAsset = asset;
689
+ vaultDecimals = await (0, core_1.getDecimalsOrThrow)(signer, receiptTokenAddr, 'vaultDeposit:receiptDecimals');
690
+ }
691
+ else {
692
+ poolContract = (0, core_1.createContract)({
693
+ address: target,
694
+ provider: signer,
695
+ abi: abis_1.ABI_LENDING_POOLS,
696
+ });
697
+ const [fetchedUnderlyingAsset, rawDecimals] = await Promise.all([
698
+ poolContract.asset(),
699
+ (0, core_1.getDecimalsOrThrow)(signer, target, 'vaultDeposit:poolDecimals'),
700
+ ]);
701
+ vaultDecimals = rawDecimals;
702
+ underlyingAsset = fetchedUnderlyingAsset;
703
+ }
704
+ const actualDepositAsset = depositAsset || underlyingAsset;
705
+ const isNativeToken = actualDepositAsset === ethers_1.ZeroAddress ||
706
+ actualDepositAsset === '0x0000000000000000000000000000000000000000';
707
+ const isAdapterDeposit = actualDepositAsset.toLowerCase() !== underlyingAsset.toLowerCase();
708
+ const isMultiAssetVault = vaultVersion === 'evm-2';
709
+ // vaultDeposit is the NATIVE path only — it never routes through the
710
+ // SwapRouter. Any-token swap deposits are the explicit, opt-in job of
711
+ // {@link swapRouterDeposit}; keeping them out of here is what stops a
712
+ // natively-accepted asset from being silently swapped.
713
+ if (isAdapterDeposit && !isMultiAssetVault && !adapterConfig) {
714
+ throw new core_1.AugustValidationError('INVALID_INPUT', `vaultDeposit: depositAsset ${actualDepositAsset} differs from vault underlying ${underlyingAsset} but no adapter is configured for vault ${target}. For an any-token deposit into a SwapRouter-eligible vault, use swapRouterDeposit instead.`);
715
+ }
716
+ const depositTokenDecimals = await resolveDepositTokenDecimals({
717
+ actualDepositAsset,
718
+ underlyingAsset,
719
+ isNativeToken,
720
+ isMultiAssetVault,
721
+ vaultDecimals,
722
+ readErc20Decimals: async (addr) => (0, core_1.getDecimalsOrThrow)(signer, addr, 'vaultDeposit:depositTokenDecimals'),
723
+ });
724
+ const normalizedAmt = (0, core_1.toNormalizedBn)(amount, depositTokenDecimals);
725
+ if (!isNativeToken) {
726
+ const spenderAddress = resolveSpender({
727
+ target,
728
+ isMultiAssetVault,
729
+ isAdapterDeposit,
730
+ adapterWrapperAddress: adapterConfig?.wrapperAddress,
731
+ });
732
+ const depositTokenContract = (0, core_1.createContract)({
733
+ address: actualDepositAsset,
734
+ provider: signer,
735
+ abi: abis_1.ABI_ERC20,
736
+ });
737
+ const currentAllowance = await depositTokenContract.allowance(wallet, spenderAddress);
738
+ if (safeBigInt(currentAllowance, 'vaultDeposit:allowance') <
739
+ BigInt(normalizedAmt.raw)) {
740
+ const { hash: approveHash } = await safeSendTx(() => depositTokenContract
741
+ .connect(signer)
742
+ .approve(spenderAddress, normalizedAmt.raw), signer, true);
743
+ core_1.Logger.log.info('approve:tx_hash', approveHash);
744
+ }
745
+ }
746
+ // Route to appropriate deposit method
747
+ if (isMultiAssetVault) {
748
+ if (options?.isDepositWithPermit) {
749
+ // depositWithPermit flow
750
+ // build structure hash of permit off chain
751
+ const deadline = (0, utils_1.createDeadline)();
752
+ //sign the hash with private key of depositor
753
+ const { v, r, s } = await (0, utils_1.generatePermitSignature)(actualDepositAsset, wallet, await poolContract.getAddress(), normalizedAmt.raw, deadline, signer, tokenizedVault.chain);
754
+ //actually call depositWithPermit func
755
+ const { hash: permitHash } = await safeSendTx(() => poolContract
756
+ .connect(signer)
757
+ .depositWithPermit(actualDepositAsset, normalizedAmt.raw, wallet, deadline, r, s, v), signer, wait);
758
+ core_1.Logger.log.info('deposit:tx_hash', permitHash);
759
+ return permitHash;
760
+ }
761
+ // Multi-asset vault deposit (EVM-2)
762
+ depositTx = await poolContract
763
+ .connect(signer)
764
+ .deposit(actualDepositAsset, normalizedAmt.raw, wallet);
765
+ }
766
+ else if (isAdapterDeposit && adapterConfig) {
767
+ // Adapter deposit (swap or native wrapper)
768
+ if (adapterConfig.bridgeId === 2) {
769
+ // Paraswap bridge - build swap and deposit transaction
770
+ const adapterContract = await (0, adapter_helpers_1.buildSwapAndDepositTransaction)({
771
+ signer,
772
+ srcToken: actualDepositAsset,
773
+ srcDecimals: depositTokenDecimals,
774
+ destToken: underlyingAsset,
775
+ destDecimals: vaultDecimals,
776
+ amount: normalizedAmt.raw.toString(),
777
+ network: chainId?.toString() || '1',
778
+ adapterConfig,
779
+ userAddress: wallet,
780
+ });
781
+ const swapAndDepositParams = {
782
+ amountIn: normalizedAmt.raw,
783
+ minAmountOut: 0,
784
+ srcToken: actualDepositAsset,
785
+ dstToken: underlyingAsset,
786
+ bridgeId: adapterConfig.bridgeId,
787
+ quoteData: '0x', // This will be populated by buildSwapAndDepositTransaction
788
+ };
789
+ depositTx = await adapterContract
790
+ .connect(signer)
791
+ .swapAndDeposit(swapAndDepositParams);
792
+ }
793
+ else {
794
+ // Native/wrapped token deposit via wrapper contract
795
+ const nativeDepositConfig = await (0, adapter_helpers_1.buildNativeDepositTransaction)({
796
+ signer,
797
+ srcToken: actualDepositAsset,
798
+ amount: normalizedAmt.raw.toString(),
799
+ poolName,
800
+ adapterConfig,
801
+ userAddress: wallet,
802
+ });
803
+ depositTx = await nativeDepositConfig.contract
804
+ .connect(signer)[nativeDepositConfig.functionName](...nativeDepositConfig.args, nativeDepositConfig.value
805
+ ? { value: nativeDepositConfig.value }
806
+ : {});
807
+ }
808
+ }
809
+ else {
810
+ // Standard deposit
811
+ if (isNativeToken) {
812
+ // Native ETH/AVAX deposit
813
+ depositTx = await poolContract
814
+ .connect(signer)
815
+ .deposit(normalizedAmt.raw, wallet, { value: normalizedAmt.raw });
816
+ }
817
+ else {
818
+ // Standard ERC20 deposit
819
+ depositTx = await poolContract
820
+ .connect(signer)
821
+ .deposit(normalizedAmt.raw, wallet);
822
+ }
823
+ }
824
+ if (wait)
825
+ await safeWaitForTx(depositTx);
826
+ core_1.Logger.log.info('deposit:tx_hash', depositTx?.hash);
827
+ return depositTx?.hash;
828
+ }
829
+ catch (e) {
830
+ const recovered = await tryRecoverTxHash(e, signer, wait);
831
+ if (recovered) {
832
+ core_1.Logger.log.warn('deposit:nonce-fallback', { hash: recovered });
833
+ return recovered;
834
+ }
835
+ if (e instanceof core_1.AugustSDKError)
836
+ throw e;
837
+ // depositTx is only ever unset here when the depositWithPermit sub-path
838
+ // threw — that branch sends its single main tx through safeSendTx and
839
+ // never assigns depositTx, so the error's own markBroadcast marker is the
840
+ // only place the broadcast hash survives. Every other sub-path assigns
841
+ // depositTx before this catch can run.
842
+ const broadcast = depositTx
843
+ ? localTxBroadcastContext(depositTx, e)
844
+ : errorTxBroadcastContext(e);
845
+ // User-cancelled deposits are normal; only genuine failures stay at error.
846
+ const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
847
+ (0, core_1.logChainError)('deposit', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
848
+ target,
849
+ amount,
850
+ depositAsset,
851
+ ...broadcast,
852
+ });
853
+ throwIfInsufficientFunds(insufficientFunds, 'Deposit', e, {
854
+ target,
855
+ amount,
856
+ depositAsset,
857
+ ...broadcast,
858
+ });
859
+ throw new core_1.AugustSDKError('UNKNOWN', `Deposit failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, amount, depositAsset, ...broadcast } });
860
+ }
861
+ }
862
+ /**
863
+ * Request to redeem vault shares. For EVM-1 vaults this queues a standard
864
+ * redemption (claimable later via `vaultRedeem`); for EVM-2 vaults this
865
+ * also handles the receipt-token allowance automatically. Pass
866
+ * `isInstantRedeem: true` for vaults that support instant settlement.
867
+ *
868
+ * Approval of the receipt token (EVM-2) is always waited on before the
869
+ * redeem call, regardless of the caller's `wait` flag.
870
+ *
871
+ * @param signer ethers Signer / Wallet (or viem WalletClient via {@link setSigner})
872
+ * @param options Vault address, wallet, amount, optional `isInstantRedeem`,
873
+ * optional `wait` to wait for the redeem receipt.
874
+ * @returns Redeem transaction hash.
875
+ * @throws AugustValidationError on invalid wallet/target/amount.
876
+ * @throws AugustSDKError when the redeem submission or on-chain call fails.
877
+ *
878
+ * @example
879
+ * ```ts
880
+ * const hash = await augustSdk.evm.vaultRequestRedeem({
881
+ * target: vaultAddress,
882
+ * wallet: walletAddress,
883
+ * amount: '10', // 10 shares (UI units)
884
+ * wait: true,
885
+ * });
886
+ * ```
887
+ *
888
+ * @example Instant redemption
889
+ * ```ts
890
+ * await augustSdk.evm.vaultRequestRedeem({
891
+ * target: vaultAddress,
892
+ * wallet: walletAddress,
893
+ * amount: '10',
894
+ * isInstantRedeem: true,
895
+ * });
896
+ * ```
897
+ */
898
+ async function vaultRequestRedeem(signer, options) {
899
+ const { wallet, target, wait, amount } = options;
900
+ const [goodWallet, goodPool] = [
901
+ (0, core_1.checkAddress)(wallet, console, 'wallet'),
902
+ (0, core_1.checkAddress)(target, console),
903
+ ];
904
+ if (!goodWallet || !goodPool) {
905
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', `vaultRequestRedeem: invalid ${!goodWallet ? 'wallet' : 'target'} address`);
906
+ }
907
+ if (amount === undefined || amount === null) {
908
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'vaultRequestRedeem: amount is required');
909
+ }
910
+ validateAmountPrecision(amount);
911
+ // Hoisted out of the try so the catch can tell "the redeem was broadcast and
912
+ // we lost the confirmation" from "the redeem never left". The EVM-2 receipt
913
+ // token approval sent earlier in the try is deliberately NOT tracked here:
914
+ // a failed approval means the redeem itself was never sent.
915
+ let requestRedeemTx;
916
+ try {
917
+ // Get vault version to determine correct ABI
918
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(target))?.[0];
919
+ const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
920
+ let poolContract;
921
+ let decimals;
922
+ // Handle different vault versions
923
+ if (vaultVersion === 'evm-2') {
924
+ // For evm-2, always use ABI_TOKENIZED_VAULT_V2 to get lpTokenAddress
925
+ // even for instant redeem, we need lpTokenAddress for receipt token decimals
926
+ const vaultContract = (0, core_1.createContract)({
927
+ address: target,
928
+ provider: signer,
929
+ abi: abis_1.ABI_TOKENIZED_VAULT_V2,
930
+ });
931
+ // Get receipt token for decimals. Retried on transport blips only; a
932
+ // misroute (a non-evm-2 vault reaching this branch) still throws the
933
+ // original error. See getReceiptTokenAddressOrThrow.
934
+ const receiptTokenAddr = await (0, core_1.getReceiptTokenAddressOrThrow)(signer, target, 'vaultRequestRedeem:receiptToken');
935
+ const receiptContract = (0, core_1.createContract)({
936
+ address: receiptTokenAddr,
937
+ provider: signer,
938
+ abi: abis_1.ABI_ERC20,
939
+ });
940
+ decimals = await (0, core_1.getDecimalsOrThrow)(signer, receiptTokenAddr, 'vaultRequestRedeem:receiptDecimals');
941
+ // Convert amount to bignumber
942
+ const normalizedAmt = (0, core_1.toNormalizedBn)(amount, decimals);
943
+ // EVM-2 lp/receipt token is a separate ERC-20: vault.transferFrom(lp)
944
+ // needs explicit allowance even on self-redeem (msg.sender on the lp
945
+ // token is the vault, not the user — no ERC-4626 owner-shortcut).
946
+ const allowance = await receiptContract.allowance(wallet, target);
947
+ if (safeBigInt(allowance, 'vaultRequestRedeem:allowance') <
948
+ BigInt(normalizedAmt.raw)) {
949
+ const { hash: approveHash } = await safeSendTx(() => receiptContract.connect(signer).approve(target, normalizedAmt.raw), signer, true);
950
+ core_1.Logger.log.info('approve:receipt_token:tx_hash', approveHash);
951
+ }
952
+ // For evm-2, use vaultContract for both instant and standard redeem
953
+ // ABI_TOKENIZED_VAULT_V2 has both instantRedeem (2 params) and requestRedeem
954
+ poolContract = vaultContract;
955
+ }
956
+ else {
957
+ poolContract = options.isInstantRedeem
958
+ ? (0, core_1.createContract)({
959
+ address: target,
960
+ provider: signer,
961
+ abi: abis_1.ABI_LENDING_POOL_V3,
962
+ })
963
+ : (0, core_1.createContract)({
964
+ address: target,
965
+ provider: signer,
966
+ abi: abis_1.ABI_LENDING_POOLS,
967
+ });
968
+ decimals = await (0, core_1.getDecimalsOrThrow)(signer, target, 'vaultRequestRedeem:poolDecimals');
969
+ }
970
+ // Convert amount to bignumber (for non-evm-2 vaults)
971
+ const normalizedAmt = (0, core_1.toNormalizedBn)(amount, decimals);
972
+ // Withdraw from vault - EVM-2 uses 2 params, others use 3 params
973
+ if (options.isInstantRedeem) {
974
+ if (vaultVersion === 'evm-2') {
975
+ // EVM-2: instantRedeem(uint256 shares, address receiverAddr) - 2 params
976
+ requestRedeemTx = await poolContract
977
+ .connect(signer)
978
+ .instantRedeem(BigInt(normalizedAmt.raw), wallet);
979
+ }
980
+ else {
981
+ // EVM-0/EVM-1: instantRedeem(uint256 shares, address receiverAddr, address holderAddr) - 3 params
982
+ requestRedeemTx = await poolContract
983
+ .connect(signer)
984
+ .instantRedeem(BigInt(normalizedAmt.raw), wallet, wallet);
985
+ }
986
+ }
987
+ else if (vaultVersion === 'evm-2') {
988
+ // EVM-2: requestRedeem(uint256 shares, address receiverAddr)
989
+ requestRedeemTx = await poolContract
990
+ .connect(signer)
991
+ .requestRedeem(BigInt(normalizedAmt.raw), wallet);
992
+ }
993
+ else {
994
+ // EVM-0/EVM-1: requestRedeem(uint256 shares, address receiver, address owner)
995
+ requestRedeemTx = await poolContract
996
+ .connect(signer)
997
+ .requestRedeem(BigInt(normalizedAmt.raw), wallet, wallet);
998
+ }
999
+ if (wait)
1000
+ await safeWaitForTx(requestRedeemTx);
1001
+ core_1.Logger.log.info('requestRedeem:tx_hash', requestRedeemTx?.hash);
1002
+ return requestRedeemTx?.hash;
1003
+ }
1004
+ catch (e) {
1005
+ const recovered = await tryRecoverTxHash(e, signer, wait);
1006
+ if (recovered) {
1007
+ core_1.Logger.log.warn('requestRedeem:nonce-fallback', { hash: recovered });
1008
+ return recovered;
1009
+ }
1010
+ if (e instanceof core_1.AugustSDKError)
1011
+ throw e;
1012
+ const broadcast = localTxBroadcastContext(requestRedeemTx, e);
1013
+ // User-cancelled redeem requests are normal; only genuine failures stay at error.
1014
+ const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
1015
+ (0, core_1.logChainError)('requestRedeem', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
1016
+ target,
1017
+ amount,
1018
+ ...broadcast,
1019
+ });
1020
+ throwIfInsufficientFunds(insufficientFunds, 'Request redeem', e, {
1021
+ target,
1022
+ amount,
1023
+ ...broadcast,
1024
+ });
1025
+ throw new core_1.AugustSDKError('UNKNOWN', `Request redeem failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, amount, ...broadcast } });
1026
+ }
1027
+ }
1028
+ /**
1029
+ * @TODO
1030
+ * @description withdraw funds from the specified pool including accrued rewards
1031
+ * @param signer signer / wallet object
1032
+ * @param options object including pool contract address, user wallet address, and more
1033
+ * @returns claim tx hash
1034
+ */
1035
+ async function vaultRedeem(signer, options) {
1036
+ const { wallet, target, wait, year, day, month, receiverIndex } = options;
1037
+ const [goodWallet, goodPool] = [
1038
+ (0, core_1.checkAddress)(wallet, console, 'wallet'),
1039
+ (0, core_1.checkAddress)(target, console),
1040
+ ];
1041
+ if (!goodWallet || !goodPool) {
1042
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', `vaultRedeem: invalid ${!goodWallet ? 'wallet' : 'target'} address`);
1043
+ }
1044
+ if (!year || !month || !day || !receiverIndex) {
1045
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'vaultRedeem: year, month, day, and receiverIndex are required');
1046
+ }
1047
+ try {
1048
+ const poolContract = (0, core_1.createContract)({
1049
+ address: target,
1050
+ provider: signer,
1051
+ abi: abis_1.ABI_LENDING_POOLS,
1052
+ });
1053
+ const { hash: redeemHash } = await safeSendTx(() => poolContract
1054
+ .connect(signer)
1055
+ .redeem(year, month, day, receiverIndex, wallet), signer, wait);
1056
+ core_1.Logger.log.info('redeem:tx_hash', redeemHash);
1057
+ return redeemHash;
1058
+ }
1059
+ catch (e) {
1060
+ const recovered = await tryRecoverTxHash(e, signer, wait);
1061
+ if (recovered) {
1062
+ core_1.Logger.log.warn('redeem:nonce-fallback', { hash: recovered });
1063
+ return recovered;
1064
+ }
1065
+ if (e instanceof core_1.AugustSDKError)
1066
+ throw e;
1067
+ // Single tx on this path, so the error's own marker can only be the redeem.
1068
+ const broadcast = errorTxBroadcastContext(e);
1069
+ // User-cancelled redeems are normal; only genuine failures stay at error.
1070
+ const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
1071
+ (0, core_1.logChainError)('redeem', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
1072
+ target,
1073
+ year,
1074
+ month,
1075
+ day,
1076
+ receiverIndex,
1077
+ ...broadcast,
1078
+ });
1079
+ throwIfInsufficientFunds(insufficientFunds, 'Redeem', e, {
1080
+ target,
1081
+ year,
1082
+ month,
1083
+ day,
1084
+ receiverIndex,
1085
+ ...broadcast,
1086
+ });
1087
+ throw new core_1.AugustSDKError('UNKNOWN', `Redeem failed: ${e instanceof Error ? e.message : 'Unknown error'}`, {
1088
+ cause: e,
1089
+ context: { target, year, month, day, receiverIndex, ...broadcast },
1090
+ });
1091
+ }
1092
+ }
1093
+ /**
1094
+ * Deposit a native token (ETH / AVAX / etc.) into a vault via the
1095
+ * `MultiAssetNativeDepositWrapper`. The wrapper forwards `msg.value` to
1096
+ * the vault as the deposit underlying — no allowance is required.
1097
+ *
1098
+ * `amount` is the **raw on-chain value in wei-equivalent** (18 decimals
1099
+ * for ETH-family natives). If you have a UI-shaped amount in ether, scale
1100
+ * it first.
1101
+ *
1102
+ * @param signer ethers Signer / Wallet
1103
+ * @param options `wrapperAddress` is the deployed wrapper contract;
1104
+ * `receiver` defaults to the signer's address; `amount` is the native
1105
+ * amount in wei; `wait` waits for the deposit receipt.
1106
+ * @returns Deposit transaction hash.
1107
+ * @throws AugustValidationError on invalid wrapper / receiver / amount.
1108
+ * @throws AugustSDKError when the deposit submission or on-chain call fails.
1109
+ *
1110
+ * @example
1111
+ * ```ts
1112
+ * import { parseEther } from 'ethers';
1113
+ *
1114
+ * await augustSdk.evm.depositNative({
1115
+ * wrapperAddress: nativeWrapperAddress,
1116
+ * amount: parseEther('0.5'), // 0.5 ETH in wei
1117
+ * wait: true,
1118
+ * });
1119
+ * ```
1120
+ */
1121
+ async function depositNative(signer, options) {
1122
+ const { wrapperAddress, receiver, amount, wait } = options;
1123
+ const goodWrapper = (0, core_1.checkAddress)(wrapperAddress, console, 'contract');
1124
+ if (!goodWrapper) {
1125
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', 'depositNative: invalid wrapper address');
1126
+ }
1127
+ if (amount === undefined || amount === null) {
1128
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'depositNative: amount is required');
1129
+ }
1130
+ validateAmountPrecision(amount);
1131
+ // Hoisted so the catch can distinguish "broadcast, confirmation lost" from
1132
+ // "never sent" — see localTxBroadcastContext.
1133
+ let depositTx;
1134
+ try {
1135
+ // Create wrapper contract instance
1136
+ const wrapperContract = (0, core_1.createContract)({
1137
+ address: wrapperAddress,
1138
+ provider: signer,
1139
+ abi: abis_1.ABI_MULTI_ASSET_NATIVE_DEPOSIT_WRAPPER,
1140
+ }); // TODO: provide typed interface
1141
+ // Convert amount to bigint (native tokens use 18 decimals typically)
1142
+ // For native deposits, amount should be in wei/smallest unit
1143
+ const amountBigInt = typeof amount === 'bigint'
1144
+ ? amount
1145
+ : typeof amount === 'string'
1146
+ ? BigInt(amount)
1147
+ : BigInt(amount);
1148
+ // Connect the contract first, then get the function to disambiguate between overloads
1149
+ const connectedContract = wrapperContract.connect(signer);
1150
+ // Call depositNative with or without receiver parameter
1151
+ if (receiver) {
1152
+ const goodReceiver = (0, core_1.checkAddress)(receiver, console, 'wallet');
1153
+ if (!goodReceiver) {
1154
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', 'depositNative: invalid receiver address');
1155
+ }
1156
+ // depositNative(address receiver) - with receiver
1157
+ const depositNativeWithReceiver = connectedContract.getFunction('depositNative(address)');
1158
+ depositTx = await depositNativeWithReceiver(receiver, {
1159
+ value: amountBigInt,
1160
+ });
1161
+ }
1162
+ else {
1163
+ // depositNative() - without receiver (uses msg.sender as receiver)
1164
+ const depositNativeNoParams = connectedContract.getFunction('depositNative()');
1165
+ depositTx = await depositNativeNoParams({
1166
+ value: amountBigInt,
1167
+ });
1168
+ }
1169
+ if (wait)
1170
+ await safeWaitForTx(depositTx);
1171
+ core_1.Logger.log.info('depositNative:tx_hash', depositTx?.hash);
1172
+ return depositTx?.hash;
1173
+ }
1174
+ catch (e) {
1175
+ const recovered = await tryRecoverTxHash(e, signer, wait);
1176
+ if (recovered) {
1177
+ core_1.Logger.log.warn('depositNative:nonce-fallback', { hash: recovered });
1178
+ return recovered;
1179
+ }
1180
+ if (e instanceof core_1.AugustSDKError)
1181
+ throw e;
1182
+ const broadcast = localTxBroadcastContext(depositTx, e);
1183
+ // User-cancelled native deposits are normal; only genuine failures stay at error.
1184
+ const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
1185
+ (0, core_1.logChainError)('depositNative', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
1186
+ wrapperAddress,
1187
+ receiver,
1188
+ amount,
1189
+ ...broadcast,
1190
+ });
1191
+ throwIfInsufficientFunds(insufficientFunds, 'Deposit native', e, {
1192
+ wrapperAddress,
1193
+ receiver,
1194
+ amount,
1195
+ ...broadcast,
1196
+ });
1197
+ throw new core_1.AugustSDKError('UNKNOWN', `Deposit native failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { wrapperAddress, receiver, amount, ...broadcast } });
1198
+ }
1199
+ }
1200
+ /**
1201
+ * Redeem vault shares for an underlying asset via the RwaRedeemSubaccount.
1202
+ * Caller must approve the **vault share token** (not the output asset) to
1203
+ * the subaccount beforehand — it pulls shares via `transferFrom`.
1204
+ *
1205
+ * Pass `minOut` to enforce slippage protection (in `outputDecimals` units).
1206
+ * The SDK does not currently compute `minOut` from a slippage percentage —
1207
+ * the caller is responsible for choosing a safe lower bound.
1208
+ *
1209
+ * @throws AugustValidationError on invalid addresses or amount inputs.
1210
+ * @throws AugustSDKError when the redeem submission or on-chain call fails.
1211
+ *
1212
+ * @example
1213
+ * ```ts
1214
+ * // 1. Approve the vault share token to the subaccount first.
1215
+ * await augustSdk.evm.vaultApprove({
1216
+ * target: subaccountAddress,
1217
+ * wallet: walletAddress,
1218
+ * amount: '10',
1219
+ * wait: true,
1220
+ * });
1221
+ *
1222
+ * // 2. Redeem 10 shares for USDC, requiring at least 9.5 USDC out.
1223
+ * await augustSdk.evm.rwaRedeemAsset({
1224
+ * target: subaccountAddress,
1225
+ * wallet: walletAddress,
1226
+ * asset: USDC_ADDRESS,
1227
+ * amount: '10',
1228
+ * minOut: '9.5',
1229
+ * decimals: 18, // vault share decimals
1230
+ * outputDecimals: 6, // USDC decimals
1231
+ * wait: true,
1232
+ * });
1233
+ * ```
1234
+ */
1235
+ async function rwaRedeemAsset(signer, options) {
1236
+ const { target, wallet, asset, amount, minOut, decimals, outputDecimals, wait, } = options;
1237
+ const goodTarget = (0, core_1.checkAddress)(target, console, 'contract');
1238
+ const goodAsset = (0, core_1.checkAddress)(asset, console, 'contract');
1239
+ const goodWallet = (0, core_1.checkAddress)(wallet, console, 'wallet');
1240
+ if (!goodTarget || !goodAsset || !goodWallet) {
1241
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', `rwaRedeemAsset: invalid ${!goodTarget ? 'target' : !goodAsset ? 'asset' : 'wallet'} address`);
1242
+ }
1243
+ validateAmountPrecision(amount);
1244
+ validateAmountPrecision(minOut);
1245
+ try {
1246
+ const subaccountContract = (0, core_1.createContract)({
1247
+ address: target,
1248
+ provider: signer,
1249
+ abi: abis_1.RWA_REDEEM_SUBACCOUNT,
1250
+ });
1251
+ if (!subaccountContract) {
1252
+ throw new Error(`rwaRedeemAsset: failed to instantiate subaccount contract at ${target}`);
1253
+ }
1254
+ const normalizedAmt = (0, core_1.toNormalizedBn)(amount, decimals);
1255
+ const normalizedMinOut = (0, core_1.toNormalizedBn)(minOut, outputDecimals);
1256
+ const { hash: redeemHash } = await safeSendTx(() => subaccountContract
1257
+ .connect(signer)
1258
+ .redeemAsset(asset, BigInt(normalizedAmt.raw), BigInt(normalizedMinOut.raw)), signer, wait);
1259
+ core_1.Logger.log.info('rwaRedeemAsset:tx_hash', redeemHash);
1260
+ return redeemHash;
1261
+ }
1262
+ catch (e) {
1263
+ const recovered = await tryRecoverTxHash(e, signer, wait);
1264
+ if (recovered) {
1265
+ core_1.Logger.log.warn('rwaRedeemAsset:nonce-fallback', { hash: recovered });
1266
+ return recovered;
1267
+ }
1268
+ if (e instanceof core_1.AugustSDKError)
1269
+ throw e;
1270
+ // Single tx on this path, so the error's own marker can only be the redeem.
1271
+ const broadcast = errorTxBroadcastContext(e);
1272
+ // User-cancelled RWA redeems are normal; only genuine failures stay at error.
1273
+ const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
1274
+ (0, core_1.logChainError)('rwaRedeemAsset', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
1275
+ target,
1276
+ asset,
1277
+ amount,
1278
+ minOut,
1279
+ ...broadcast,
1280
+ });
1281
+ throwIfInsufficientFunds(insufficientFunds, 'RWA redeem', e, {
1282
+ target,
1283
+ asset,
1284
+ amount,
1285
+ minOut,
1286
+ ...broadcast,
1287
+ });
1288
+ throw new core_1.AugustSDKError('UNKNOWN', `RWA redeem failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, asset, amount, minOut, ...broadcast } });
1289
+ }
1290
+ }
1291
+ /** @internal */
1292
+ async function dispatchViaSwapRouter(args) {
1293
+ const amountRaw = BigInt(args.normalizedAmt.raw);
1294
+ const sharesReceiver = args.receiver ?? args.wallet;
1295
+ if (args.isNativeToken) {
1296
+ // SwapRouter native path requires underlying === wrapped native.
1297
+ const wrappedNative = core_1.SWAP_ROUTER_WRAPPED_NATIVE[args.chainId];
1298
+ if (!wrappedNative ||
1299
+ args.underlyingAsset.toLowerCase() !== wrappedNative.toLowerCase()) {
1300
+ throw new core_1.AugustValidationError('INVALID_INPUT', `vaultDeposit (SwapRouter): native deposit is only supported when the vault's reference asset is the chain's wrapped-native token. Vault ${args.target} underlying ${args.underlyingAsset} is not. Wrap to ${wrappedNative ?? 'WETH'} first and call again with depositAsset = the wrapped token.`, {
1301
+ context: {
1302
+ vault: args.target,
1303
+ underlying: args.underlyingAsset,
1304
+ chainId: args.chainId,
1305
+ wrappedNative,
1306
+ },
1307
+ });
1308
+ }
1309
+ return depositNativeViaSwapRouter(args.signer, {
1310
+ chainId: args.chainId,
1311
+ vault: args.target,
1312
+ receiver: sharesReceiver,
1313
+ amount: amountRaw,
1314
+ wait: args.wait,
1315
+ });
1316
+ }
1317
+ const isUnderlying = args.actualDepositAsset.toLowerCase() ===
1318
+ args.underlyingAsset.toLowerCase();
1319
+ if (isUnderlying) {
1320
+ return depositViaSwapRouter(args.signer, {
1321
+ chainId: args.chainId,
1322
+ vault: args.target,
1323
+ receiver: sharesReceiver,
1324
+ asset: args.actualDepositAsset,
1325
+ amount: amountRaw,
1326
+ wait: args.wait,
1327
+ });
1328
+ }
1329
+ // Vault share decimals can diverge from the underlying asset decimals
1330
+ // (e.g. an 18-decimal share token over an 8-decimal WBTC underlying on
1331
+ // a multi-asset v2 vault). The swap quote must use the underlying's
1332
+ // on-chain decimals so Paraswap's /prices interprets the route correctly.
1333
+ const underlyingDecimals = await (0, core_1.getDecimalsOrThrow)(args.signer, args.underlyingAsset, 'dispatchViaSwapRouter:underlyingDecimals');
1334
+ // The on-chain SwapRouter only authorizes a specific (router, selector) pair
1335
+ // per chain (see SWAP_ROUTER_DEX_AGGREGATOR). Pin the aggregator to that
1336
+ // router's single generic method so the calldata selector is deterministic,
1337
+ // then reject any quote that would not match — otherwise the deposit reverts
1338
+ // on-chain with InvalidRouter() / InvalidNotWhitelisted() after the user has
1339
+ // already paid gas.
1340
+ const aggregator = core_1.SWAP_ROUTER_DEX_AGGREGATOR[args.chainId];
1341
+ if (!aggregator) {
1342
+ throw new core_1.AugustValidationError('INVALID_CHAIN', `vaultDeposit (SwapRouter): no DEX aggregator is configured for chainId ${args.chainId}, but a swap is required because the deposit asset (${args.actualDepositAsset}) differs from the vault's reference asset (${args.underlyingAsset}). Deposit the reference asset directly, or add a SWAP_ROUTER_DEX_AGGREGATOR entry once the router is whitelisted on-chain.`, {
1343
+ context: {
1344
+ chainId: args.chainId,
1345
+ depositAsset: args.actualDepositAsset,
1346
+ underlyingAsset: args.underlyingAsset,
1347
+ },
1348
+ });
1349
+ }
1350
+ const quote = await (0, swap_quotes_1.fetchSwapQuote)({
1351
+ chainId: args.chainId,
1352
+ srcToken: args.actualDepositAsset,
1353
+ srcDecimals: args.depositTokenDecimals,
1354
+ destToken: args.underlyingAsset,
1355
+ destDecimals: underlyingDecimals,
1356
+ amount: amountRaw,
1357
+ receiver: args.routerAddress,
1358
+ slippageBps: args.slippageBps,
1359
+ contractMethod: aggregator.contractMethod,
1360
+ });
1361
+ // Fail closed on off-chain/on-chain config drift: if the aggregator's quote
1362
+ // targets a router or selector the deployed SwapRouter has not whitelisted,
1363
+ // the swapAndDeposit call would revert. Surfacing it here turns a wasted gas
1364
+ // burn into an actionable SDK error.
1365
+ const quotedSelector = quote.payload.slice(0, 10).toLowerCase();
1366
+ if (quote.router.toLowerCase() !== aggregator.router.toLowerCase() ||
1367
+ quotedSelector !== aggregator.selector.toLowerCase()) {
1368
+ throw new core_1.AugustValidationError('INVALID_INPUT', `vaultDeposit (SwapRouter): the aggregator quote (router ${quote.router}, selector ${quotedSelector}) does not match the router whitelisted on chain ${args.chainId} (router ${aggregator.router}, selector ${aggregator.selector}); the on-chain SwapRouter would revert. This signals drift between the SDK's SWAP_ROUTER_DEX_AGGREGATOR and the deployed router's enableRouter config.`, {
1369
+ context: {
1370
+ chainId: args.chainId,
1371
+ quotedRouter: quote.router,
1372
+ quotedSelector,
1373
+ expectedRouter: aggregator.router,
1374
+ expectedSelector: aggregator.selector,
1375
+ },
1376
+ });
1377
+ }
1378
+ return swapAndDeposit(args.signer, {
1379
+ chainId: args.chainId,
1380
+ vault: args.target,
1381
+ receiver: sharesReceiver,
1382
+ swaps: [
1383
+ {
1384
+ tokenIn: args.actualDepositAsset,
1385
+ tokenOut: args.underlyingAsset,
1386
+ amountIn: amountRaw,
1387
+ minAmountOut: quote.minAmountOut,
1388
+ router: quote.router,
1389
+ payload: quote.payload,
1390
+ },
1391
+ ],
1392
+ wait: args.wait,
1393
+ });
1394
+ }
1395
+ /** @internal */
1396
+ function resolveSwapRouterOrThrow(chainId) {
1397
+ const router = (0, core_1.getSwapRouterAddress)(chainId);
1398
+ if (!router) {
1399
+ throw new core_1.AugustValidationError('INVALID_CHAIN', `SwapRouter: no deployment registered for chainId ${chainId}`, { context: { chainId } });
1400
+ }
1401
+ return router;
1402
+ }
1403
+ /** @internal */
1404
+ async function ensureAllowance(signer, token, owner, spender, amount) {
1405
+ const tokenContract = (0, core_1.createContract)({
1406
+ address: token,
1407
+ provider: signer,
1408
+ abi: abis_1.ABI_ERC20,
1409
+ });
1410
+ const current = safeBigInt(await tokenContract.allowance(owner, spender), 'swapRouter:allowance');
1411
+ if (current >= amount)
1412
+ return;
1413
+ const { hash } = await safeSendTx(() => tokenContract.connect(signer).approve(spender, amount), signer, true);
1414
+ core_1.Logger.log.info('swapRouter:approve:tx_hash', hash);
1415
+ }
1416
+ /** @internal */
1417
+ function ensureSwapsValid(swaps) {
1418
+ if (swaps.length === 0) {
1419
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'swapAndDeposit: at least one swap leg is required');
1420
+ }
1421
+ if (swaps.length > core_1.SWAP_ROUTER_MAX_SWAPS) {
1422
+ throw new core_1.AugustValidationError('INVALID_INPUT', `swapAndDeposit: too many swaps (${swaps.length} > ${core_1.SWAP_ROUTER_MAX_SWAPS})`, { context: { count: swaps.length, max: core_1.SWAP_ROUTER_MAX_SWAPS } });
1423
+ }
1424
+ for (let i = 0; i < swaps.length; i += 1) {
1425
+ validateSwapParams(swaps[i], i);
1426
+ }
1427
+ }
1428
+ /** @internal */
1429
+ function validateSwapParams(swap, index) {
1430
+ const where = `swapAndDeposit: swaps[${index}]`;
1431
+ if (!(0, core_1.checkAddress)(swap.tokenIn, console, 'contract')) {
1432
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', `${where}.tokenIn is not a valid address`);
1433
+ }
1434
+ if (!(0, core_1.checkAddress)(swap.tokenOut, console, 'contract')) {
1435
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', `${where}.tokenOut is not a valid address`);
1436
+ }
1437
+ if (!(0, core_1.checkAddress)(swap.router, console, 'contract')) {
1438
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', `${where}.router is not a valid address`);
1439
+ }
1440
+ if (swap.tokenIn.toLowerCase() === swap.tokenOut.toLowerCase()) {
1441
+ throw new core_1.AugustValidationError('INVALID_INPUT', `${where}: tokenIn and tokenOut are identical — no swap needed`);
1442
+ }
1443
+ if (swap.amountIn <= 0n) {
1444
+ throw new core_1.AugustValidationError('INVALID_INPUT', `${where}.amountIn must be greater than zero`);
1445
+ }
1446
+ if (swap.minAmountOut <= 0n) {
1447
+ throw new core_1.AugustValidationError('INVALID_INPUT', `${where}.minAmountOut must be greater than zero — slippage protection cannot be disabled`);
1448
+ }
1449
+ if (!/^0x[0-9a-fA-F]+$/.test(swap.payload) || swap.payload.length < 10) {
1450
+ throw new core_1.AugustValidationError('INVALID_INPUT', `${where}.payload must be ABI-encoded calldata (got "${swap.payload.slice(0, 12)}…")`);
1451
+ }
1452
+ }
1453
+ /** @internal */
1454
+ function totalInputPerToken(swaps) {
1455
+ const totals = new Map();
1456
+ for (const swap of swaps) {
1457
+ const key = swap.tokenIn.toLowerCase();
1458
+ totals.set(key, (totals.get(key) ?? 0n) + swap.amountIn);
1459
+ }
1460
+ return totals;
1461
+ }
1462
+ // Allowance owner must equal msg.sender (contract uses transferFrom(msg.sender, ...));
1463
+ // smart-account wallets need explicit handling outside the SDK.
1464
+ /** @internal */
1465
+ async function resolveSignerEOA(signer, callerContext) {
1466
+ const getAddress = signer.getAddress;
1467
+ if (typeof getAddress !== 'function') {
1468
+ throw new core_1.AugustValidationError('INVALID_INPUT', `${callerContext}: signer must implement getAddress(); got ${typeof getAddress}`);
1469
+ }
1470
+ const eoa = await getAddress.call(signer);
1471
+ if (!eoa || !(0, core_1.checkAddress)(eoa, console, 'wallet')) {
1472
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', `${callerContext}: signer.getAddress() returned an invalid address: ${String(eoa)}`);
1473
+ }
1474
+ return eoa;
1475
+ }
1476
+ /**
1477
+ * Swap one or more whitelisted ERC-20s into a vault's reference asset via
1478
+ * the on-chain SwapRouter, then deposit the proceeds into the vault.
1479
+ *
1480
+ * Approval is sent automatically when the caller's allowance on each
1481
+ * `swaps[i].tokenIn` against the SwapRouter is below `amountIn`.
1482
+ *
1483
+ * @param signer - ethers `Signer` or `Wallet` with the EOA that holds the input tokens.
1484
+ * @param options - {@link ISwapAndDepositOptions}.
1485
+ * @returns The transaction hash of the `swapAndDeposit` call.
1486
+ *
1487
+ * @throws {@link AugustValidationError} for unsupported chains, invalid
1488
+ * addresses, empty/oversized swap arrays.
1489
+ * @throws {@link AugustSDKError} on unrecoverable contract or RPC failure.
1490
+ *
1491
+ * @example
1492
+ * ```ts
1493
+ * const quote = await fetchSwapQuote({
1494
+ * chainId: 1,
1495
+ * srcToken: WBTC,
1496
+ * srcDecimals: 8,
1497
+ * destToken: USDC,
1498
+ * destDecimals: 6,
1499
+ * amount: 100_000_000n,
1500
+ * receiver: SWAP_ROUTER_ADDRESSES[1]!,
1501
+ * });
1502
+ * const hash = await swapAndDeposit(signer, {
1503
+ * chainId: 1,
1504
+ * vault: '0x8AcA0841993ef4C87244d519166e767f49362C21',
1505
+ * receiver: wallet,
1506
+ * swaps: [{
1507
+ * tokenIn: WBTC,
1508
+ * tokenOut: USDC,
1509
+ * amountIn: 100_000_000n,
1510
+ * minAmountOut: quote.minAmountOut,
1511
+ * router: quote.router,
1512
+ * payload: quote.payload,
1513
+ * }],
1514
+ * });
1515
+ * ```
1516
+ */
1517
+ async function swapAndDeposit(signer, options) {
1518
+ const { chainId, vault, receiver, swaps, originCode, wait } = options;
1519
+ const goodVault = (0, core_1.checkAddress)(vault, console, 'contract');
1520
+ const goodReceiver = (0, core_1.checkAddress)(receiver, console, 'wallet');
1521
+ if (!goodVault || !goodReceiver) {
1522
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', `swapAndDeposit: invalid ${!goodVault ? 'vault' : 'receiver'} address`);
1523
+ }
1524
+ ensureSwapsValid(swaps);
1525
+ const routerAddress = resolveSwapRouterOrThrow(chainId);
1526
+ const eoa = await resolveSignerEOA(signer, 'swapAndDeposit');
1527
+ for (const [tokenIn, totalIn] of totalInputPerToken(swaps)) {
1528
+ await ensureAllowance(signer, tokenIn, eoa, routerAddress, totalIn);
1529
+ }
1530
+ const routerContract = (0, core_1.createContract)({
1531
+ address: routerAddress,
1532
+ provider: signer,
1533
+ abi: abis_1.ABI_SWAP_ROUTER,
1534
+ });
1535
+ const { hash } = await safeSendTx(() => routerContract
1536
+ .connect(signer)
1537
+ .swapAndDeposit((0, core_1.resolveOriginCode)(originCode), vault, receiver, swaps), signer, wait);
1538
+ core_1.Logger.log.info('swapAndDeposit:tx_hash', hash);
1539
+ return hash;
1540
+ }
1541
+ /**
1542
+ * Deposit a vault's reference asset directly through the SwapRouter — no
1543
+ * swap. Useful when the caller already holds the reference asset but wants
1544
+ * the origin/referral fee accounting that only the SwapRouter path provides.
1545
+ *
1546
+ * @param signer - ethers `Signer` or `Wallet`.
1547
+ * @param options - {@link ISwapRouterDirectDepositOptions}.
1548
+ * @returns The transaction hash of the `deposit` call.
1549
+ *
1550
+ * @throws {@link AugustValidationError} for unsupported chains, invalid
1551
+ * addresses, or zero amount.
1552
+ */
1553
+ async function depositViaSwapRouter(signer, options) {
1554
+ const { chainId, vault, receiver, asset, amount, originCode, wait } = options;
1555
+ const goodVault = (0, core_1.checkAddress)(vault, console, 'contract');
1556
+ const goodReceiver = (0, core_1.checkAddress)(receiver, console, 'wallet');
1557
+ const goodAsset = (0, core_1.checkAddress)(asset, console, 'contract');
1558
+ if (!goodVault || !goodReceiver || !goodAsset) {
1559
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', 'depositViaSwapRouter: invalid address');
1560
+ }
1561
+ if (amount === 0n) {
1562
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'depositViaSwapRouter: amount must be greater than zero');
1563
+ }
1564
+ const routerAddress = resolveSwapRouterOrThrow(chainId);
1565
+ const eoa = await resolveSignerEOA(signer, 'depositViaSwapRouter');
1566
+ await ensureAllowance(signer, asset, eoa, routerAddress, amount);
1567
+ const routerContract = (0, core_1.createContract)({
1568
+ address: routerAddress,
1569
+ provider: signer,
1570
+ abi: abis_1.ABI_SWAP_ROUTER,
1571
+ });
1572
+ const { hash } = await safeSendTx(() => routerContract
1573
+ .connect(signer)
1574
+ .deposit((0, core_1.resolveOriginCode)(originCode), amount, vault, asset, receiver), signer, wait);
1575
+ core_1.Logger.log.info('depositViaSwapRouter:tx_hash', hash);
1576
+ return hash;
1577
+ }
1578
+ /**
1579
+ * Deposit native ETH (or chain-native equivalent) into a vault via the
1580
+ * SwapRouter. The router wraps `amount` to the chain's wrapped-native token
1581
+ * before depositing — only valid when the vault's reference asset equals
1582
+ * the wrapped-native token.
1583
+ *
1584
+ * No ERC-20 approval is needed; `amount` travels as `msg.value`.
1585
+ *
1586
+ * @param signer - ethers `Signer` or `Wallet`.
1587
+ * @param options - {@link ISwapRouterNativeDepositOptions}.
1588
+ * @returns The transaction hash of the `depositNativeToken` call.
1589
+ *
1590
+ * @throws {@link AugustValidationError} for unsupported chains, invalid
1591
+ * addresses, or zero amount.
1592
+ */
1593
+ async function depositNativeViaSwapRouter(signer, options) {
1594
+ const { chainId, vault, receiver, amount, originCode, wait } = options;
1595
+ const goodVault = (0, core_1.checkAddress)(vault, console, 'contract');
1596
+ const goodReceiver = (0, core_1.checkAddress)(receiver, console, 'wallet');
1597
+ if (!goodVault || !goodReceiver) {
1598
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', 'depositNativeViaSwapRouter: invalid address');
1599
+ }
1600
+ if (amount === 0n) {
1601
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'depositNativeViaSwapRouter: amount must be greater than zero');
1602
+ }
1603
+ const routerAddress = resolveSwapRouterOrThrow(chainId);
1604
+ const routerContract = (0, core_1.createContract)({
1605
+ address: routerAddress,
1606
+ provider: signer,
1607
+ abi: abis_1.ABI_SWAP_ROUTER,
1608
+ });
1609
+ const { hash } = await safeSendTx(() => routerContract
1610
+ .connect(signer)
1611
+ .depositNativeToken((0, core_1.resolveOriginCode)(originCode), vault, receiver, {
1612
+ value: amount,
1613
+ }), signer, wait);
1614
+ core_1.Logger.log.info('depositNativeViaSwapRouter:tx_hash', hash);
1615
+ return hash;
1616
+ }
1617
+ /**
1618
+ * Deposit into an August vault through the on-chain `SwapRouter`, selecting the
1619
+ * correct router path from the deposit asset. This is the high-level, explicit
1620
+ * counterpart to the SwapRouter branch inside {@link vaultDeposit}.
1621
+ *
1622
+ * Where `vaultDeposit` decides whether to use the SwapRouter from the
1623
+ * `VAULTS_USING_SWAP_ROUTER` allowlist, this function always routes through the
1624
+ * SwapRouter because the caller asked it to. Making the intent explicit is the
1625
+ * point: a native-deposit vault (e.g. a multi-asset Tokenized Vault V2 whose
1626
+ * non-reference assets are accepted directly) is never accidentally swapped —
1627
+ * the regression that implicit, allowlist-driven routing caused. Use
1628
+ * `vaultDeposit` for native / adapter deposits and this for SwapRouter deposits.
1629
+ *
1630
+ * The vault's reference asset and its decimals are read on-chain (never trusted
1631
+ * from the caller — a wrong reference asset would mis-route the swap). The path
1632
+ * is then chosen from `depositAsset`:
1633
+ * - equals the reference asset → direct router deposit, no swap
1634
+ * ({@link depositViaSwapRouter});
1635
+ * - the zero address → native-token deposit ({@link depositNativeViaSwapRouter}),
1636
+ * valid only when the reference asset is the chain's wrapped-native token;
1637
+ * - any other ERC-20 → a swap to the reference asset (Paraswap quote pinned to
1638
+ * the chain's whitelisted aggregator, fail-closed on router/selector drift)
1639
+ * bundled into {@link swapAndDeposit}.
1640
+ *
1641
+ * Side effects: reads tokenized-vault metadata, the vault's reference asset and
1642
+ * decimals, and (on the swap path) one Paraswap quote; sends one ERC-20
1643
+ * `approve` to the SwapRouter when allowance is short, then the deposit tx.
1644
+ *
1645
+ * @param signer - ethers `Signer` or `Wallet` that signs the approval + deposit.
1646
+ * @param options - {@link ISwapRouterDepositOptions}.
1647
+ * @returns The transaction hash of the router deposit call.
1648
+ * @throws {@link AugustValidationError} for an unsupported chain (no SwapRouter
1649
+ * deployment), an invalid vault or deposit-asset address, a missing or zero
1650
+ * amount, a native deposit into a non-wrapped-native vault, or drift between
1651
+ * the aggregator quote and the on-chain router whitelist.
1652
+ * @worstCaseRpcCalls 5 on the swap path — tokenized-vault metadata, reference
1653
+ * asset + decimals, deposit-token decimals, the quote, and the allowance read.
1654
+ * @example
1655
+ * ```ts
1656
+ * // USDT deposited into a USDC-reference vault → swapped to USDC en route.
1657
+ * const hash = await augustSdk.evm.swapRouterDeposit({
1658
+ * chainId: 1,
1659
+ * vault: '0xe9b725010a9e419412ed67d0fa5f3a5f40159d32',
1660
+ * depositAsset: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
1661
+ * amount: '100',
1662
+ * slippageBps: 50,
1663
+ * });
1664
+ * ```
1665
+ */
1666
+ async function swapRouterDeposit(signer, options) {
1667
+ const { chainId, vault, depositAsset, amount, receiver, slippageBps, wait } = options;
1668
+ if (!(0, core_1.checkAddress)(vault, console, 'contract')) {
1669
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', `swapRouterDeposit: invalid vault address "${vault}"`);
1670
+ }
1671
+ if (!(0, core_1.checkAddress)(depositAsset, console, 'contract')) {
1672
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', `swapRouterDeposit: invalid depositAsset address "${depositAsset}"`);
1673
+ }
1674
+ if (amount === undefined || amount === null) {
1675
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'swapRouterDeposit: amount is required');
1676
+ }
1677
+ validateAmountPrecision(amount);
1678
+ // Explicit opt-in: resolve the router straight from chainId rather than
1679
+ // consulting VAULTS_USING_SWAP_ROUTER, so this path never depends on the
1680
+ // implicit allowlist that could route a native-deposit vault through a swap.
1681
+ const routerAddress = resolveSwapRouterOrThrow(chainId);
1682
+ const eoa = await resolveSignerEOA(signer, 'swapRouterDeposit');
1683
+ // Reference asset + decimals from the vault on-chain. Mirrors vaultDeposit's
1684
+ // metadata resolution so the deposit-token decimals (and therefore the raw
1685
+ // amount) match exactly; kept separate to leave the vaultDeposit money path
1686
+ // untouched.
1687
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault))?.[0];
1688
+ const isMultiAssetVault = (0, core_1.getVaultVersionV2)(tokenizedVault) === 'evm-2';
1689
+ let underlyingAsset;
1690
+ let vaultDecimals;
1691
+ if (isMultiAssetVault) {
1692
+ const poolContract = (0, core_1.createContract)({
1693
+ address: vault,
1694
+ provider: signer,
1695
+ abi: abis_1.ABI_TOKENIZED_VAULT_V2,
1696
+ });
1697
+ const [asset, receiptTokenAddr] = await Promise.all([
1698
+ poolContract.asset(),
1699
+ // Retried on transport blips only; a misroute still throws. See
1700
+ // getReceiptTokenAddressOrThrow.
1701
+ (0, core_1.getReceiptTokenAddressOrThrow)(signer, vault, 'swapRouterDeposit:receiptToken'),
1702
+ ]);
1703
+ underlyingAsset = asset;
1704
+ vaultDecimals = await (0, core_1.getDecimalsOrThrow)(signer, receiptTokenAddr, 'swapRouterDeposit:receiptDecimals');
1705
+ }
1706
+ else {
1707
+ const poolContract = (0, core_1.createContract)({
1708
+ address: vault,
1709
+ provider: signer,
1710
+ abi: abis_1.ABI_LENDING_POOLS,
1711
+ });
1712
+ const [asset, rawDecimals] = await Promise.all([
1713
+ poolContract.asset(),
1714
+ (0, core_1.getDecimalsOrThrow)(signer, vault, 'swapRouterDeposit:poolDecimals'),
1715
+ ]);
1716
+ underlyingAsset = asset;
1717
+ vaultDecimals = rawDecimals;
1718
+ }
1719
+ const isNativeToken = depositAsset === ethers_1.ZeroAddress ||
1720
+ depositAsset === '0x0000000000000000000000000000000000000000';
1721
+ const depositTokenDecimals = await resolveDepositTokenDecimals({
1722
+ actualDepositAsset: depositAsset,
1723
+ underlyingAsset,
1724
+ isNativeToken,
1725
+ isMultiAssetVault,
1726
+ vaultDecimals,
1727
+ readErc20Decimals: async (addr) => (0, core_1.getDecimalsOrThrow)(signer, addr, 'swapRouterDeposit:depositTokenDecimals'),
1728
+ });
1729
+ const normalizedAmt = (0, core_1.toNormalizedBn)(amount, depositTokenDecimals);
1730
+ if (BigInt(normalizedAmt.raw) === 0n) {
1731
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'swapRouterDeposit: amount must be greater than zero');
1732
+ }
1733
+ return dispatchViaSwapRouter({
1734
+ signer,
1735
+ routerAddress,
1736
+ chainId,
1737
+ target: vault,
1738
+ wallet: eoa,
1739
+ receiver,
1740
+ actualDepositAsset: depositAsset,
1741
+ underlyingAsset,
1742
+ isNativeToken,
1743
+ depositTokenDecimals,
1744
+ normalizedAmt,
1745
+ slippageBps,
1746
+ wait,
1747
+ });
1748
+ }
1749
+ //# sourceMappingURL=write.actions.js.map