diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c81fb862..cf196bfc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -33,17 +33,21 @@ jobs: - run: npm ci - run: npm run lint - run: npm run build --if-present - - run: npm run hardhat:arbitrum & npm run test:arbitrum - - run: npm run hardhat & npm run test:utils - - run: npm run hardhat & npm run test:builders - - run: npm run hardhat & npm run test:quote - - run: npm run hardhat & npm run test:btc2x - - run: npm run hardhat & npm run test:cdeti - - run: npm run hardhat & npm run test:dseth - - run: npm run hardhat & npm run test:eth2x - - run: npm run hardhat & npm run test:gtceth - - run: npm run hardhat & npm run test:iceth - # - run: npm run hardhat & npm run test:icreth + - run: npm run hardhat:arbitrum & + - run: npm run hardhat & + - name: Wait for hardhat nodes to start + run: sleep 10 + - run: npm run test:utils + - run: npm run test:builders + - run: npm run test:quotes + - run: npm run test:hyeth + - run: npm run test:btc2x + - run: npm run test:cdeti + - run: npm run test:dseth + - run: npm run test:eth2x + - run: npm run test:gtceth + - run: npm run test:iceth + # - run: npm run test:icreth # run last - as it alters the block number # skip as it can't be minted or redeemed with 0x - # - run: npm run hardhat & npm run test:eth2xfli + # - run: npm run test:eth2xfli diff --git a/README.md b/README.md index 3f0cc74e..1262c8b3 100644 --- a/README.md +++ b/README.md @@ -126,8 +126,8 @@ When adding new .env vars do not forget to update the [publish.yml](.github/work 0. add a test for determining the correct issuance module [here](./src/utils/issuanceModules.test.ts) 1. add a test for determining the correct contract [here](./src/utils/contracts.test.ts) 2. if there is a new FlashMint contract, add it as described [below](#adding-a-new-contract) -3. additionally, add a test in [tests](./src/tests/) -4. add symbol to `function getContractType(token: string)` in [src/quote/provider/utils.ts](./src/quote//provider/utils.ts) and add a test +3. add symbol to `function getContractType(token: string)` in [src/quote/provider/utils.ts](./src/quote//provider/utils.ts) and add a test +4. additionally, add a test in [tests](./src/tests/) ### Adding a new contract diff --git a/package.json b/package.json index 1e8e0773..26388945 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@indexcoop/flash-mint-sdk", - "version": "3.0.0", + "version": "3.1.0", "description": "The FlashMintSDK of the Index Coop.", "engines": { "node": ">=18" @@ -26,9 +26,8 @@ "lint": "prettier -c . && eslint ./src", "lint:fix": "prettier -w . && eslint ./src --fix", "test": "jest", - "test:arbitrum": "npm test src/flashmint/builders/leveraged-extended.test.ts src/quote/leveraged-extended", - "test:builders": "npm test src/flashmint/builders/leveraged.test.ts src/flashmint/builders/zeroex.test.ts", - "test:quote": "npm test src/quote/flashmint/leveraged/provider.test.ts src/quote/flashmint/zeroex src/quote/provider/ ", + "test:builders": "npm test src/flashmint/", + "test:quotes": "npm test src/quote/", "test:utils": "npm test src/utils", "test:btc2x": "npm test src/tests/btc2x.test.ts", "test:cdeti": "npm test src/tests/cdeti", @@ -36,6 +35,7 @@ "test:eth2x": "npm test src/tests/eth2x.test.ts", "test:eth2xfli": "npm test src/tests/eth2xfli", "test:gtceth": "npm test src/tests/gtceth", + "test:hyeth": "npm test src/tests/hyeth.test.ts", "test:iceth": "npm test src/tests/iceth", "test:icreth": "npm test src/tests/icreth", "test:watch": "jest --watch" diff --git a/src/constants/abis/DebtIssuanceModuleV2.json b/src/constants/abis/DebtIssuanceModuleV2.json new file mode 100644 index 00000000..cdca4885 --- /dev/null +++ b/src/constants/abis/DebtIssuanceModuleV2.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"contract IController","name":"_controller","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"indexed":false,"internalType":"address","name":"_newFeeRecipient","type":"address"}],"name":"FeeRecipientUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"_newIssueFee","type":"uint256"}],"name":"IssueFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"_newRedeemFee","type":"uint256"}],"name":"RedeemFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"indexed":true,"internalType":"address","name":"_issuer","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"address","name":"_hookContract","type":"address"},{"indexed":false,"internalType":"uint256","name":"_quantity","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_managerFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_protocolFee","type":"uint256"}],"name":"SetTokenIssued","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"indexed":true,"internalType":"address","name":"_redeemer","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256","name":"_quantity","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_managerFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_protocolFee","type":"uint256"}],"name":"SetTokenRedeemed","type":"event"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_quantity","type":"uint256"},{"internalType":"bool","name":"_isIssue","type":"bool"}],"name":"calculateTotalFees","outputs":[{"internalType":"uint256","name":"totalQuantity","type":"uint256"},{"internalType":"uint256","name":"managerFee","type":"uint256"},{"internalType":"uint256","name":"protocolFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"controller","outputs":[{"internalType":"contract IController","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"}],"name":"getModuleIssuanceHooks","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_quantity","type":"uint256"}],"name":"getRequiredComponentIssuanceUnits","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_quantity","type":"uint256"}],"name":"getRequiredComponentRedemptionUnits","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_maxManagerFee","type":"uint256"},{"internalType":"uint256","name":"_managerIssueFee","type":"uint256"},{"internalType":"uint256","name":"_managerRedeemFee","type":"uint256"},{"internalType":"address","name":"_feeRecipient","type":"address"},{"internalType":"contract IManagerIssuanceHook","name":"_managerIssuanceHook","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"address","name":"_hook","type":"address"}],"name":"isModuleIssuanceHook","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"","type":"address"}],"name":"issuanceSettings","outputs":[{"internalType":"uint256","name":"maxManagerFee","type":"uint256"},{"internalType":"uint256","name":"managerIssueFee","type":"uint256"},{"internalType":"uint256","name":"managerRedeemFee","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"},{"internalType":"contract IManagerIssuanceHook","name":"managerIssuanceHook","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_quantity","type":"uint256"},{"internalType":"address","name":"_to","type":"address"}],"name":"issue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_quantity","type":"uint256"},{"internalType":"address","name":"_to","type":"address"}],"name":"redeem","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"}],"name":"registerToIssuanceModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"removeModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"}],"name":"unregisterFromIssuanceModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"address","name":"_newFeeRecipient","type":"address"}],"name":"updateFeeRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_newIssueFee","type":"uint256"}],"name":"updateIssueFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_newRedeemFee","type":"uint256"}],"name":"updateRedeemFee","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/src/constants/abis/FlashMintHyEth.json b/src/constants/abis/FlashMintHyEth.json new file mode 100644 index 00000000..8e3ea894 --- /dev/null +++ b/src/constants/abis/FlashMintHyEth.json @@ -0,0 +1 @@ +[{"inputs":[{"components":[{"internalType":"address","name":"quickRouter","type":"address"},{"internalType":"address","name":"sushiRouter","type":"address"},{"internalType":"address","name":"uniV3Router","type":"address"},{"internalType":"address","name":"uniV3Quoter","type":"address"},{"internalType":"address","name":"curveAddressProvider","type":"address"},{"internalType":"address","name":"curveCalculator","type":"address"},{"internalType":"address","name":"weth","type":"address"}],"internalType":"struct DEXAdapterV2.Addresses","name":"_dexAddresses","type":"tuple"},{"internalType":"contract IController","name":"_setController","type":"address"},{"internalType":"contract IDebtIssuanceModule","name":"_issuanceModule","type":"address"},{"internalType":"contract IStETH","name":"_stETH","type":"address"},{"internalType":"address","name":"_stEthETHPool","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_recipient","type":"address"},{"indexed":true,"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"indexed":true,"internalType":"contract IERC20","name":"_inputToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"_amountInputToken","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_amountSetIssued","type":"uint256"}],"name":"FlashMint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_recipient","type":"address"},{"indexed":true,"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"indexed":true,"internalType":"contract IERC20","name":"_outputToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"_amountSetRedeemed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_amountOutputToken","type":"uint256"}],"name":"FlashRedeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"ROUNDING_ERROR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acrossPool","outputs":[{"internalType":"contract IAcrossHubPoolV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acrossToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"}],"name":"approveSetToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"_token","type":"address"},{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_allowance","type":"uint256"}],"name":"approveToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dexAdapter","outputs":[{"internalType":"address","name":"quickRouter","type":"address"},{"internalType":"address","name":"sushiRouter","type":"address"},{"internalType":"address","name":"uniV3Router","type":"address"},{"internalType":"address","name":"uniV3Quoter","type":"address"},{"internalType":"address","name":"curveAddressProvider","type":"address"},{"internalType":"address","name":"curveCalculator","type":"address"},{"internalType":"address","name":"weth","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"issuanceModule","outputs":[{"internalType":"contract IDebtIssuanceModule","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_amountSetToken","type":"uint256"},{"internalType":"contract IERC20","name":"_inputToken","type":"address"},{"internalType":"uint256","name":"_maxInputTokenAmount","type":"uint256"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"enum DEXAdapterV2.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV2.SwapData","name":"_swapDataInputTokenToEth","type":"tuple"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"enum DEXAdapterV2.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV2.SwapData","name":"_swapDataEthToInputToken","type":"tuple"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"enum DEXAdapterV2.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV2.SwapData[]","name":"_swapDataEthToComponent","type":"tuple[]"}],"name":"issueExactSetFromERC20","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_amountSetToken","type":"uint256"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"enum DEXAdapterV2.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV2.SwapData[]","name":"_swapDataEthToComponent","type":"tuple[]"}],"name":"issueExactSetFromETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IPendleMarketV3","name":"","type":"address"}],"name":"pendleMarketData","outputs":[{"internalType":"contract IPendlePrincipalToken","name":"pt","type":"address"},{"internalType":"contract IPendleStandardizedYield","name":"sy","type":"address"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"uint256","name":"exchangeRateFactor","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IPendlePrincipalToken","name":"","type":"address"}],"name":"pendleMarkets","outputs":[{"internalType":"contract IPendleMarketV3","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_amountSetToken","type":"uint256"},{"internalType":"contract IERC20","name":"_outputToken","type":"address"},{"internalType":"uint256","name":"_minOutputTokenAmount","type":"uint256"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"enum DEXAdapterV2.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV2.SwapData","name":"_swapDataEthToOutputToken","type":"tuple"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"enum DEXAdapterV2.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV2.SwapData[]","name":"_swapDataComponentToEth","type":"tuple[]"}],"name":"redeemExactSetForERC20","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_amountSetToken","type":"uint256"},{"internalType":"uint256","name":"_minETHOut","type":"uint256"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"enum DEXAdapterV2.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV2.SwapData[]","name":"_swapDataComponentToEth","type":"tuple[]"}],"name":"redeemExactSetForETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setController","outputs":[{"internalType":"contract IController","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IPendlePrincipalToken","name":"_pt","type":"address"},{"internalType":"contract IPendleStandardizedYield","name":"_sy","type":"address"},{"internalType":"address","name":"_underlying","type":"address"},{"internalType":"contract IPendleMarketV3","name":"_market","type":"address"},{"internalType":"uint256","name":"_exchangeRateFactor","type":"uint256"}],"name":"setPendleMarket","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_inputToken","type":"address"},{"internalType":"address","name":"_outputToken","type":"address"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"enum DEXAdapterV2.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV2.SwapData","name":"_swapData","type":"tuple"}],"name":"setSwapData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stETH","outputs":[{"internalType":"contract IStETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"_ptToAccount","type":"int256"},{"internalType":"int256","name":"_syToAccount","type":"int256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"swapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"swapData","outputs":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"enum DEXAdapterV2.Exchange","name":"exchange","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20[]","name":"_tokens","type":"address[]"},{"internalType":"address payable","name":"_to","type":"address"}],"name":"withdrawTokens","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/src/constants/addresses.ts b/src/constants/addresses.ts new file mode 100644 index 00000000..5ededf3b --- /dev/null +++ b/src/constants/addresses.ts @@ -0,0 +1,2 @@ +export const AddressZero = '0x0000000000000000000000000000000000000000' +export const EthAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' diff --git a/src/constants/contracts.ts b/src/constants/contracts.ts index fd26dd14..6b3c8fbe 100644 --- a/src/constants/contracts.ts +++ b/src/constants/contracts.ts @@ -1,4 +1,7 @@ // Index Protocol +export const FlashMintHyEthAddress = + '0xC290c371F5a36970AfF00bbffDBFADd81820109a' + export const FlashMintLeveragedAddress = '0x45c00508C14601fd1C1e296eB3C0e3eEEdCa45D0' diff --git a/src/constants/index.ts b/src/constants/index.ts index 57b46692..4564651c 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,3 +1,4 @@ +export * from './addresses' export * from './chains' export * from './contracts' export * from './swapdata' diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index 203095c1..1d8ac681 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -44,6 +44,11 @@ export const GitcoinStakedETHIndex: Token = { symbol: 'gtcETH', } +export const HighYieldETHIndex: Token = { + address: '0xc4506022Fb8090774E8A628d5084EED61D9B99Ee', + symbol: 'hyETH', +} + export const IndexCoopBitcoin2xIndex: Token = { address: '0xD2AC55cA3Bbd2Dd1e9936eC640dCb4b745fDe759', addressArbitrum: '0xeb5bE62e6770137beaA0cC712741165C594F59D7', diff --git a/src/flashmint/builders/hyeth.test.ts b/src/flashmint/builders/hyeth.test.ts new file mode 100644 index 00000000..e97e7aab --- /dev/null +++ b/src/flashmint/builders/hyeth.test.ts @@ -0,0 +1,188 @@ +import { BigNumber } from '@ethersproject/bignumber' + +import { EthAddress } from 'constants/addresses' +import { FlashMintHyEthAddress } from 'constants/contracts' +import { + LocalhostProvider, + LocalhostProviderUrl, + QuoteTokens, +} from 'tests/utils' +import { getFlashMintHyEthContract } from 'utils/contracts' +import { wei } from 'utils/numbers' +import { Exchange } from 'utils/swap-data' +import { getComponentsSwapData } from 'quote/flashmint/hyeth/swap-data' + +import { + FlashMintHyEthBuildRequest, + FlashMintHyEthTransactionBuilder, +} from './hyeth' + +const provider = LocalhostProvider +const rpcUrl = LocalhostProviderUrl + +const { hyeth, usdc, weth } = QuoteTokens + +const eth = EthAddress +const indexToken = hyeth + +describe('FlashMintHyEthTransactionBuilder()', () => { + const contract = getFlashMintHyEthContract(provider) + + beforeEach((): void => { + jest.setTimeout(10000000) + }) + + test('returns null for invalid request (no input token)', async () => { + const buildRequest = createBuildRequest() + buildRequest.inputToken = '' + const builder = new FlashMintHyEthTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns null for invalid request (no output token)', async () => { + const buildRequest = createBuildRequest() + buildRequest.outputToken = '' + const builder = new FlashMintHyEthTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns null for invalid request (indexTokenAmount = 0)', async () => { + const buildRequest = createBuildRequest() + // isMinting: true + buildRequest.outputTokenAmount = BigNumber.from(0) + const builder = new FlashMintHyEthTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns null for invalid request (inputTokenAmount = 0)', async () => { + const buildRequest = createBuildRequest() + // isMinting: true + buildRequest.inputTokenAmount = BigNumber.from(0) + const builder = new FlashMintHyEthTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns null for invalid request (no component quotes)', async () => { + const buildRequest = createBuildRequest() + buildRequest.componentsSwapData = [] + const builder = new FlashMintHyEthTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns a tx for minting hyETH (ERC20)', async () => { + const buildRequest = createBuildRequest() + const refTx = await contract.populateTransaction.issueExactSetFromERC20( + buildRequest.outputToken, + buildRequest.outputTokenAmount, + buildRequest.inputToken, + buildRequest.inputTokenAmount, + buildRequest.swapDataInputTokenToEth, + buildRequest.swapDataEthToInputOutputToken, + buildRequest.componentsSwapData + ) + const builder = new FlashMintHyEthTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + if (!tx) fail() + expect(tx.to).toBe(FlashMintHyEthAddress) + expect(tx.data).toEqual(refTx.data) + }) + + test('returns a tx for minting hyETH (ETH)', async () => { + const buildRequest = createBuildRequest(true, eth, 'ETH') + const refTx = await contract.populateTransaction.issueExactSetFromETH( + buildRequest.outputToken, + buildRequest.outputTokenAmount, + buildRequest.componentsSwapData, + { value: buildRequest.inputTokenAmount } + ) + const builder = new FlashMintHyEthTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + if (!tx) fail() + expect(tx.to).toBe(FlashMintHyEthAddress) + expect(tx.data).toEqual(refTx.data) + expect(tx.value).toEqual(buildRequest.inputTokenAmount) + }) + + test('returns a tx for redeeming hyETH (ERC20)', async () => { + const buildRequest = createBuildRequest( + false, + indexToken.address, + 'hyETH', + usdc.address, + usdc.symbol + ) + const refTx = await contract.populateTransaction.redeemExactSetForERC20( + buildRequest.inputToken, + buildRequest.inputTokenAmount, + buildRequest.outputToken, + buildRequest.outputTokenAmount, + buildRequest.swapDataEthToInputOutputToken, + buildRequest.componentsSwapData + ) + const builder = new FlashMintHyEthTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + if (!tx) fail() + expect(tx.to).toBe(FlashMintHyEthAddress) + expect(tx.data).toEqual(refTx.data) + }) + + test('returns a tx for redeeming hyETH (ETH)', async () => { + const buildRequest = createBuildRequest( + false, + indexToken.address, + 'hyETH', + eth, + 'ETH' + ) + const refTx = await contract.populateTransaction.redeemExactSetForETH( + buildRequest.inputToken, + buildRequest.inputTokenAmount, + buildRequest.outputTokenAmount, + buildRequest.componentsSwapData + ) + const builder = new FlashMintHyEthTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + if (!tx) fail() + expect(tx.to).toBe(FlashMintHyEthAddress) + expect(tx.data).toEqual(refTx.data) + }) +}) + +function createBuildRequest( + isMinting = true, + inputToken: string = usdc.address, + inputTokenSymbol: string = usdc.symbol, + outputToken: string = indexToken.address, + outputTokenSymbol: string = indexToken.symbol +): FlashMintHyEthBuildRequest { + const swapDataInputTokenToEth = { + exchange: Exchange.UniV3, + path: [usdc.address, weth.address], + fees: [500], + pool: '0xDC24316b9AE028F1497c275EB9192a3Ea0f67022', + } + const swapDataEthToInputOutputToken = { + exchange: Exchange.UniV3, + path: [weth.address, usdc.address], + fees: [500], + pool: '0xDC24316b9AE028F1497c275EB9192a3Ea0f67022', + } + const componentsSwapData = getComponentsSwapData() + return { + isMinting, + inputToken, + inputTokenSymbol, + outputToken, + outputTokenSymbol, + inputTokenAmount: isMinting ? BigNumber.from(194235680) : wei(1), + outputTokenAmount: isMinting ? wei(1) : BigNumber.from(194235680), + componentsSwapData, + swapDataInputTokenToEth, + swapDataEthToInputOutputToken, + } +} diff --git a/src/flashmint/builders/hyeth.ts b/src/flashmint/builders/hyeth.ts new file mode 100644 index 00000000..62a41654 --- /dev/null +++ b/src/flashmint/builders/hyeth.ts @@ -0,0 +1,139 @@ +import { TransactionRequest } from '@ethersproject/abstract-provider' +import { BigNumber } from '@ethersproject/bignumber' + +import { getFlashMintHyEthContract } from 'utils/contracts' +import { getRpcProvider } from 'utils/rpc-provider' +import { Exchange, SwapData } from 'utils/swap-data' + +import { TransactionBuilder } from './interface' +import { isEmptyString, isInvalidAmount } from './utils' + +export interface FlashMintHyEthBuildRequest { + isMinting: boolean + inputToken: string + inputTokenSymbol: string + outputToken: string + outputTokenSymbol: string + inputTokenAmount: BigNumber + outputTokenAmount: BigNumber + componentsSwapData: SwapData[] + swapDataInputTokenToEth: SwapData | null + swapDataEthToInputOutputToken: SwapData | null +} + +export class FlashMintHyEthTransactionBuilder + implements TransactionBuilder +{ + constructor(private readonly rpcUrl: string) {} + + async build( + request: FlashMintHyEthBuildRequest + ): Promise { + if (!this.isValidRequest(request)) return null + const provider = getRpcProvider(this.rpcUrl) + const { + componentsSwapData, + inputToken, + inputTokenSymbol, + inputTokenAmount, + outputToken, + outputTokenSymbol, + outputTokenAmount, + isMinting, + swapDataInputTokenToEth, + swapDataEthToInputOutputToken, + } = request + const indexToken = isMinting ? outputToken : inputToken + const indexTokenAmount = isMinting ? outputTokenAmount : inputTokenAmount + const contract = getFlashMintHyEthContract(provider) + if (isMinting) { + if (inputTokenSymbol === 'ETH') { + return await contract.populateTransaction.issueExactSetFromETH( + indexToken, + indexTokenAmount, + componentsSwapData, + { value: inputTokenAmount } + ) + } else { + return await contract.populateTransaction.issueExactSetFromERC20( + indexToken, + indexTokenAmount, + inputToken, + inputTokenAmount, // _maxInputTokenAmount + swapDataInputTokenToEth, + swapDataEthToInputOutputToken, + componentsSwapData + ) + } + } else { + if (outputTokenSymbol === 'ETH') { + return await contract.populateTransaction.redeemExactSetForETH( + indexToken, + indexTokenAmount, + outputTokenAmount, // _minETHOut + componentsSwapData + ) + } else { + return await contract.populateTransaction.redeemExactSetForERC20( + indexToken, + indexTokenAmount, + outputToken, + outputTokenAmount, // _minOutputTokenAmount + swapDataEthToInputOutputToken, + componentsSwapData + ) + } + } + } + + private isValidSwapData(swapData: SwapData | null): boolean { + if (!swapData) return false + if (swapData.exchange === Exchange.None) { + if (swapData.pool.length !== 42) return false + return true + } + if ( + swapData.exchange === Exchange.UniV3 && + swapData.fees.length !== swapData.path.length - 1 + ) + return false + if (swapData.path.length === 0) return false + if (swapData.pool.length !== 42) return false + return true + } + + private isValidRequest(request: FlashMintHyEthBuildRequest): boolean { + const { + componentsSwapData, + inputToken, + inputTokenAmount, + inputTokenSymbol, + isMinting, + outputToken, + outputTokenAmount, + outputTokenSymbol, + swapDataEthToInputOutputToken, + swapDataInputTokenToEth, + } = request + if (isEmptyString(inputToken)) return false + if (isEmptyString(inputTokenSymbol)) return false + if (isEmptyString(outputToken)) return false + if (isEmptyString(outputTokenSymbol)) return false + if (isInvalidAmount(inputTokenAmount)) return false + if (isInvalidAmount(outputTokenAmount)) return false + if (componentsSwapData.length === 0) return false + if ( + isMinting && + inputTokenSymbol !== 'ETH' && + !this.isValidSwapData(swapDataInputTokenToEth) + ) + return false + if ( + ((isMinting && inputTokenSymbol !== 'ETH') || + (!isMinting && outputTokenSymbol !== 'ETH')) && + !this.isValidSwapData(swapDataEthToInputOutputToken) + ) + return false + return true + } +} diff --git a/src/flashmint/builders/index.ts b/src/flashmint/builders/index.ts index 8d089cf8..d5b66f0f 100644 --- a/src/flashmint/builders/index.ts +++ b/src/flashmint/builders/index.ts @@ -1,3 +1,4 @@ +export * from './hyeth' export * from './interface' export * from './leveraged' export * from './leveraged-extended' diff --git a/src/quote/flashmint/hyeth/component-quotes/Vault.json b/src/quote/flashmint/hyeth/component-quotes/Vault.json new file mode 100644 index 00000000..bd320eb1 --- /dev/null +++ b/src/quote/flashmint/hyeth/component-quotes/Vault.json @@ -0,0 +1,1732 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint8", + "name": "status", + "type": "uint8" + } + ], + "name": "LogChangeStatus", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "stEthAmount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "protocolId", + "type": "uint256" + } + ], + "name": "LogClaimEthWithdrawal", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "fromNFTId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ethClaimed", + "type": "uint256" + } + ], + "name": "LogClaimSteth", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "fromNFTId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ethClaimed", + "type": "uint256" + } + ], + "name": "LogClaimStethAndPaybackFluid", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "LogCollectRevenue", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "ethAmount", + "type": "uint256" + } + ], + "name": "LogEthSweep", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint8", + "name": "protocol", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "withdrawAmount", + "type": "uint256" + } + ], + "name": "LogFillVaultAvailability", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "iTokenAmount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "route", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "deleverageWethAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "withdrawStETHAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "userNetDeposit", + "type": "uint256" + } + ], + "name": "LogImportV1ETHVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint8", + "name": "protocol", + "type": "uint8" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "route", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wstETHflashAmt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ethAmountBorrow", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "vaults", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "vaultAmts", + "type": "uint256[]" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "swapMode", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "unitAmt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "vaultSwapAmt", + "type": "uint256" + } + ], + "name": "LogLeverage", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "fromProtocolId", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "route", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wETHBorrowAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "withdrawStethAmount", + "type": "uint256" + } + ], + "name": "LogQueueSteth", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint8", + "name": "protocolFrom", + "type": "uint8" + }, + { + "indexed": true, + "internalType": "uint8", + "name": "protocolTo", + "type": "uint8" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "route", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wstETHflashAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wETHBorrowAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "withdrawAmount", + "type": "uint256" + } + ], + "name": "LogRefinance", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "oldAggrMaxVaultRatio", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "aggrMaxVaultRatio", + "type": "uint256" + } + ], + "name": "LogUpdateAggrMaxVaultRatio", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "exchangePriceBefore", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "exchangePriceAfter", + "type": "uint256" + } + ], + "name": "LogUpdateExchangePrice", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "revenueFeePercentage", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "withdrawalFeePercentage", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "withdrawFeeAbsoluteMin", + "type": "uint256" + } + ], + "name": "LogUpdateFees", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "oldLimit", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "newLimit", + "type": "uint256" + } + ], + "name": "LogUpdateLeverageMaxUnitAmountLimit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint8", + "name": "protocolId", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newRiskRatio", + "type": "uint256" + } + ], + "name": "LogUpdateMaxRiskRatio", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "rebalancer", + "type": "address" + }, + { + "indexed": true, + "internalType": "bool", + "name": "isRebalancer", + "type": "bool" + } + ], + "name": "LogUpdateRebalancer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldSecondaryAuth", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "secondaryAuth", + "type": "address" + } + ], + "name": "LogUpdateSecondaryAuth", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldTreasury", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newTreasury", + "type": "address" + } + ], + "name": "LogUpdateTreasury", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint8", + "name": "protocol", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "depositAmount", + "type": "uint256" + } + ], + "name": "LogVaultToProtocolDeposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "protocolId", + "type": "uint256" + } + ], + "name": "LogWethPayback", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "wethAmount", + "type": "uint256" + } + ], + "name": "LogWethSweep", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "payer", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "LogWithdrawFeeCollected", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [ + { "internalType": "address", "name": "auth_", "type": "address" } + ], + "name": "addDSAAuth", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "aggrMaxVaultRatio", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "asset", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "underlying_", "type": "address" } + ], + "name": "borrowBalanceMorphoAaveV3", + "outputs": [ + { "internalType": "uint256", "name": "totalBalance_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint8", "name": "status_", "type": "uint8" }], + "name": "changeVaultStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "requestId_", "type": "uint256" }, + { "internalType": "uint8", "name": "toProtocolId_", "type": "uint8" } + ], + "name": "claimEthWithdrawal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "fromNftID_", "type": "uint256" } + ], + "name": "claimSteth", + "outputs": [ + { "internalType": "uint256", "name": "claimed_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "fromNftID_", "type": "uint256" } + ], + "name": "claimStethAndPaybackFluid", + "outputs": [ + { "internalType": "uint256", "name": "ethClaimed_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "underlying_", "type": "address" } + ], + "name": "collateralBalanceMorphoAaveV3", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralBalance_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amount_", "type": "uint256" } + ], + "name": "collectRevenue", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "shares", "type": "uint256" } + ], + "name": "convertToAssets", + "outputs": [ + { "internalType": "uint256", "name": "assets", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "assets", "type": "uint256" } + ], + "name": "convertToShares", + "outputs": [ + { "internalType": "uint256", "name": "shares", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "assets_", "type": "uint256" }, + { "internalType": "address", "name": "receiver_", "type": "address" } + ], + "name": "deposit", + "outputs": [ + { "internalType": "uint256", "name": "shares_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "exchangePrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint8", "name": "protocolId_", "type": "uint8" }, + { + "internalType": "uint256", + "name": "withdrawAmount_", + "type": "uint256" + } + ], + "name": "fillVaultAvailability", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getNetAssets", + "outputs": [ + { "internalType": "uint256", "name": "totalAssets_", "type": "uint256" }, + { "internalType": "uint256", "name": "totalDebt_", "type": "uint256" }, + { "internalType": "uint256", "name": "netAssets_", "type": "uint256" }, + { + "internalType": "uint256", + "name": "aggregatedRatio_", + "type": "uint256" + }, + { + "components": [ + { + "components": [ + { "internalType": "uint256", "name": "stETH", "type": "uint256" }, + { "internalType": "uint256", "name": "wETH", "type": "uint256" } + ], + "internalType": "struct IViewModule.ProtocolAssetsInStETH", + "name": "aaveV2", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "wstETH", + "type": "uint256" + }, + { "internalType": "uint256", "name": "wETH", "type": "uint256" } + ], + "internalType": "struct IViewModule.ProtocolAssetsInWstETH", + "name": "aaveV3", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "wstETH", + "type": "uint256" + }, + { "internalType": "uint256", "name": "wETH", "type": "uint256" } + ], + "internalType": "struct IViewModule.ProtocolAssetsInWstETH", + "name": "compoundV3", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "wstETH", + "type": "uint256" + }, + { "internalType": "uint256", "name": "wETH", "type": "uint256" } + ], + "internalType": "struct IViewModule.ProtocolAssetsInWstETH", + "name": "euler", + "type": "tuple" + }, + { + "components": [ + { "internalType": "uint256", "name": "stETH", "type": "uint256" }, + { "internalType": "uint256", "name": "wETH", "type": "uint256" } + ], + "internalType": "struct IViewModule.ProtocolAssetsInStETH", + "name": "morphoAaveV2", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "wstETH", + "type": "uint256" + }, + { "internalType": "uint256", "name": "wETH", "type": "uint256" } + ], + "internalType": "struct IViewModule.ProtocolAssetsInWstETH", + "name": "morphoAaveV3", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "wstETH", + "type": "uint256" + }, + { "internalType": "uint256", "name": "wETH", "type": "uint256" } + ], + "internalType": "struct IViewModule.ProtocolAssetsInWstETH", + "name": "spark", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "wstETH", + "type": "uint256" + }, + { "internalType": "uint256", "name": "wETH", "type": "uint256" } + ], + "internalType": "struct IViewModule.ProtocolAssetsInWstETH", + "name": "fluid", + "type": "tuple" + }, + { + "components": [ + { "internalType": "uint256", "name": "stETH", "type": "uint256" }, + { + "internalType": "uint256", + "name": "wstETH", + "type": "uint256" + }, + { "internalType": "uint256", "name": "wETH", "type": "uint256" } + ], + "internalType": "struct IViewModule.IdealBalances", + "name": "vaultBalances", + "type": "tuple" + }, + { + "components": [ + { "internalType": "uint256", "name": "stETH", "type": "uint256" }, + { + "internalType": "uint256", + "name": "wstETH", + "type": "uint256" + }, + { "internalType": "uint256", "name": "wETH", "type": "uint256" } + ], + "internalType": "struct IViewModule.IdealBalances", + "name": "dsaBalances", + "type": "tuple" + } + ], + "internalType": "struct IViewModule.NetAssetsHelper", + "name": "assets_", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint8", "name": "protocolId_", "type": "uint8" } + ], + "name": "getProtocolRatio", + "outputs": [ + { "internalType": "uint256", "name": "ratio_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRatioAaveV2", + "outputs": [ + { "internalType": "uint256", "name": "stEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ethAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ratio_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "stEthPerWsteth_", + "type": "uint256" + } + ], + "name": "getRatioAaveV3", + "outputs": [ + { "internalType": "uint256", "name": "wstEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "stEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ethAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ratio_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "stEthPerWsteth_", + "type": "uint256" + } + ], + "name": "getRatioCompoundV3", + "outputs": [ + { "internalType": "uint256", "name": "wstEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "stEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ethAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ratio_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "stEthPerWsteth_", + "type": "uint256" + } + ], + "name": "getRatioEuler", + "outputs": [ + { "internalType": "uint256", "name": "wstEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "stEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ethAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ratio_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "stEthPerWsteth_", + "type": "uint256" + } + ], + "name": "getRatioFluid", + "outputs": [ + { "internalType": "uint256", "name": "wstEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "stEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ethAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ratio_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRatioMorphoAaveV2", + "outputs": [ + { "internalType": "uint256", "name": "stEthAmount_", "type": "uint256" }, + { + "internalType": "uint256", + "name": "stEthAmountPool_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stEthAmountP2P_", + "type": "uint256" + }, + { "internalType": "uint256", "name": "ethAmount_", "type": "uint256" }, + { + "internalType": "uint256", + "name": "ethAmountPool_", + "type": "uint256" + }, + { "internalType": "uint256", "name": "ethAmountP2P_", "type": "uint256" }, + { "internalType": "uint256", "name": "ratio_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "stEthPerWsteth_", + "type": "uint256" + } + ], + "name": "getRatioMorphoAaveV3", + "outputs": [ + { "internalType": "uint256", "name": "wstEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "stEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ethAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ratio_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "stEthPerWsteth_", + "type": "uint256" + } + ], + "name": "getRatioSpark", + "outputs": [ + { "internalType": "uint256", "name": "wstEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "stEthAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ethAmount_", "type": "uint256" }, + { "internalType": "uint256", "name": "ratio_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "stETHAmount_", "type": "uint256" } + ], + "name": "getWithdrawFee", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "route_", "type": "uint256" }, + { + "internalType": "uint256", + "name": "deleverageWETHAmount_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawStETHAmount_", + "type": "uint256" + }, + { "internalType": "address", "name": "receiver_", "type": "address" } + ], + "name": "importPosition", + "outputs": [ + { "internalType": "uint256", "name": "shares_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "isRebalancer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint8", "name": "protocolId_", "type": "uint8" }, + { "internalType": "uint256", "name": "route_", "type": "uint256" }, + { + "internalType": "uint256", + "name": "wstETHflashAmount_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "wETHBorrowAmount_", + "type": "uint256" + }, + { "internalType": "address[]", "name": "vaults_", "type": "address[]" }, + { + "internalType": "uint256[]", + "name": "vaultAmounts_", + "type": "uint256[]" + }, + { "internalType": "uint256", "name": "swapMode_", "type": "uint256" }, + { "internalType": "uint256", "name": "unitAmount_", "type": "uint256" }, + { "internalType": "bytes", "name": "oneInchData_", "type": "bytes" } + ], + "name": "leverage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "leverageMaxUnitAmountLimit", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "maxDeposit", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "maxMint", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "maxRedeem", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "name": "maxRiskRatio", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "maxWithdraw", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "shares_", "type": "uint256" }, + { "internalType": "address", "name": "receiver_", "type": "address" } + ], + "name": "mint", + "outputs": [ + { "internalType": "uint256", "name": "assets_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "bytes", "name": "", "type": "bytes" } + ], + "name": "onERC721Received", + "outputs": [{ "internalType": "bytes4", "name": "", "type": "bytes4" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint8", "name": "toProtocolId_", "type": "uint8" } + ], + "name": "paybackDebt", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "assets", "type": "uint256" } + ], + "name": "previewDeposit", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "shares", "type": "uint256" } + ], + "name": "previewMint", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "shares", "type": "uint256" } + ], + "name": "previewRedeem", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "assets", "type": "uint256" } + ], + "name": "previewWithdraw", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint8", "name": "fromProtocolId_", "type": "uint8" }, + { "internalType": "uint256", "name": "route_", "type": "uint256" }, + { + "internalType": "uint256", + "name": "wETHBorrowAmount_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawStethAmount_", + "type": "uint256" + } + ], + "name": "queueSteth", + "outputs": [ + { + "internalType": "uint256", + "name": "ratioFromProtocol_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "queuedWithdrawStEth", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "slot_", "type": "bytes32" } + ], + "name": "readFromStorage", + "outputs": [ + { "internalType": "uint256", "name": "result_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "shares_", "type": "uint256" }, + { "internalType": "address", "name": "receiver_", "type": "address" }, + { "internalType": "address", "name": "owner_", "type": "address" } + ], + "name": "redeem", + "outputs": [ + { + "internalType": "uint256", + "name": "assetsAfterFee_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newAggrMaxVaultRatio_", + "type": "uint256" + } + ], + "name": "reduceAggrMaxVaultRatio", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint8[]", "name": "protocolId_", "type": "uint8[]" }, + { + "internalType": "uint256[]", + "name": "newRiskRatio_", + "type": "uint256[]" + } + ], + "name": "reduceMaxRiskRatio", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint8", "name": "fromProtocolId_", "type": "uint8" }, + { "internalType": "uint8", "name": "toProtocolId_", "type": "uint8" }, + { "internalType": "uint256", "name": "route_", "type": "uint256" }, + { + "internalType": "uint256", + "name": "wstETHflashAmount_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "wETHBorrowAmount_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawAmount_", + "type": "uint256" + } + ], + "name": "refinance", + "outputs": [ + { + "internalType": "uint256", + "name": "ratioFromProtocol_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "ratioToProtocol_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "revenue", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "revenueExchangePrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "revenueFeePercentage", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "secondaryAuth", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to_", "type": "address" }, + { "internalType": "bytes", "name": "calldata_", "type": "bytes" }, + { "internalType": "uint256", "name": "value_", "type": "uint256" }, + { "internalType": "uint256", "name": "operation_", "type": "uint256" } + ], + "name": "spell", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "sweepEthToSteth", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sweepWethToSteth", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalAssets", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "treasury", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newAggrMaxVaultRatio_", + "type": "uint256" + } + ], + "name": "updateAggrMaxVaultRatio", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "updateExchangePrice", + "outputs": [ + { + "internalType": "uint256", + "name": "newExchangePrice_", + "type": "uint256" + }, + { "internalType": "uint256", "name": "newRevenue_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "revenueFeePercent_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawalFeePercent_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawFeeAbsoluteMin_", + "type": "uint256" + } + ], + "name": "updateFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "newLimit_", "type": "uint256" } + ], + "name": "updateLeverageMaxUnitAmountLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint8[]", "name": "protocolId_", "type": "uint8[]" }, + { + "internalType": "uint256[]", + "name": "newRiskRatio_", + "type": "uint256[]" + } + ], + "name": "updateMaxRiskRatio", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rebalancer_", "type": "address" }, + { "internalType": "bool", "name": "isRebalancer_", "type": "bool" } + ], + "name": "updateRebalancer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "secondaryAuth_", "type": "address" } + ], + "name": "updateSecondaryAuth", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newTreasury_", "type": "address" } + ], + "name": "updateTreasury", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vaultDSA", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint8", "name": "protocolId_", "type": "uint8" }, + { "internalType": "uint256", "name": "depositAmount_", "type": "uint256" } + ], + "name": "vaultToProtocolDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "assets_", "type": "uint256" }, + { "internalType": "address", "name": "receiver_", "type": "address" }, + { "internalType": "address", "name": "owner_", "type": "address" } + ], + "name": "withdraw", + "outputs": [ + { "internalType": "uint256", "name": "shares_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawFeeAbsoluteMin", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalFeePercentage", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] diff --git a/src/quote/flashmint/hyeth/component-quotes/across.ts b/src/quote/flashmint/hyeth/component-quotes/across.ts new file mode 100644 index 00000000..45ad2496 --- /dev/null +++ b/src/quote/flashmint/hyeth/component-quotes/across.ts @@ -0,0 +1,72 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { BigNumber } from '@ethersproject/bignumber' +import { Contract } from '@ethersproject/contracts' + +import { WETH } from 'constants/tokens' +import { SwapQuoteProvider } from 'quote/swap' +import { isSameAddress } from 'utils/addresses' +import { getRpcProvider } from 'utils/rpc-provider' + +export class AcrossQuoteProvider { + readonly acrossPool = '0xc186fA914353c44b2E33eBE05f21846F1048bEda' + // https://github.com/IndexCoop/index-coop-smart-contracts/blob/72243db162ad34124db681aad363746bed075944/contracts/exchangeIssuance/FlashMintHyETH.sol#L59 + readonly roundingError = BigInt(10) + readonly weth = WETH.address! + + constructor( + private readonly rpcUrl: string, + private readonly swapQuoteProvider: SwapQuoteProvider + ) {} + + getPoolContract(): Contract { + const provider = getRpcProvider(this.rpcUrl) + const abi = [ + 'function exchangeRateCurrent(address l1Token) public returns (uint256)', + ] + return new Contract(this.acrossPool, abi, provider) + } + + async getDepositQuote( + acrossLpAmount: bigint, + inputToken: string + ): Promise { + const outputToken = this.weth + const pool = this.getPoolContract() + const exchangeRate: BigNumber = await pool.callStatic.exchangeRateCurrent( + this.weth + ) + const ethAmount = + (exchangeRate.toBigInt() * acrossLpAmount) / BigInt(1e18) + + this.roundingError + if (isSameAddress(inputToken, outputToken)) return ethAmount + const quote = await this.swapQuoteProvider.getSwapQuote({ + chainId: 1, + inputToken, + outputToken, + outputAmount: ethAmount.toString(), + }) + if (!quote) return null + return BigInt(quote.inputAmount) + } + + async getWithdrawQuote( + acrossLpAmount: bigint, + outputToken: string + ): Promise { + const inputToken = this.weth + const pool = this.getPoolContract() + const exchangeRate: BigNumber = await pool.callStatic.exchangeRateCurrent( + this.weth + ) + const ethAmount = (exchangeRate.toBigInt() * acrossLpAmount) / BigInt(1e18) + if (isSameAddress(inputToken, outputToken)) return ethAmount + const quote = await this.swapQuoteProvider.getSwapQuote({ + chainId: 1, + inputToken, + outputToken, + inputAmount: ethAmount.toString(), + }) + if (!quote) return null + return BigInt(quote.outputAmount) + } +} diff --git a/src/quote/flashmint/hyeth/component-quotes/index.ts b/src/quote/flashmint/hyeth/component-quotes/index.ts new file mode 100644 index 00000000..761eb472 --- /dev/null +++ b/src/quote/flashmint/hyeth/component-quotes/index.ts @@ -0,0 +1,162 @@ +import { BigNumber } from '@ethersproject/bignumber' +import { Address, isAddressEqual } from 'viem' + +import { SwapQuoteProvider } from 'quote/swap' + +import { QuoteToken } from '../../../interfaces' + +import { AcrossQuoteProvider } from './across' +import { InstadappQuoteProvider } from './instadapp' +import { PendleQuoteProvider } from './pendle' + +interface ComponentQuotesResult { + componentQuotes: string[] + inputOutputTokenAmount: bigint +} + +export class ComponentQuotesProvider { + constructor( + readonly chainId: number, + readonly slippage: number, + readonly wethAddress: string, + readonly rpcUrl: string, + readonly swapQuoteProvider: SwapQuoteProvider + ) {} + + isAcross(token: string) { + return isAddressEqual( + token as Address, + '0x28F77208728B0A45cAb24c4868334581Fe86F95B' + ) + } + + isInstdapp(token: string) { + return isAddressEqual( + token as Address, + '0xA0D3707c569ff8C87FA923d3823eC5D81c98Be78' + ) + } + + isPendle(token: string) { + const pendleTokens: Address[] = [ + '0x1c085195437738d73d75DC64bC5A3E098b7f93b1', + '0x6ee2b5E19ECBa773a352E5B21415Dc419A700d1d', + '0xf7906F274c174A52d444175729E3fa98f9bde285', + ] + return pendleTokens.some((pendleToken) => + isAddressEqual(pendleToken, token as Address) + ) + } + + async getComponentQuotes( + components: string[], + positions: BigNumber[], + isMinting: boolean, + inputToken: QuoteToken, + outputToken: QuoteToken + ): Promise { + if (components.length === 0 || positions.length === 0) return null + if (components.length !== positions.length) return null + + const { swapQuoteProvider } = this + + const inputTokenAddress = this.getTokenAddressOrWeth(inputToken) + const outputTokenAddress = this.getTokenAddressOrWeth(outputToken) + + const quotePromises: Promise[] = [] + + for (let i = 0; i < components.length; i += 1) { + const index = i + const component = components[index] + const amount = positions[index].toBigInt() + + if (this.isAcross(component)) { + const acrossQuoteProvider = new AcrossQuoteProvider( + this.rpcUrl, + swapQuoteProvider + ) + if (isMinting) { + const quotePromise = acrossQuoteProvider.getDepositQuote( + amount, + inputTokenAddress + ) + quotePromises.push(quotePromise) + } else { + const quotePromise = acrossQuoteProvider.getWithdrawQuote( + amount, + outputTokenAddress + ) + quotePromises.push(quotePromise) + } + } + + if (this.isInstdapp(component)) { + const instadappProvider = new InstadappQuoteProvider( + this.rpcUrl, + swapQuoteProvider + ) + if (isMinting) { + const quotePromise = instadappProvider.getMintQuote( + component, + amount, + inputTokenAddress + ) + quotePromises.push(quotePromise) + } else { + const quotePromise = instadappProvider.getRedeemQuote( + component, + amount, + outputTokenAddress + ) + quotePromises.push(quotePromise) + } + } + + if (this.isPendle(component)) { + const pendleQuoteProvider = new PendleQuoteProvider( + this.rpcUrl, + swapQuoteProvider + ) + if (isMinting) { + const quotePromise = pendleQuoteProvider.getDepositQuote( + component, + amount, + inputTokenAddress + ) + quotePromises.push(quotePromise) + } else { + const quotePromise = pendleQuoteProvider.getWithdrawQuote( + component, + amount, + outputTokenAddress + ) + quotePromises.push(quotePromise) + } + } + } + const resultsWithNull = await Promise.all(quotePromises) + const results: bigint[] = resultsWithNull.filter( + (e): e is Exclude => e !== null + ) + if (results.length !== resultsWithNull.length) return null + // const componentQuotes = results.map((result) => result.callData) + const inputOutputTokenAmount = results + .map((result) => result) + .reduce((prevValue, currValue) => { + return currValue + prevValue + }) + return { + componentQuotes: [], + inputOutputTokenAmount, + } + } + + /** + * Returns the WETH address if token is ETH. Otherwise the token's address. + * @param token A token of type QuoteToken. + * @returns a token address as string + */ + getTokenAddressOrWeth(token: QuoteToken): string { + return token.symbol === 'ETH' ? this.wethAddress : token.address + } +} diff --git a/src/quote/flashmint/hyeth/component-quotes/instadapp.ts b/src/quote/flashmint/hyeth/component-quotes/instadapp.ts new file mode 100644 index 00000000..ea109cbe --- /dev/null +++ b/src/quote/flashmint/hyeth/component-quotes/instadapp.ts @@ -0,0 +1,54 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { BigNumber } from '@ethersproject/bignumber' +import { Contract } from '@ethersproject/contracts' + +import { stETH } from 'constants/tokens' +import { SwapQuoteProvider } from 'quote/swap' +import { getRpcProvider } from 'utils/rpc-provider' + +import VAULT_ABI from './Vault.json' + +export class InstadappQuoteProvider { + constructor( + private readonly rpcUrl: string, + private readonly swapQuoteProvider: SwapQuoteProvider + ) {} + + async getMintQuote( + component: string, + position: bigint, + inputToken: string + ): Promise { + const provider = getRpcProvider(this.rpcUrl) + // https://etherscan.io/address/0xa0d3707c569ff8c87fa923d3823ec5d81c98be78#readProxyContract + const tokenContract = new Contract(component, VAULT_ABI, provider) + const stEthAmount: BigNumber = await tokenContract.previewMint(position) + const quote = await this.swapQuoteProvider.getSwapQuote({ + chainId: 1, + inputToken, + outputToken: stETH.address!, + outputAmount: stEthAmount.toString(), + }) + if (!quote) return null + return BigInt(quote.inputAmount) + } + + async getRedeemQuote( + component: string, + position: bigint, + outputToken: string + ): Promise { + const provider = getRpcProvider(this.rpcUrl) + // https://etherscan.io/address/0xa0d3707c569ff8c87fa923d3823ec5d81c98be78#readProxyContract + const tokenContract = new Contract(component, VAULT_ABI, provider) + const stEthAmount: BigNumber = await tokenContract.previewRedeem(position) + const quote = await this.swapQuoteProvider.getSwapQuote({ + chainId: 1, + inputToken: stETH.address!, + outputToken, + inputAmount: stEthAmount.toString(), + }) + if (!quote) return null + return BigInt(quote.outputAmount) + } +} diff --git a/src/quote/flashmint/hyeth/component-quotes/pendle.ts b/src/quote/flashmint/hyeth/component-quotes/pendle.ts new file mode 100644 index 00000000..996e7d30 --- /dev/null +++ b/src/quote/flashmint/hyeth/component-quotes/pendle.ts @@ -0,0 +1,105 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { BigNumber } from '@ethersproject/bignumber' +import { Contract } from '@ethersproject/contracts' + +import FLASHMINT_HYETH_ABI from 'constants/abis/FlashMintHyEth.json' +import { AddressZero } from 'constants/addresses' +import { FlashMintHyEthAddress } from 'constants/contracts' +import { WETH } from 'constants/tokens' +import { SwapQuoteProvider } from 'quote/swap' +import { isSameAddress } from 'utils/addresses' +import { getRpcProvider } from 'utils/rpc-provider' + +export class PendleQuoteProvider { + readonly routerStaticMainnet = '0x263833d47eA3fA4a30f269323aba6a107f9eB14C' + readonly weth = WETH.address! + + constructor( + private readonly rpcUrl: string, + private readonly swapQuoteProvider: SwapQuoteProvider + ) {} + + getFlashMintHyEth(): Contract { + const provider = getRpcProvider(this.rpcUrl) + return new Contract(FlashMintHyEthAddress, FLASHMINT_HYETH_ABI, provider) + } + + getRouterStatic(address: string): Contract { + const provider = getRpcProvider(this.rpcUrl) + const abi = [ + 'function getPtToAssetRate(address market) public view returns (uint256)', + ] + return new Contract(address, abi, provider) + } + + getPtContract(pt: string): Contract { + const provider = getRpcProvider(this.rpcUrl) + const abi = ['function SY() view returns (address)'] + return new Contract(pt, abi, provider) + } + + getSyContract(sy: string): Contract { + const provider = getRpcProvider(this.rpcUrl) + const abi = [ + 'function previewDeposit(address tokenIn, uint256 amountTokenToDeposit) view returns (uint256 amountSharesOut) ', + ] + return new Contract(sy, abi, provider) + } + + async getDepositQuote( + component: string, + position: bigint, + inputToken: string + ): Promise { + const outputToken = this.weth + const fmHyEth = this.getFlashMintHyEth() + const market = await fmHyEth.pendleMarkets(component) + const marketData = await fmHyEth.pendleMarketData(market) + const ptContract = this.getPtContract(component) + const sy = await ptContract.SY() + const syContract = this.getSyContract(sy) + const routerContract = this.getRouterStatic(this.routerStaticMainnet) + const assetRate: BigNumber = await routerContract.getPtToAssetRate(market) + let ethAmount = (position * assetRate.toBigInt()) / BigInt(1e18) + const syAmountPreview: BigNumber = await syContract.previewDeposit( + AddressZero, + ethAmount + ) + if (syAmountPreview.toBigInt() < position) { + ethAmount = + (ethAmount * marketData.exchangeRateFactor.toBigInt()) / + BigInt('1000000000000000000') + } + if (isSameAddress(inputToken, outputToken)) return ethAmount + const quote = await this.swapQuoteProvider.getSwapQuote({ + chainId: 1, + inputToken, + outputToken, + outputAmount: ethAmount.toString(), + }) + if (!quote) return null + return BigInt(quote.inputAmount) + } + + async getWithdrawQuote( + component: string, + position: bigint, + outputToken: string + ): Promise { + const inputToken = this.weth + const fmHyEth = this.getFlashMintHyEth() + const market = await fmHyEth.pendleMarkets(component) + const routerContract = this.getRouterStatic(this.routerStaticMainnet) + const assetRate: BigNumber = await routerContract.getPtToAssetRate(market) + const ethAmount = (position * assetRate.toBigInt()) / BigInt(1e18) + if (isSameAddress(inputToken, outputToken)) return ethAmount + const quote = await this.swapQuoteProvider.getSwapQuote({ + chainId: 1, + inputToken, + outputToken, + inputAmount: ethAmount.toString(), + }) + if (!quote) return null + return BigInt(quote.inputAmount) + } +} diff --git a/src/quote/flashmint/hyeth/index.ts b/src/quote/flashmint/hyeth/index.ts new file mode 100644 index 00000000..6f29423e --- /dev/null +++ b/src/quote/flashmint/hyeth/index.ts @@ -0,0 +1 @@ +export * from './provider' diff --git a/src/quote/flashmint/hyeth/issuance.ts b/src/quote/flashmint/hyeth/issuance.ts new file mode 100644 index 00000000..a4da62ef --- /dev/null +++ b/src/quote/flashmint/hyeth/issuance.ts @@ -0,0 +1,32 @@ +import { Contract } from '@ethersproject/contracts' + +import DEBT_ISSUANCE_MODULE_V2 from 'constants/abis/DebtIssuanceModuleV2.json' +import { getRpcProvider } from 'utils/rpc-provider' +import { getIssuanceModule } from 'utils' + +import { FlashMintHyEthQuoteRequest } from './provider' + +export async function getRequiredComponents( + quoteRequest: FlashMintHyEthQuoteRequest, + rpcUrl: string +) { + const { isMinting, indexTokenAmount, inputToken, outputToken } = quoteRequest + const indexToken = isMinting ? outputToken : inputToken + const provider = getRpcProvider(rpcUrl) + const issuance = getIssuanceModule(indexToken.symbol) + const contract = new Contract( + issuance.address, + DEBT_ISSUANCE_MODULE_V2, + provider + ) + const [components, positions] = isMinting + ? await contract.getRequiredComponentIssuanceUnits( + indexToken.address, + indexTokenAmount + ) + : await contract.getRequiredComponentRedemptionUnits( + indexToken.address, + indexTokenAmount + ) + return { components, positions } +} diff --git a/src/quote/flashmint/hyeth/provider.test.ts b/src/quote/flashmint/hyeth/provider.test.ts new file mode 100644 index 00000000..93b50719 --- /dev/null +++ b/src/quote/flashmint/hyeth/provider.test.ts @@ -0,0 +1,242 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { AddressZero, EthAddress } from 'constants/addresses' +import { noopSwapData } from 'constants/swapdata' +import { USDC, WETH } from 'constants/tokens' +import { wei } from 'utils/numbers' +import { Exchange } from 'utils' + +import { + IndexZeroExSwapQuoteProvider, + LocalhostProviderUrl, + QuoteTokens, +} from 'tests/utils' + +import { FlashMintHyEthQuoteProvider } from './provider' + +const rpcUrl = LocalhostProviderUrl +const swapQuoteProvider = IndexZeroExSwapQuoteProvider + +const { eth, hyeth, usdc, weth } = QuoteTokens +const indexToken = hyeth + +describe('FlashMintHyEthQuoteProvider()', () => { + test('returns a quote for minting w/ ETH', async () => { + const request = { + isMinting: true, + inputToken: eth, + outputToken: { + symbol: indexToken.symbol, + decimals: 18, + address: indexToken.address!, + }, + indexTokenAmount: wei(1).toBigInt(), + slippage: 0.5, + } + const quoteProvider = new FlashMintHyEthQuoteProvider( + rpcUrl, + swapQuoteProvider + ) + const quote = await quoteProvider.getQuote(request) + if (!quote) fail() + expect(quote.indexTokenAmount).toEqual(request.indexTokenAmount) + expect(quote.inputOutputTokenAmount > 0).toBe(true) + const componentSwapDataIssue = [ + noopSwapData, + noopSwapData, + noopSwapData, + noopSwapData, + noopSwapData, + ] + expect(quote.componentsSwapData).toStrictEqual(componentSwapDataIssue) + expect(quote.swapDataEthToInputOutputToken).toBeNull() + expect(quote.swapDataInputTokenToEth).toBeNull() + }) + + test('returns a quote for minting w/ WETH', async () => { + const request = { + isMinting: true, + inputToken: weth, + outputToken: { + symbol: indexToken.symbol, + decimals: 18, + address: indexToken.address!, + }, + indexTokenAmount: wei(1).toBigInt(), + slippage: 0.5, + } + const quoteProvider = new FlashMintHyEthQuoteProvider( + rpcUrl, + swapQuoteProvider + ) + const quote = await quoteProvider.getQuote(request) + if (!quote) fail() + expect(quote.indexTokenAmount).toEqual(request.indexTokenAmount) + expect(quote.inputOutputTokenAmount > 0).toBe(true) + const componentSwapDataIssue = [ + noopSwapData, + noopSwapData, + noopSwapData, + noopSwapData, + noopSwapData, + // { + // exchange: Exchange.UniV3, + // fees: [500], + // path: [WETH.address, USDC.address], + // pool: AddressZero, + // }, + ] + expect(quote.componentsSwapData).toStrictEqual(componentSwapDataIssue) + const swapDataInputTokenToEth = { + path: [weth.address, EthAddress], + fees: [], + pool: AddressZero, + exchange: Exchange.None, + } + expect(quote.swapDataInputTokenToEth).toStrictEqual(swapDataInputTokenToEth) + const swapDataEthToInputToken = { + path: [EthAddress, weth.address], + fees: [], + pool: AddressZero, + exchange: Exchange.None, + } + expect(quote.swapDataEthToInputOutputToken).toStrictEqual( + swapDataEthToInputToken + ) + }) + + test('returns a quote for minting w/ USDC', async () => { + const request = { + isMinting: true, + inputToken: usdc, + outputToken: { + symbol: indexToken.symbol, + decimals: 18, + address: indexToken.address!, + }, + indexTokenAmount: wei(1).toBigInt(), + slippage: 0.5, + } + const quoteProvider = new FlashMintHyEthQuoteProvider( + rpcUrl, + swapQuoteProvider + ) + const quote = await quoteProvider.getQuote(request) + if (!quote) fail() + expect(quote.indexTokenAmount).toEqual(request.indexTokenAmount) + expect(quote.inputOutputTokenAmount > 0).toBe(true) + const componentSwapDataIssue = [ + noopSwapData, + noopSwapData, + noopSwapData, + noopSwapData, + noopSwapData, + // { + // exchange: Exchange.UniV3, + // fees: [500], + // path: [WETH.address, USDC.address], + // pool: AddressZero, + // }, + ] + expect(quote.componentsSwapData).toStrictEqual(componentSwapDataIssue) + const swapDataInputTokenToEth = { + path: [USDC.address, WETH.address], + fees: [500], + pool: AddressZero, + exchange: Exchange.UniV3, + } + expect(quote.swapDataInputTokenToEth).toStrictEqual(swapDataInputTokenToEth) + const swapDataEthToInputToken = { + path: [WETH.address, USDC.address], + fees: [500], + pool: AddressZero, + exchange: Exchange.UniV3, + } + expect(quote.swapDataEthToInputOutputToken).toStrictEqual( + swapDataEthToInputToken + ) + }) + + test('returns a quote for redeeming to ETH', async () => { + const request = { + isMinting: false, + inputToken: { + symbol: indexToken.symbol, + decimals: 18, + address: indexToken.address!, + }, + outputToken: eth, + indexTokenAmount: wei(1).toBigInt(), + slippage: 0.5, + } + const quoteProvider = new FlashMintHyEthQuoteProvider( + rpcUrl, + swapQuoteProvider + ) + const quote = await quoteProvider.getQuote(request) + if (!quote) fail() + expect(quote.indexTokenAmount).toEqual(request.indexTokenAmount) + expect(quote.inputOutputTokenAmount > 0).toBe(true) + const componentSwapDataRedeem = [ + noopSwapData, + noopSwapData, + noopSwapData, + noopSwapData, + noopSwapData, + // { + // exchange: Exchange.UniV3, + // fees: [500], + // path: [USDC.address, WETH.address], + // pool: AddressZero, + // }, + ] + expect(quote.componentsSwapData).toStrictEqual(componentSwapDataRedeem) + expect(quote.swapDataEthToInputOutputToken).toBeNull() + expect(quote.swapDataInputTokenToEth).toBeNull() + }) + + test('returns a quote for redeeming to ERC-20', async () => { + const request = { + isMinting: false, + inputToken: { + symbol: indexToken.symbol, + decimals: 18, + address: indexToken.address!, + }, + outputToken: usdc, + indexTokenAmount: wei(1).toBigInt(), + slippage: 0.5, + } + const quoteProvider = new FlashMintHyEthQuoteProvider( + rpcUrl, + swapQuoteProvider + ) + const quote = await quoteProvider.getQuote(request) + if (!quote) fail() + expect(quote.indexTokenAmount).toEqual(request.indexTokenAmount) + expect(quote.inputOutputTokenAmount > 0).toBe(true) + const componentSwapDataRedeem = [ + noopSwapData, + noopSwapData, + noopSwapData, + noopSwapData, + noopSwapData, + // { + // exchange: Exchange.UniV3, + // fees: [500], + // path: [USDC.address, WETH.address], + // pool: AddressZero, + // }, + ] + expect(quote.componentsSwapData).toStrictEqual(componentSwapDataRedeem) + expect(quote.swapDataInputTokenToEth).toBeNull() + const swapDataEthToInputToken = { + path: [WETH.address, USDC.address], + fees: [500], + pool: AddressZero, + exchange: Exchange.UniV3, + } + expect(quote.swapDataEthToInputOutputToken).toStrictEqual( + swapDataEthToInputToken + ) + }) +}) diff --git a/src/quote/flashmint/hyeth/provider.ts b/src/quote/flashmint/hyeth/provider.ts new file mode 100644 index 00000000..78868a50 --- /dev/null +++ b/src/quote/flashmint/hyeth/provider.ts @@ -0,0 +1,94 @@ +import { WETH } from 'constants/tokens' +import { QuoteProvider, QuoteToken } from 'quote/interfaces' +import { SwapQuoteProvider } from 'quote/swap' +import { SwapData } from 'utils' + +import { ComponentQuotesProvider } from './component-quotes' +import { getRequiredComponents } from './issuance' +import { + getComponentsSwapData, + getEthToInputOutputTokenSwapData, + getInputTokenToEthSwapData, +} from './swap-data' + +export interface FlashMintHyEthQuoteRequest { + isMinting: boolean + inputToken: QuoteToken + outputToken: QuoteToken + indexTokenAmount: bigint + slippage: number +} + +export interface FlashMintHyEthQuote { + indexTokenAmount: bigint + inputOutputTokenAmount: bigint + // Represents `swapDataEthToComponent` for minting + // and `swapDataComponentToEth` for redeeming + componentsSwapData: SwapData[] + // Used only for minting w/ ERC-20 tokens + swapDataInputTokenToEth: SwapData | null + // Represents `swapDataEthToInputToken` for minting w/ ERC-20 token + // and `swapDataEthToOutputToken` for redeeming to ERC-20 token + swapDataEthToInputOutputToken: SwapData | null +} + +export class FlashMintHyEthQuoteProvider + implements QuoteProvider +{ + constructor( + private readonly rpcUrl: string, + private readonly swapQuoteProvider: SwapQuoteProvider + ) {} + + async getQuote( + request: FlashMintHyEthQuoteRequest + ): Promise { + const { indexTokenAmount, inputToken, isMinting, outputToken, slippage } = + request + const componentsSwapData = getComponentsSwapData() + // Only relevant for minting ERC-20's + const swapDataInputTokenToEth = isMinting + ? getInputTokenToEthSwapData(inputToken) + : null + const inputOutputToken = isMinting ? inputToken : outputToken + const swapDataEthToInputOutputToken = + getEthToInputOutputTokenSwapData(inputOutputToken) + + const { components, positions } = await getRequiredComponents( + request, + this.rpcUrl + ) + + if (componentsSwapData.length !== components.length) return null + + // Mainnet only for now + const chainId = 1 + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const wethAddress = WETH.address! + const quoteProvider = new ComponentQuotesProvider( + chainId, + slippage, + wethAddress, + this.rpcUrl, + this.swapQuoteProvider + ) + const quoteResult = await quoteProvider.getComponentQuotes( + components, + positions, + isMinting, + inputToken, + outputToken + ) + if (!quoteResult) return null + const inputOutputTokenAmount = isMinting + ? (quoteResult.inputOutputTokenAmount * BigInt(1003)) / BigInt(1000) + : (quoteResult.inputOutputTokenAmount * BigInt(1000)) / BigInt(1007) + return { + indexTokenAmount, + inputOutputTokenAmount, + componentsSwapData, + swapDataInputTokenToEth, + swapDataEthToInputOutputToken, + } + } +} diff --git a/src/quote/flashmint/hyeth/swap-data.ts b/src/quote/flashmint/hyeth/swap-data.ts new file mode 100644 index 00000000..974293e2 --- /dev/null +++ b/src/quote/flashmint/hyeth/swap-data.ts @@ -0,0 +1,50 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { AddressZero, EthAddress } from 'constants/addresses' +import { noopSwapData } from 'constants/swapdata' +import { ETH, WETH } from 'constants/tokens' +import { QuoteToken } from 'quote/interfaces' +import { Exchange, SwapData } from 'utils' + +export function getComponentsSwapData(): SwapData[] { + return [noopSwapData, noopSwapData, noopSwapData, noopSwapData, noopSwapData] +} + +export function getEthToInputOutputTokenSwapData( + inputOutputToken: QuoteToken +): SwapData | null { + if (inputOutputToken.symbol === ETH.symbol) return null + if (inputOutputToken.symbol === WETH.symbol) { + return { + path: [EthAddress, inputOutputToken.address], + fees: [], + pool: AddressZero, + exchange: Exchange.None, + } + } + return { + path: [WETH.address!, inputOutputToken.address], + fees: [500], + pool: AddressZero, + exchange: Exchange.UniV3, + } +} + +export function getInputTokenToEthSwapData( + inputToken: QuoteToken +): SwapData | null { + if (inputToken.symbol === ETH.symbol) return null + if (inputToken.symbol === WETH.symbol) { + return { + path: [inputToken.address, EthAddress], + fees: [], + pool: AddressZero, + exchange: Exchange.None, + } + } + return { + path: [inputToken.address, WETH.address!], + fees: [500], + pool: AddressZero, + exchange: Exchange.UniV3, + } +} diff --git a/src/quote/flashmint/index.ts b/src/quote/flashmint/index.ts index 2fe68dd6..757ca68e 100644 --- a/src/quote/flashmint/index.ts +++ b/src/quote/flashmint/index.ts @@ -1,3 +1,4 @@ +export * from './hyeth' export * from './leveraged' export * from './leveraged-extended' export * from './zeroEx' diff --git a/src/quote/provider/index.test.ts b/src/quote/provider/index.test.ts index fa25ea68..c0be7217 100644 --- a/src/quote/provider/index.test.ts +++ b/src/quote/provider/index.test.ts @@ -1,6 +1,9 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { ChainId } from 'constants/chains' -import { FlashMintZeroExMainnetAddress } from 'constants/contracts' +import { + FlashMintHyEthAddress, + FlashMintZeroExMainnetAddress, +} from 'constants/contracts' import { IndexCoopEthereum2xIndex } from 'constants/tokens' import { getFlashMintLeveragedContractForToken, @@ -28,7 +31,7 @@ const rpcUrl = LocalhostProviderUrl const provider = LocalhostProvider const zeroexSwapQuoteProvider = IndexZeroExSwapQuoteProvider -const { cdeti, dseth, eth, eth2x, iceth, usdc } = QuoteTokens +const { cdeti, dseth, eth, eth2x, hyeth, iceth, usdc } = QuoteTokens describe('FlashMintQuoteProvider()', () => { test('throws if token is unsupported', async () => { @@ -202,6 +205,37 @@ describe('FlashMintQuoteProvider()', () => { expect(quote.tx.data?.length).toBeGreaterThan(0) }) + test('returns a quote for minting hyETH', async () => { + const request: FlashMintQuoteRequest = { + isMinting: true, + inputToken: usdc, + outputToken: hyeth, + indexTokenAmount: wei(1), + slippage: 0.5, + } + const quoteProvider = new FlashMintQuoteProvider( + LocalhostProviderUrl, + IndexZeroExSwapQuoteProvider + ) + const quote = await quoteProvider.getQuote(request) + if (!quote) fail() + const chainId = (await provider.getNetwork()).chainId + expect(quote.chainId).toEqual(chainId) + expect(quote.contractType).toEqual(FlashMintContractType.hyeth) + expect(quote.contract).toEqual(FlashMintHyEthAddress) + expect(quote.isMinting).toEqual(request.isMinting) + expect(quote.inputToken).toEqual(request.inputToken) + expect(quote.outputToken).toEqual(request.outputToken) + expect(quote.outputToken).toEqual(request.outputToken) + expect(quote.inputAmount).toEqual(quote.inputOutputAmount) + expect(quote.indexTokenAmount).toEqual(request.indexTokenAmount) + expect(quote.inputOutputAmount.gt(0)).toBe(true) + expect(quote.slippage).toEqual(request.slippage) + expect(quote.tx).not.toBeNull() + expect(quote.tx.to).toBe(FlashMintHyEthAddress) + expect(quote.tx.data?.length).toBeGreaterThan(0) + }) + test('returns a quote for redeeming ETH2X', async () => { const arbitrumProvider = LocalhostProviderArbitrum const inputToken = { @@ -245,6 +279,36 @@ describe('FlashMintQuoteProvider()', () => { expect(quote.tx.data?.length).toBeGreaterThan(0) }) + test('returns a quote for redeeming hyETH', async () => { + const request: FlashMintQuoteRequest = { + isMinting: false, + inputToken: hyeth, + outputToken: usdc, + indexTokenAmount: wei(1), + slippage: 0.5, + } + const quoteProvider = new FlashMintQuoteProvider( + LocalhostProviderUrl, + IndexZeroExSwapQuoteProvider + ) + const quote = await quoteProvider.getQuote(request) + if (!quote) fail() + const chainId = (await provider.getNetwork()).chainId + expect(quote.chainId).toEqual(chainId) + expect(quote.contractType).toEqual(FlashMintContractType.hyeth) + expect(quote.contract).toEqual(FlashMintHyEthAddress) + expect(quote.isMinting).toEqual(request.isMinting) + expect(quote.inputToken).toEqual(request.inputToken) + expect(quote.outputToken).toEqual(request.outputToken) + expect(quote.outputToken).toEqual(request.outputToken) + expect(quote.inputAmount).toEqual(quote.indexTokenAmount) + expect(quote.inputOutputAmount.gt(0)).toBe(true) + expect(quote.slippage).toEqual(request.slippage) + expect(quote.tx).not.toBeNull() + expect(quote.tx.to).toBe(FlashMintHyEthAddress) + expect(quote.tx.data?.length).toBeGreaterThan(0) + }) + test('returns a quote for redeeming icETH', async () => { const inputToken = iceth const outputToken = eth diff --git a/src/quote/provider/index.ts b/src/quote/provider/index.ts index d538e5db..bb9bf7cf 100644 --- a/src/quote/provider/index.ts +++ b/src/quote/provider/index.ts @@ -2,6 +2,7 @@ import { TransactionRequest } from '@ethersproject/abstract-provider' import { BigNumber } from '@ethersproject/bignumber' import { + FlashMintHyEthTransactionBuilder, FlashMintLeveragedBuildRequest, FlashMintLeveragedExtendedBuildRequest, FlashMintZeroExBuildRequest, @@ -9,8 +10,10 @@ import { LeveragedTransactionBuilder, ZeroExTransactionBuilder, } from 'flashmint' +import { getRpcProvider } from 'utils/rpc-provider' import { wei } from 'utils' +import { FlashMintHyEthQuoteProvider } from '../flashmint/hyeth' import { LeveragedQuoteProvider } from '../flashmint/leveraged' import { LeveragedExtendedQuoteProvider } from '../flashmint/leveraged-extended' import { ZeroExQuoteProvider } from '../flashmint/zeroEx' @@ -18,9 +21,9 @@ import { QuoteProvider, QuoteToken } from '../interfaces' import { SwapQuoteProvider } from '../swap' import { getContractType } from './utils' -import { getRpcProvider } from 'utils/rpc-provider' export enum FlashMintContractType { + hyeth, leveraged, leveragedExtended, zeroEx, @@ -73,6 +76,58 @@ export class FlashMintQuoteProvider throw new Error('Index token not supported') } switch (contractType) { + case FlashMintContractType.hyeth: { + const hyethQuoteProvider = new FlashMintHyEthQuoteProvider( + rpcUrl, + swapQuoteProvider + ) + const hyethQuote = await hyethQuoteProvider.getQuote({ + isMinting, + inputToken, + outputToken, + indexTokenAmount: indexTokenAmount.toBigInt(), + slippage, + }) + if (!hyethQuote) return null + const inputOutputTokenAmount = BigNumber.from( + hyethQuote.inputOutputTokenAmount.toString() + ) + const builder = new FlashMintHyEthTransactionBuilder(rpcUrl) + const txRequest = { + isMinting, + inputToken: inputToken.address, + inputTokenSymbol: inputToken.symbol, + outputToken: outputToken.address, + outputTokenSymbol: outputToken.symbol, + inputTokenAmount: isMinting + ? inputOutputTokenAmount + : indexTokenAmount, + outputTokenAmount: isMinting + ? indexTokenAmount + : inputOutputTokenAmount, + componentsSwapData: hyethQuote.componentsSwapData, + swapDataInputTokenToEth: hyethQuote.swapDataInputTokenToEth, + swapDataEthToInputOutputToken: + hyethQuote.swapDataEthToInputOutputToken, + } + const tx = await builder.build(txRequest) + if (!tx) return null + return { + chainId, + contractType, + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + contract: tx.to!, + isMinting, + inputToken, + outputToken, + inputAmount: isMinting ? inputOutputTokenAmount : indexTokenAmount, + outputAmount: isMinting ? indexTokenAmount : inputOutputTokenAmount, + indexTokenAmount, + inputOutputAmount: inputOutputTokenAmount, + slippage, + tx, + } + } case FlashMintContractType.leveraged: { const leveragedQuoteProvider = new LeveragedQuoteProvider( rpcUrl, @@ -98,7 +153,7 @@ export class FlashMintQuoteProvider return { chainId, contractType, - /* eslint-disable @typescript-eslint/no-non-null-assertion */ + /* eslint-disable @typescript-eslint/no-non-null-assertion */ contract: tx.to!, isMinting, inputToken, @@ -139,7 +194,7 @@ export class FlashMintQuoteProvider return { chainId, contractType, - /* eslint-disable @typescript-eslint/no-non-null-assertion */ + /* eslint-disable @typescript-eslint/no-non-null-assertion */ contract: tx.to!, isMinting, inputToken, diff --git a/src/quote/provider/utils.test.ts b/src/quote/provider/utils.test.ts index 196b654f..ec5f418f 100644 --- a/src/quote/provider/utils.test.ts +++ b/src/quote/provider/utils.test.ts @@ -7,6 +7,7 @@ import { DiversifiedStakedETHIndex, ETH2xFlexibleLeverageIndex, GitcoinStakedETHIndex, + HighYieldETHIndex, IndexCoopBitcoin2xIndex, IndexCoopBitcoin3xIndex, IndexCoopEthereum2xIndex, @@ -121,7 +122,7 @@ describe('getContractType()', () => { expect(contractType).toBe(FlashMintContractType.leveraged) }) - test('returns correct contract type for ETH2X (mainnet', async () => { + test('returns correct contract type for ETH2X (mainnet)', async () => { const contractType = getContractType( IndexCoopEthereum2xIndex.symbol, ChainId.Mainnet @@ -129,6 +130,14 @@ describe('getContractType()', () => { expect(contractType).toBe(FlashMintContractType.leveraged) }) + test('returns correct contract type for hyETH', async () => { + const contractType = getContractType( + HighYieldETHIndex.symbol, + ChainId.Mainnet + ) + expect(contractType).toBe(FlashMintContractType.hyeth) + }) + test('returns correct contract type for icETH', async () => { const contractType = getContractType( InterestCompoundingETHIndex.symbol, diff --git a/src/quote/provider/utils.ts b/src/quote/provider/utils.ts index 24c55a35..70b41bf8 100644 --- a/src/quote/provider/utils.ts +++ b/src/quote/provider/utils.ts @@ -7,6 +7,7 @@ import { DiversifiedStakedETHIndex, ETH2xFlexibleLeverageIndex, GitcoinStakedETHIndex, + HighYieldETHIndex, IndexCoopBitcoin2xIndex, IndexCoopBitcoin3xIndex, IndexCoopEthereum2xIndex, @@ -36,6 +37,9 @@ export function getContractType( return FlashMintContractType.leveragedExtended } } + if (token === HighYieldETHIndex.symbol) { + return FlashMintContractType.hyeth + } if ( token === BanklessBEDIndex.symbol || token === CoinDeskEthTrendIndex.symbol || diff --git a/src/tests/hyeth.test.ts b/src/tests/hyeth.test.ts new file mode 100644 index 00000000..be78151b --- /dev/null +++ b/src/tests/hyeth.test.ts @@ -0,0 +1,93 @@ +import { + getMainnetTestFactory, + QuoteTokens, + SignerAccount4, + TestFactory, + transferFromWhale, + wei, +} from './utils' + +const { eth, hyeth, usdc } = QuoteTokens + +describe('hyETH', () => { + const indexToken = hyeth + let factory: TestFactory + beforeEach(async () => { + const signer = SignerAccount4 + factory = getMainnetTestFactory(signer) + }) + + test('can mint with ETH', async () => { + await factory.fetchQuote({ + isMinting: true, + inputToken: eth, + outputToken: indexToken, + indexTokenAmount: wei('1'), + slippage: 0.5, + }) + await factory.executeTx() + }) + + test('can mint with ETH (large amout)', async () => { + await factory.fetchQuote({ + isMinting: true, + inputToken: eth, + outputToken: indexToken, + indexTokenAmount: wei('300'), + slippage: 0.5, + }) + await factory.executeTx() + }) + + test('can mint with USDC', async () => { + const quote = await factory.fetchQuote({ + isMinting: true, + inputToken: usdc, + outputToken: indexToken, + indexTokenAmount: wei('100'), + slippage: 0.5, + }) + const whale = '0x7713974908Be4BEd47172370115e8b1219F4A5f0' + await transferFromWhale( + whale, + factory.getSigner().address, + wei('1000000', quote.inputToken.decimals), + quote.inputToken.address, + factory.getProvider() + ) + await factory.executeTx() + }) + + test('can redeem to ETH', async () => { + await factory.fetchQuote({ + isMinting: false, + inputToken: indexToken, + outputToken: eth, + indexTokenAmount: wei('1'), + slippage: 0.5, + }) + await factory.executeTx() + }) + + test('can redeem to ETH (large amount)', async () => { + await factory.fetchQuote({ + isMinting: false, + inputToken: indexToken, + outputToken: eth, + indexTokenAmount: wei('200'), + slippage: 0.5, + }) + await factory.executeTx() + }) + + test('can redeem to USDC', async () => { + await factory.fetchQuote({ + isMinting: false, + inputToken: indexToken, + outputToken: eth, + indexTokenAmount: wei('1'), + slippage: 0.5, + }) + await factory.executeTx() + }) +}) diff --git a/src/tests/utils/quoteTokens.ts b/src/tests/utils/quoteTokens.ts index 67fc3846..b39d3dce 100644 --- a/src/tests/utils/quoteTokens.ts +++ b/src/tests/utils/quoteTokens.ts @@ -8,6 +8,7 @@ import { ETH, ETH2xFlexibleLeverageIndex, GitcoinStakedETHIndex, + HighYieldETHIndex, IndexCoopBitcoin2xIndex, IndexCoopEthereum2xIndex, InterestCompoundingETHIndex, @@ -21,7 +22,7 @@ import { stETH, wsETH2, } from 'constants/tokens' -import { QuoteToken } from 'quote/quoteToken' +import { QuoteToken } from 'quote/interfaces' const btc2x: QuoteToken = { address: IndexCoopBitcoin2xIndex.address!, @@ -83,6 +84,12 @@ const gtcETH = { symbol: GitcoinStakedETHIndex.symbol, } +const hyeth = { + address: HighYieldETHIndex.address!, + decimals: 18, + symbol: HighYieldETHIndex.symbol, +} + const iceth: QuoteToken = { symbol: InterestCompoundingETHIndex.symbol, decimals: 18, @@ -153,6 +160,7 @@ export const QuoteTokens = { eth2x, eth2xfli, gtcETH, + hyeth, iceth, icreth, mvi, diff --git a/src/utils/addresses.ts b/src/utils/addresses.ts new file mode 100644 index 00000000..79e92611 --- /dev/null +++ b/src/utils/addresses.ts @@ -0,0 +1,5 @@ +import { Address, isAddressEqual } from 'viem' + +export function isSameAddress(address1: string, address2: string): boolean { + return isAddressEqual(address1 as Address, address2 as Address) +} diff --git a/src/utils/contracts.test.ts b/src/utils/contracts.test.ts index baeef869..d9cc3317 100644 --- a/src/utils/contracts.test.ts +++ b/src/utils/contracts.test.ts @@ -4,6 +4,7 @@ import { ExchangeIssuanceLeveragedPolygonAddress, ExchangeIssuanceZeroExMainnetAddress, ExchangeIssuanceZeroExPolygonAddress, + FlashMintHyEthAddress, FlashMintLeveragedAddress, FlashMintLeveragedExtendedAddress, FlashMintLeveragedForCompoundAddress, @@ -34,6 +35,7 @@ import { getFlashMintZeroExContract, getFlashMintZeroExContractForToken, getFlashMintLeveragedContractForToken, + getFlashMintHyEthContract, } from './contracts' describe('getExchangeIssuanceLeveragedContractAddress()', () => { @@ -56,6 +58,18 @@ describe('getExchangeIssuanceLeveragedContractAddress()', () => { }) }) +describe('getFlashMintHyEthContract()', () => { + test('return correct contract', async () => { + const expectedAddress = FlashMintHyEthAddress + const contract = getFlashMintHyEthContract(undefined) + expect(contract.address).toEqual(expectedAddress) + expect(contract.functions.issueExactSetFromERC20).toBeDefined() + expect(contract.functions.issueExactSetFromETH).toBeDefined() + expect(contract.functions.redeemExactSetForERC20).toBeDefined() + expect(contract.functions.redeemExactSetForETH).toBeDefined() + }) +}) + describe('getFlashMintLeveragedContract()', () => { test('return correct address for polygon', async () => { const expectedAddress = ExchangeIssuanceLeveragedPolygonAddress diff --git a/src/utils/contracts.ts b/src/utils/contracts.ts index 264ccf3c..87afaece 100644 --- a/src/utils/contracts.ts +++ b/src/utils/contracts.ts @@ -4,6 +4,7 @@ import { Contract } from '@ethersproject/contracts' import EXCHANGE_ISSUANCE_LEVERAGED_ABI from '../constants/abis/ExchangeIssuanceLeveraged.json' import EXCHANGE_ISSUANCE_ZERO_EX_ABI from '../constants/abis/ExchangeIssuanceZeroEx.json' +import FLASHMINT_HYETH_ABI from '../constants/abis/FlashMintHyEth.json' import FLASHMINT_LEVERAGED_COMPOUND from '../constants/abis/FlashMintLeveragedForCompound.json' import FLASHMINT_LEVERAGED_EXTENDED_ABI from '../constants/abis/FlashMintLeveragedExtended.json' import FLASHMINT_ZEROEX_ABI from '../constants/abis/FlashMintZeroEx.json' @@ -14,6 +15,7 @@ import { ExchangeIssuanceLeveragedPolygonAddress, ExchangeIssuanceZeroExMainnetAddress, ExchangeIssuanceZeroExPolygonAddress, + FlashMintHyEthAddress, FlashMintLeveragedAddress, FlashMintLeveragedExtendedAddress, FlashMintLeveragedForCompoundAddress, @@ -64,6 +66,17 @@ export const getFlashMintLeveragedContract = ( ) } +/** + * Returns an instance of a FlashMintHyEth contract. + * Currently, only Mainnet is supported. + */ +export const getFlashMintHyEthContract = ( + signerOrProvider: Signer | Provider | undefined +): Contract => { + const contractAddress = FlashMintHyEthAddress + return new Contract(contractAddress, FLASHMINT_HYETH_ABI, signerOrProvider) +} + /** * Returns an instance of the Index FlashMintLeveraged contract (mainnet) * diff --git a/src/utils/index.ts b/src/utils/index.ts index 2a3a50f2..6dde6d95 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ +export * from './addresses' export * from './contracts' export * from './issuanceModules' export * from './numbers' diff --git a/src/utils/issuanceModules.test.ts b/src/utils/issuanceModules.test.ts index a738a287..fcf6dfe6 100644 --- a/src/utils/issuanceModules.test.ts +++ b/src/utils/issuanceModules.test.ts @@ -23,6 +23,7 @@ import { IndexCoopEthereum2xIndex, IndexCoopBitcoin2xIndex, IndexCoopBitcoin3xIndex, + HighYieldETHIndex, } from 'constants/tokens' import { getIssuanceModule } from './issuanceModules' @@ -63,6 +64,13 @@ describe('getIssuanceModule() - Mainnet - IndexProtocol', () => { expect(issuanceModule.isDebtIssuance).toBe(true) }) + test('returns debt issuance module v2 for hyETH', async () => { + const expectedModule = IndexDebtIssuanceModuleV2Address_v2 + const issuanceModule = getIssuanceModule(HighYieldETHIndex.symbol) + expect(issuanceModule.address).toEqual(expectedModule) + expect(issuanceModule.isDebtIssuance).toBe(true) + }) + test('returns debt issuance module v2 for icRETH', async () => { const expectedModule = IndexDebtIssuanceModuleV2Address_v2 const issuanceModule = getIssuanceModule(LeveragedrEthStakingYield.symbol) diff --git a/src/utils/issuanceModules.ts b/src/utils/issuanceModules.ts index 916b7699..8461a6b3 100644 --- a/src/utils/issuanceModules.ts +++ b/src/utils/issuanceModules.ts @@ -19,6 +19,7 @@ import { CoinDeskEthTrendIndex, IndexCoopEthereum2xIndex, IndexCoopBitcoin2xIndex, + HighYieldETHIndex, } from '../constants/tokens' export interface IssuanceModule { @@ -48,6 +49,7 @@ export function getIssuanceModule( return { address: DebtIssuanceModuleAddress, isDebtIssuance: true } case CoinDeskEthTrendIndex.symbol: case DiversifiedStakedETHIndex.symbol: + case HighYieldETHIndex.symbol: case IndexCoopBitcoin2xIndex.symbol: case IndexCoopEthereum2xIndex.symbol: case LeveragedrEthStakingYield.symbol: