diff --git a/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/dai.aave-v2.cy.ts b/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/dai.aave-v2.cy.ts index 1eb495a42c..e87dac8cae 100644 --- a/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/dai.aave-v2.cy.ts +++ b/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/dai.aave-v2.cy.ts @@ -59,14 +59,13 @@ const testData = { hasApproval: true, repayOption: constants.repayType.default, }, - // { - // asset: assets.aaveMarket.DAI, - // apyType: constants.apyType.stable, - // amount: 10, - // hasApproval: false, - // repayOption: constants.repayType.collateral, - // assetForRepay: assets.aaveMarket.BAT, - // }, + { + asset: assets.aaveMarket.DAI, + apyType: constants.apyType.stable, + amount: 10, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, ], withdraw: { asset: assets.aaveMarket.DAI, @@ -81,7 +80,6 @@ const testData = { type: constants.dashboardTypes.deposit, assetName: assets.aaveMarket.DAI.shortName, wrapped: assets.aaveMarket.DAI.wrapped, - // amount: 30, amount: 40, collateralType: constants.collateralType.isCollateral, isCollateral: true, @@ -90,8 +88,7 @@ const testData = { type: constants.dashboardTypes.borrow, assetName: assets.aaveMarket.DAI.shortName, wrapped: assets.aaveMarket.DAI.wrapped, - // amount: 80, - amount: 90, + amount: 80, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/ren.aave-v2.cy.ts b/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/ren.aave-v2.cy.ts index 7d8c3b2988..f811b984b5 100644 --- a/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/ren.aave-v2.cy.ts +++ b/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/ren.aave-v2.cy.ts @@ -59,14 +59,13 @@ const testData = { hasApproval: true, repayOption: constants.repayType.default, }, - // { - // asset: assets.aaveMarket.REN, - // apyType: constants.apyType.stable, - // amount: 20, - // hasApproval: false, - // repayOption: constants.repayType.collateral, - // assetForRepay: assets.aaveMarket.BAT, - // }, + { + asset: assets.aaveMarket.REN, + apyType: constants.apyType.stable, + amount: 20, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, ], withdraw: { asset: assets.aaveMarket.REN, @@ -81,7 +80,6 @@ const testData = { type: constants.dashboardTypes.deposit, assetName: assets.aaveMarket.REN.shortName, wrapped: assets.aaveMarket.REN.wrapped, - // amount: 60, amount: 80, collateralType: constants.collateralType.isCollateral, isCollateral: true, @@ -90,8 +88,7 @@ const testData = { type: constants.dashboardTypes.borrow, assetName: assets.aaveMarket.REN.shortName, wrapped: assets.aaveMarket.REN.wrapped, - // amount: 160, - amount: 180, + amount: 160, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/tusd.aave-v2.cy.ts b/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/tusd.aave-v2.cy.ts index a932392319..3900460d95 100644 --- a/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/tusd.aave-v2.cy.ts +++ b/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/tusd.aave-v2.cy.ts @@ -59,14 +59,13 @@ const testData = { hasApproval: true, repayOption: constants.repayType.default, }, - // { - // asset: assets.aaveMarket.TUSD, - // apyType: constants.apyType.stable, - // amount: 10, - // hasApproval: false, - // repayOption: constants.repayType.collateral, - // assetForRepay: assets.aaveMarket.TUSD, - // }, + { + asset: assets.aaveMarket.TUSD, + apyType: constants.apyType.stable, + amount: 10, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, ], withdraw: { asset: assets.aaveMarket.TUSD, @@ -81,7 +80,6 @@ const testData = { type: constants.dashboardTypes.deposit, assetName: assets.aaveMarket.TUSD.shortName, wrapped: assets.aaveMarket.TUSD.wrapped, - // amount: 30, amount: 40, collateralType: constants.collateralType.isCollateral, isCollateral: true, @@ -90,8 +88,7 @@ const testData = { type: constants.dashboardTypes.borrow, assetName: assets.aaveMarket.TUSD.shortName, wrapped: assets.aaveMarket.TUSD.wrapped, - // amount: 80, - amount: 90, + amount: 80, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/usdc.aave-v2.cy.ts b/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/usdc.aave-v2.cy.ts index c5d22718f7..6d1ce859fb 100644 --- a/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/usdc.aave-v2.cy.ts +++ b/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/usdc.aave-v2.cy.ts @@ -59,14 +59,13 @@ const testData = { hasApproval: true, repayOption: constants.repayType.default, }, - // { - // asset: assets.aaveMarket.USDC, - // apyType: constants.apyType.stable, - // amount: 10, - // hasApproval: false, - // repayOption: constants.repayType.collateral, - // assetForRepay: assets.aaveMarket.USDC, - // }, + { + asset: assets.aaveMarket.USDC, + apyType: constants.apyType.stable, + amount: 10, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, ], withdraw: { asset: assets.aaveMarket.USDC, @@ -81,7 +80,6 @@ const testData = { type: constants.dashboardTypes.deposit, assetName: assets.aaveMarket.USDC.shortName, wrapped: assets.aaveMarket.USDC.wrapped, - // amount: 30, amount: 40, collateralType: constants.collateralType.isCollateral, isCollateral: true, @@ -90,8 +88,7 @@ const testData = { type: constants.dashboardTypes.borrow, assetName: assets.aaveMarket.USDC.shortName, wrapped: assets.aaveMarket.USDC.wrapped, - // amount: 80, - amount: 90, + amount: 80, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/usdt.aave-v2.cy.ts b/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/usdt.aave-v2.cy.ts index 90779a4569..d55c05c2b2 100644 --- a/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/usdt.aave-v2.cy.ts +++ b/cypress/e2e/0-v2-markets/0-main-v2-market/0-assets/usdt.aave-v2.cy.ts @@ -62,14 +62,13 @@ const testData = { hasApproval: true, repayOption: constants.repayType.default, }, - // { - // asset: assets.aaveMarket.USDT, - // apyType: constants.apyType.stable, - // amount: 10, - // hasApproval: false, - // repayOption: constants.repayType.collateral, - // assetForRepay: assets.aaveMarket.USDT, - // }, + { + asset: assets.aaveMarket.USDT, + apyType: constants.apyType.stable, + amount: 10, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, ], withdraw: { asset: assets.aaveMarket.USDT, @@ -88,7 +87,6 @@ const testData = { type: constants.dashboardTypes.deposit, assetName: assets.aaveMarket.USDT.shortName, wrapped: assets.aaveMarket.USDT.wrapped, - // amount: 30, amount: 40, collateralType: constants.collateralType.isCollateral, isCollateral: false, @@ -97,8 +95,7 @@ const testData = { type: constants.dashboardTypes.borrow, assetName: assets.aaveMarket.USDT.shortName, wrapped: assets.aaveMarket.USDT.wrapped, - // amount: 80, - amount: 90, + amount: 80, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/0-v2-markets/2-polygon-v2-market/0-assets/usdc.polygon-v2.cy.ts b/cypress/e2e/0-v2-markets/2-polygon-v2-market/0-assets/usdc.polygon-v2.cy.ts index edbcbfc6e1..8aa9ab3003 100644 --- a/cypress/e2e/0-v2-markets/2-polygon-v2-market/0-assets/usdc.polygon-v2.cy.ts +++ b/cypress/e2e/0-v2-markets/2-polygon-v2-market/0-assets/usdc.polygon-v2.cy.ts @@ -22,13 +22,22 @@ const testData = { amount: 10, hasApproval: false, }, - repay: { - asset: assets.polygonMarket.USDC, - apyType: constants.apyType.variable, - amount: 2, - hasApproval: true, - repayOption: constants.repayType.default, - }, + repay: [ + { + asset: assets.polygonMarket.USDC, + apyType: constants.apyType.variable, + amount: 2, + hasApproval: true, + repayOption: constants.repayType.default, + }, + { + asset: assets.polygonMarket.USDC, + apyType: constants.apyType.variable, + amount: 2, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, + ], withdraw: { asset: assets.polygonMarket.USDC, isCollateral: true, @@ -50,7 +59,7 @@ const testData = { type: constants.dashboardTypes.borrow, assetName: assets.polygonMarket.USDC.shortName, wrapped: assets.polygonMarket.USDC.wrapped, - amount: 23.0, + amount: 21.0, apyType: constants.borrowAPYType.variable, }, ], @@ -64,7 +73,9 @@ describe('USDC INTEGRATION SPEC, POLYGON V2 MARKET', () => { supply(testData.depositBaseAmount, skipTestState, true); borrow(testData.testCases.borrow, skipTestState, true); supply(testData.testCases.deposit, skipTestState, true); - repay(testData.testCases.repay, skipTestState, false); + testData.testCases.repay.forEach((repayCase) => { + repay(repayCase, skipTestState, false); + }); withdraw(testData.testCases.withdraw, skipTestState, false); dashboardAssetValuesVerification(testData.verifications.finalDashboard, skipTestState); }); diff --git a/cypress/e2e/0-v2-markets/2-polygon-v2-market/0-assets/usdt.polygon-v2.cy.ts b/cypress/e2e/0-v2-markets/2-polygon-v2-market/0-assets/usdt.polygon-v2.cy.ts index bda9fd6c8e..68ccca77a0 100644 --- a/cypress/e2e/0-v2-markets/2-polygon-v2-market/0-assets/usdt.polygon-v2.cy.ts +++ b/cypress/e2e/0-v2-markets/2-polygon-v2-market/0-assets/usdt.polygon-v2.cy.ts @@ -25,13 +25,22 @@ const testData = { amount: 10, hasApproval: false, }, - repay: { - asset: assets.polygonMarket.USDT, - apyType: constants.apyType.variable, - amount: 2, - hasApproval: true, - repayOption: constants.repayType.default, - }, + repay: [ + { + asset: assets.polygonMarket.USDT, + apyType: constants.apyType.variable, + amount: 2, + hasApproval: true, + repayOption: constants.repayType.default, + }, + { + asset: assets.polygonMarket.USDT, + apyType: constants.apyType.variable, + amount: 2, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, + ], withdraw: { asset: assets.polygonMarket.USDT, isCollateral: false, @@ -57,7 +66,7 @@ const testData = { type: constants.dashboardTypes.borrow, assetName: assets.polygonMarket.USDT.shortName, wrapped: assets.polygonMarket.USDT.wrapped, - amount: 23.0, + amount: 21.0, apyType: constants.borrowAPYType.variable, }, ], @@ -71,7 +80,9 @@ describe('USDT INTEGRATION SPEC, POLYGON MARKET', () => { supply(testData.depositBaseAmount, skipTestState, true); borrow(testData.testCases.borrow, skipTestState, true); supply(testData.testCases.deposit, skipTestState, true); - repay(testData.testCases.repay, skipTestState, false); + testData.testCases.repay.forEach((repayCase) => { + repay(repayCase, skipTestState, false); + }); withdraw(testData.testCases.withdraw, skipTestState, false); changeBorrowTypeBlocked(testData.testCases.checkDisabledCollateral, skipTestState); dashboardAssetValuesVerification(testData.verifications.finalDashboard, skipTestState); diff --git a/cypress/e2e/0-v2-markets/3-avalanche-v2-market/0-assets/usdc.avalanche-v2.cy.ts b/cypress/e2e/0-v2-markets/3-avalanche-v2-market/0-assets/usdc.avalanche-v2.cy.ts index 44ef49fec1..a8e8d585b0 100644 --- a/cypress/e2e/0-v2-markets/3-avalanche-v2-market/0-assets/usdc.avalanche-v2.cy.ts +++ b/cypress/e2e/0-v2-markets/3-avalanche-v2-market/0-assets/usdc.avalanche-v2.cy.ts @@ -25,13 +25,22 @@ const testData = { amount: 10, hasApproval: false, }, - repay: { - asset: assets.avalancheMarket.USDC, - apyType: constants.apyType.variable, - amount: 2, - hasApproval: true, - repayOption: constants.repayType.default, - }, + repay: [ + { + asset: assets.avalancheMarket.USDC, + apyType: constants.apyType.variable, + amount: 2, + hasApproval: true, + repayOption: constants.repayType.default, + }, + { + asset: assets.avalancheMarket.USDC, + apyType: constants.apyType.variable, + amount: 2, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, + ], withdraw: { asset: assets.avalancheMarket.USDC, isCollateral: true, @@ -57,7 +66,7 @@ const testData = { type: constants.dashboardTypes.borrow, assetName: assets.avalancheMarket.USDC.shortName, wrapped: assets.avalancheMarket.USDC.wrapped, - amount: 23.0, + amount: 21.0, apyType: constants.borrowAPYType.variable, }, ], @@ -71,7 +80,9 @@ describe('USDC INTEGRATION SPEC, AVALANCHE V2 MARKET', () => { supply(testData.depositBaseAmount, skipTestState, true); borrow(testData.testCases.borrow, skipTestState, true); supply(testData.testCases.deposit, skipTestState, true); - repay(testData.testCases.repay, skipTestState, false); + testData.testCases.repay.forEach((repayCase) => { + repay(repayCase, skipTestState, false); + }); withdraw(testData.testCases.withdraw, skipTestState, false); switchApyBlocked(testData.testCases.checkDisabledApy, skipTestState); dashboardAssetValuesVerification(testData.verifications.finalDashboard, skipTestState); diff --git a/cypress/e2e/0-v2-markets/3-avalanche-v2-market/0-assets/usdt.avalanche-v2.cy.ts b/cypress/e2e/0-v2-markets/3-avalanche-v2-market/0-assets/usdt.avalanche-v2.cy.ts index 792537669f..78ea09759d 100644 --- a/cypress/e2e/0-v2-markets/3-avalanche-v2-market/0-assets/usdt.avalanche-v2.cy.ts +++ b/cypress/e2e/0-v2-markets/3-avalanche-v2-market/0-assets/usdt.avalanche-v2.cy.ts @@ -26,13 +26,22 @@ const testData = { amount: 10, hasApproval: false, }, - repay: { - asset: assets.avalancheMarket.USDT, - apyType: constants.apyType.variable, - amount: 2, - hasApproval: true, - repayOption: constants.repayType.default, - }, + repay: [ + { + asset: assets.avalancheMarket.USDT, + apyType: constants.apyType.variable, + amount: 2, + hasApproval: true, + repayOption: constants.repayType.default, + }, + { + asset: assets.avalancheMarket.USDT, + apyType: constants.apyType.variable, + amount: 2, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, + ], withdraw: { asset: assets.avalancheMarket.USDT, isCollateral: false, @@ -62,7 +71,7 @@ const testData = { type: constants.dashboardTypes.borrow, assetName: assets.avalancheMarket.USDT.shortName, wrapped: assets.avalancheMarket.USDT.wrapped, - amount: 23.0, + amount: 21.0, apyType: constants.borrowAPYType.variable, }, ], @@ -76,7 +85,9 @@ describe('USDT INTEGRATION SPEC, AVALANCHE V2 MARKET', () => { supply(testData.depositBaseAmount, skipTestState, true); borrow(testData.testCases.borrow, skipTestState, true); supply(testData.testCases.deposit, skipTestState, true); - repay(testData.testCases.repay, skipTestState, false); + testData.testCases.repay.forEach((repayCase) => { + repay(repayCase, skipTestState, false); + }); withdraw(testData.testCases.withdraw, skipTestState, false); switchApyBlocked(testData.testCases.checkDisabledApy, skipTestState); changeBorrowTypeBlocked(testData.testCases.checkBorrowTypeBlocked, skipTestState); diff --git a/cypress/e2e/1-v3-markets/1-arbitrum-v3-market/0-assets/usdc.arbitrum-v3.cy.ts b/cypress/e2e/1-v3-markets/1-arbitrum-v3-market/0-assets/usdc.arbitrum-v3.cy.ts index 01592b4272..58bdeb7f71 100644 --- a/cypress/e2e/1-v3-markets/1-arbitrum-v3-market/0-assets/usdc.arbitrum-v3.cy.ts +++ b/cypress/e2e/1-v3-markets/1-arbitrum-v3-market/0-assets/usdc.arbitrum-v3.cy.ts @@ -52,12 +52,19 @@ const testData = { hasApproval: false, }, repay: [ + { + asset: assets.arbitrumMarket.USDC, + apyType: constants.apyType.stable, + amount: 2, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, { asset: assets.arbitrumMarket.USDC, apyType: constants.apyType.stable, amount: 2, hasApproval: true, - repayOption: constants.repayType.default, + repayOption: constants.repayType.wallet, }, { asset: assets.arbitrumMarket.USDC, @@ -87,7 +94,7 @@ const testData = { { type: constants.dashboardTypes.borrow, assetName: assets.arbitrumMarket.USDC.shortName, - amount: 46.0, + amount: 44.0, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/1-v3-markets/2-avalanche-v3-market/0-assets/usdc.avalanche-v3.cy.ts b/cypress/e2e/1-v3-markets/2-avalanche-v3-market/0-assets/usdc.avalanche-v3.cy.ts index eb69809d30..100048498c 100644 --- a/cypress/e2e/1-v3-markets/2-avalanche-v3-market/0-assets/usdc.avalanche-v3.cy.ts +++ b/cypress/e2e/1-v3-markets/2-avalanche-v3-market/0-assets/usdc.avalanche-v3.cy.ts @@ -52,12 +52,19 @@ const testData = { hasApproval: false, }, repay: [ + { + asset: assets.avalancheV3Market.USDC, + apyType: constants.apyType.stable, + amount: 2, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, { asset: assets.avalancheV3Market.USDC, apyType: constants.apyType.stable, amount: 2, hasApproval: true, - repayOption: constants.repayType.default, + repayOption: constants.repayType.wallet, }, { asset: assets.avalancheV3Market.USDC, @@ -87,7 +94,7 @@ const testData = { { type: constants.dashboardTypes.borrow, assetName: assets.avalancheV3Market.USDC.shortName, - amount: 46.0, + amount: 44.0, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/1-v3-markets/2-avalanche-v3-market/0-assets/usdt.avalacnhe-v3.cy.ts b/cypress/e2e/1-v3-markets/2-avalanche-v3-market/0-assets/usdt.avalacnhe-v3.cy.ts index e9cc302a5a..22bb45e68b 100644 --- a/cypress/e2e/1-v3-markets/2-avalanche-v3-market/0-assets/usdt.avalacnhe-v3.cy.ts +++ b/cypress/e2e/1-v3-markets/2-avalanche-v3-market/0-assets/usdt.avalacnhe-v3.cy.ts @@ -70,6 +70,13 @@ const testData = { hasApproval: true, repayOption: constants.repayType.default, }, + { + asset: assets.avalancheV3Market.USDT, + apyType: constants.apyType.stable, + amount: 2, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, ], withdraw: { asset: assets.avalancheV3Market.USDT, @@ -94,7 +101,7 @@ const testData = { { type: constants.dashboardTypes.borrow, assetName: assets.avalancheV3Market.USDT.shortName, - amount: 16.0, + amount: 14.0, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/1-v3-markets/3-polygon-v3-market/0-assets/usdc.polygon-v3.cy.ts b/cypress/e2e/1-v3-markets/3-polygon-v3-market/0-assets/usdc.polygon-v3.cy.ts index 279af0965a..7bbfc030ad 100644 --- a/cypress/e2e/1-v3-markets/3-polygon-v3-market/0-assets/usdc.polygon-v3.cy.ts +++ b/cypress/e2e/1-v3-markets/3-polygon-v3-market/0-assets/usdc.polygon-v3.cy.ts @@ -52,12 +52,19 @@ const testData = { hasApproval: false, }, repay: [ + { + asset: assets.polygonV3Market.USDC, + apyType: constants.apyType.stable, + amount: 2, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, { asset: assets.polygonV3Market.USDC, apyType: constants.apyType.stable, amount: 2, hasApproval: true, - repayOption: constants.repayType.default, + repayOption: constants.repayType.wallet, }, { asset: assets.polygonV3Market.USDC, @@ -87,7 +94,7 @@ const testData = { { type: constants.dashboardTypes.borrow, assetName: assets.polygonV3Market.USDC.shortName, - amount: 46.0, + amount: 44.0, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/1-v3-markets/3-polygon-v3-market/0-assets/usdt.polygon-v3.cy.ts b/cypress/e2e/1-v3-markets/3-polygon-v3-market/0-assets/usdt.polygon-v3.cy.ts index fdce66bf7c..2fd46c4a50 100644 --- a/cypress/e2e/1-v3-markets/3-polygon-v3-market/0-assets/usdt.polygon-v3.cy.ts +++ b/cypress/e2e/1-v3-markets/3-polygon-v3-market/0-assets/usdt.polygon-v3.cy.ts @@ -55,12 +55,19 @@ const testData = { hasApproval: false, }, repay: [ + { + asset: assets.polygonV3Market.USDT, + apyType: constants.apyType.stable, + amount: 2, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, { asset: assets.polygonV3Market.USDT, apyType: constants.apyType.stable, amount: 2, hasApproval: true, - repayOption: constants.repayType.default, + repayOption: constants.repayType.wallet, }, { asset: assets.polygonV3Market.USDT, @@ -94,7 +101,7 @@ const testData = { { type: constants.dashboardTypes.borrow, assetName: assets.polygonV3Market.USDT.shortName, - amount: 46.0, + amount: 44.0, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/1-v3-markets/3-polygon-v3-market/e-mode.polygon-v3.cy.ts b/cypress/e2e/1-v3-markets/3-polygon-v3-market/e-mode.polygon-v3.cy.ts index 7edac00438..9a87334ece 100644 --- a/cypress/e2e/1-v3-markets/3-polygon-v3-market/e-mode.polygon-v3.cy.ts +++ b/cypress/e2e/1-v3-markets/3-polygon-v3-market/e-mode.polygon-v3.cy.ts @@ -45,7 +45,6 @@ const testData = { assets.polygonV3Market.EURS, assets.polygonV3Market.jEUR, assets.polygonV3Market.agEUR, - assets.polygonV3Market.miMATIC, ], }, }; diff --git a/cypress/e2e/1-v3-markets/4-optimism-v3-market/0-assets/usdc.optimism-v3.cy.ts b/cypress/e2e/1-v3-markets/4-optimism-v3-market/0-assets/usdc.optimism-v3.cy.ts index bb4d57065b..d61c2d7a2a 100644 --- a/cypress/e2e/1-v3-markets/4-optimism-v3-market/0-assets/usdc.optimism-v3.cy.ts +++ b/cypress/e2e/1-v3-markets/4-optimism-v3-market/0-assets/usdc.optimism-v3.cy.ts @@ -52,12 +52,19 @@ const testData = { hasApproval: false, }, repay: [ + { + asset: assets.optimismMarket.USDC, + apyType: constants.apyType.stable, + amount: 2, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, { asset: assets.optimismMarket.USDC, apyType: constants.apyType.stable, amount: 2, hasApproval: true, - repayOption: constants.repayType.default, + repayOption: constants.repayType.wallet, }, { asset: assets.optimismMarket.USDC, @@ -87,7 +94,7 @@ const testData = { { type: constants.dashboardTypes.borrow, assetName: assets.optimismMarket.USDC.shortName, - amount: 46.0, + amount: 44.0, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/1-v3-markets/4-optimism-v3-market/0-assets/usdt.optimism-v3.cy.ts b/cypress/e2e/1-v3-markets/4-optimism-v3-market/0-assets/usdt.optimism-v3.cy.ts index 29ff2b2a73..067f6f13c6 100644 --- a/cypress/e2e/1-v3-markets/4-optimism-v3-market/0-assets/usdt.optimism-v3.cy.ts +++ b/cypress/e2e/1-v3-markets/4-optimism-v3-market/0-assets/usdt.optimism-v3.cy.ts @@ -55,12 +55,19 @@ const testData = { hasApproval: false, }, repay: [ + { + asset: assets.optimismMarket.USDT, + apyType: constants.apyType.stable, + amount: 2, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, { asset: assets.optimismMarket.USDT, apyType: constants.apyType.stable, amount: 2, hasApproval: true, - repayOption: constants.repayType.default, + repayOption: constants.repayType.wallet, }, { asset: assets.optimismMarket.USDT, @@ -94,7 +101,7 @@ const testData = { { type: constants.dashboardTypes.borrow, assetName: assets.optimismMarket.USDT.shortName, - amount: 46.0, + amount: 44.0, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/1-v3-markets/5-fantom-v3-market/0-assets/usdc.fantom-v3.cy.ts b/cypress/e2e/1-v3-markets/5-fantom-v3-market/0-assets/usdc.fantom-v3.cy.ts index 216acb955e..156aab5193 100644 --- a/cypress/e2e/1-v3-markets/5-fantom-v3-market/0-assets/usdc.fantom-v3.cy.ts +++ b/cypress/e2e/1-v3-markets/5-fantom-v3-market/0-assets/usdc.fantom-v3.cy.ts @@ -52,12 +52,19 @@ const testData = { hasApproval: false, }, repay: [ + { + asset: assets.fantomMarket.USDC, + apyType: constants.apyType.stable, + amount: 2, + hasApproval: false, + repayOption: constants.repayType.collateral, + }, { asset: assets.fantomMarket.USDC, apyType: constants.apyType.stable, amount: 2, hasApproval: true, - repayOption: constants.repayType.default, + repayOption: constants.repayType.wallet, }, { asset: assets.fantomMarket.USDC, @@ -87,7 +94,7 @@ const testData = { { type: constants.dashboardTypes.borrow, assetName: assets.fantomMarket.USDC.shortName, - amount: 46.0, + amount: 44.0, apyType: constants.borrowAPYType.stable, }, ], diff --git a/cypress/e2e/2-settings/wallet-connect.cy.ts b/cypress/e2e/2-settings/wallet-connect.cy.ts index 097231c1b5..61b37cab9f 100644 --- a/cypress/e2e/2-settings/wallet-connect.cy.ts +++ b/cypress/e2e/2-settings/wallet-connect.cy.ts @@ -11,21 +11,22 @@ export const closeModal = (selector: string) => { cy.get(selector).click(); }; +const walletButtonlocator = '#wallet-button'; + describe('Manipulation on the wallet connect', () => { describe('CASE1: Disconnect and connect wallet using Wallet connect option', () => { configEnvWithTenderlyMainnetFork({}); - const walletButton = '#wallet-button'; it('step1:Disconnect wallet', () => { cy.wait(1000); - cy.get(walletButton).click(); - cy.wait(6000); + cy.get(walletButtonlocator).click(); + cy.wait(3000); cy.contains('Disconnect').click(); cy.contains('Please, connect your wallet').should('be.visible'); }); it('step2:Connect wallet using wallet connect', () => { - cy.get(walletButton).click(); + cy.get(walletButtonlocator).click(); optionOnConnectionModal('WalletConnect'); checkElementsOnModal( '#walletconnect-qrcode-text', @@ -39,6 +40,9 @@ describe('Manipulation on the wallet connect', () => { describe('CASE2:Connect and disconnect wallet over Coinbase', () => { it('step1:Connect wallet over Coinbase', () => { + cy.wait(1000); + cy.get(walletButtonlocator).click(); + cy.wait(3000); optionOnConnectionModal('Coinbase'); checkElementsOnModal('.-cbwsdk-extension-dialog-box', 'Try the Coinbase Wallet extension'); closeModal('.-cbwsdk-extension-dialog-box-cancel'); diff --git a/cypress/fixtures/assets.json b/cypress/fixtures/assets.json index 74e2239e1a..43e37bd5d7 100644 --- a/cypress/fixtures/assets.json +++ b/cypress/fixtures/assets.json @@ -287,6 +287,11 @@ "fullName": "AAVE/ETH BPT", "shortName": "ABPT", "address": "0x41A08648C3766F9F9d85598fF102a08f4ef84F84" + }, + "MKR": { + "fullName": "MKR", + "shortName": "MKR", + "address": "0x7deB5e830be29F91E298ba5FF1356BB7f8146998" } }, "fantomMarket": { diff --git a/cypress/support/steps/main.steps.ts b/cypress/support/steps/main.steps.ts index 24a72b0839..fb9e99e6c6 100644 --- a/cypress/support/steps/main.steps.ts +++ b/cypress/support/steps/main.steps.ts @@ -176,6 +176,7 @@ export const repay = ( repayOption, hasApproval = false, repayableAsset, + assetForCollateralRepay, isMaxAmount = false, }: { asset: { shortName: string; fullName: string }; @@ -184,6 +185,7 @@ export const repay = ( repayOption: string; hasApproval: boolean; repayableAsset?: { shortName: string }; + assetForCollateralRepay?: { shortName: string }; isMaxAmount?: boolean; }, skip: SkipType, @@ -192,8 +194,15 @@ export const repay = ( const _shortName = asset.shortName; const _actionName = constants.actionTypes.repay; - return describe(`Repay by ${repayOption} process for ${_shortName} by ${ - repayableAsset ? repayableAsset.shortName : _shortName + return describe(`Repay by ${repayOption} process for ${_shortName} by + ${ + repayOption == constants.repayType.collateral + ? assetForCollateralRepay + ? assetForCollateralRepay.shortName + : 'default asset' + : repayableAsset + ? repayableAsset.shortName + : _shortName }`, () => { skipSetup({ skip, updateSkipStatus }); it(`Open ${_shortName} repay popup view`, () => { diff --git a/package.json b/package.json index c6357199b6..4b8ecc524e 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,8 @@ "test:ci": "jest --ci" }, "dependencies": { - "@aave/contract-helpers": "1.9.1-d6df625bf33ae6be0ed1a122ca941e3015691c6a.0", - "@aave/math-utils": "1.9.0", + "@aave/contract-helpers": "1.10.1-84cac2ea132250e855317992ead7740288ff03b2.0+7a35ce0", + "@aave/math-utils": "1.10.1-84cac2ea132250e855317992ead7740288ff03b2.0+7a35ce0", "@emotion/cache": "11.10.3", "@emotion/react": "11.10.4", "@emotion/server": "latest", diff --git a/public/icons/other/paraswap.svg b/public/icons/other/paraswap.svg new file mode 100644 index 0000000000..8803b657b1 --- /dev/null +++ b/public/icons/other/paraswap.svg @@ -0,0 +1 @@ + diff --git a/scripts/populate-cache.js b/scripts/populate-cache.js index 94bb0de26b..81294d964c 100644 --- a/scripts/populate-cache.js +++ b/scripts/populate-cache.js @@ -35725,6 +35725,8 @@ var require_types2 = __commonJS({ ProtocolAction2['repayCollateral'] = 'repayCollateral'; ProtocolAction2['withdrawETH'] = 'withdrawETH'; ProtocolAction2['borrowETH'] = 'borrwoETH'; + ProtocolAction2['supplyWithPermit'] = 'supplyWithPermit'; + ProtocolAction2['repayWithPermit'] = 'repayWithPermit'; })((ProtocolAction = exports2.ProtocolAction || (exports2.ProtocolAction = {}))); var GovernanceVote; (function (GovernanceVote2) { @@ -37479,6 +37481,14 @@ var require_utils6 = __commonJS({ limit: '700000', recommended: '700000', }, + [types_1.ProtocolAction.supplyWithPermit]: { + limit: '350000', + recommended: '350000', + }, + [types_1.ProtocolAction.repayWithPermit]: { + limit: '350000', + recommended: '350000', + }, }; exports2.mintAmountsPerToken = { AAVE: (0, exports2.valueToWei)('100', 18), @@ -75829,8 +75839,8 @@ var networkConfigs = { // src/utils/rotationProvider.ts var import_providers = __toESM(require_lib30()); var import_ethers = __toESM(require_lib31()); -var DEFAULT_ROTATION_DELAY = 5e3; var DEFAULT_FALL_FORWARD_DELAY = 6e4; +var MAX_RETRIES = 1; function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } @@ -75871,8 +75881,10 @@ var RotationProvider = class extends import_providers.BaseProvider { super(chainId); this.currentProviderIndex = 0; this.firstRotationTimestamp = 0; + this.maxRetries = 0; + this.retries = 0; this.providers = urls.map((url) => new import_providers.StaticJsonRpcProvider(url, chainId)); - this.rotationDelay = (config == null ? void 0 : config.rotationDelay) || DEFAULT_ROTATION_DELAY; + this.maxRetries = (config == null ? void 0 : config.maxRetries) || MAX_RETRIES; this.fallForwardDelay = (config == null ? void 0 : config.fallFowardDelay) || DEFAULT_FALL_FORWARD_DELAY; } @@ -75894,7 +75906,11 @@ var RotationProvider = class extends import_providers.BaseProvider { this.firstRotationTimestamp = new Date().getTime(); this.fallForwardRotation(); } else if (this.currentProviderIndex === this.providers.length - 1) { - yield sleep(this.rotationDelay); + this.retries += 1; + if (this.retries > this.maxRetries) { + this.retries = 0; + throw new Error('RotationProvider exceeded max number of retries'); + } this.currentProviderIndex = 0; } else { this.currentProviderIndex += 1; diff --git a/src/components/AddressBlocked.tsx b/src/components/AddressBlocked.tsx index 92649e50be..1f11fea062 100644 --- a/src/components/AddressBlocked.tsx +++ b/src/components/AddressBlocked.tsx @@ -6,8 +6,9 @@ import { useWeb3Context } from 'src/libs/hooks/useWeb3Context'; import { AddressBlockedModal } from './AddressBlockedModal'; export const AddressBlocked = ({ children }: { children: ReactNode }) => { - const { currentAccount, disconnectWallet } = useWeb3Context(); - const { isAllowed } = useAddressAllowed(currentAccount); + const { currentAccount, disconnectWallet, watchModeOnly } = useWeb3Context(); + const screenAddress = watchModeOnly ? '' : currentAccount; + const { isAllowed } = useAddressAllowed(screenAddress); if (!isAllowed) { return ( diff --git a/src/components/TextWithTooltip.tsx b/src/components/TextWithTooltip.tsx index 49829d37e6..4704457804 100644 --- a/src/components/TextWithTooltip.tsx +++ b/src/components/TextWithTooltip.tsx @@ -9,6 +9,7 @@ export interface TextWithTooltipProps extends TypographyProps { text?: ReactNode; icon?: ReactNode; iconSize?: number; + iconMargin?: number; color?: string; // eslint-disable-next-line children?: ReactElement>; @@ -18,6 +19,7 @@ export const TextWithTooltip = ({ text, icon, iconSize = 14, + iconMargin, color, children, ...rest @@ -39,7 +41,7 @@ export const TextWithTooltip = ({ borderRadius: '50%', p: 0, minWidth: 0, - ml: 0.5, + ml: iconMargin || 0.5, }} > { - return ( - Approval}> - - - Before supplying, you need to approve its usage by the Aave protocol. You can learn more - in our{' '} - - FAQ - - - - - ); -}; diff --git a/src/components/infoModalContents/RetryWithApprovalInfoContent.tsx b/src/components/infoModalContents/RetryWithApprovalInfoContent.tsx deleted file mode 100644 index 900103a0a4..0000000000 --- a/src/components/infoModalContents/RetryWithApprovalInfoContent.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Trans } from '@lingui/macro'; -import Typography from '@mui/material/Typography'; - -import { InfoContentWrapper } from './InfoContentWrapper'; - -export const RetryWithApprovalInfoContent = () => { - return ( - Retry with Approval}> - - Define Retry with Approval text - - - ); -}; diff --git a/src/components/infoTooltips/ApprovalTooltip.tsx b/src/components/infoTooltips/ApprovalTooltip.tsx new file mode 100644 index 0000000000..11715b2013 --- /dev/null +++ b/src/components/infoTooltips/ApprovalTooltip.tsx @@ -0,0 +1,19 @@ +import { Trans } from '@lingui/macro'; + +import { Link } from '../primitives/Link'; +import { TextWithTooltip, TextWithTooltipProps } from '../TextWithTooltip'; + +export const ApprovalTooltip = ({ ...rest }: TextWithTooltipProps) => { + return ( + + + To continue, you need to grant Aave smart contracts permission to move your funds from your + wallet. Depending on the asset and wallet you use, it is done by signing the permission + message (gas free), or by submitting an approval transaction (requires gas).{' '} + + Learn more + + + + ); +}; diff --git a/src/components/infoTooltips/PriceImpactTooltip.tsx b/src/components/infoTooltips/PriceImpactTooltip.tsx new file mode 100644 index 0000000000..a78f20d0da --- /dev/null +++ b/src/components/infoTooltips/PriceImpactTooltip.tsx @@ -0,0 +1,50 @@ +import { Trans } from '@lingui/macro'; +import { Box, Skeleton } from '@mui/material'; + +import { FormattedNumber } from '../primitives/FormattedNumber'; +import { TextWithTooltip, TextWithTooltipProps } from '../TextWithTooltip'; + +interface PriceImpactTooltipProps extends TextWithTooltipProps { + loading: boolean; + priceImpact: string; +} + +export const PriceImpactTooltip = ({ loading, priceImpact, ...rest }: PriceImpactTooltipProps) => { + return ( + + + Price impact{' '} + {loading ? ( + + ) : ( + + )} + % + + + } + {...rest} + > + + Price impact is the spread between the total value of the entry tokens swapped and the + destination tokens obtained (in USD), which results from the limited liquidity of the + trading pair. + + + ); +}; diff --git a/src/components/infoTooltips/SlippageTooltip.tsx b/src/components/infoTooltips/SlippageTooltip.tsx new file mode 100644 index 0000000000..76ba3d73d0 --- /dev/null +++ b/src/components/infoTooltips/SlippageTooltip.tsx @@ -0,0 +1,14 @@ +import { Trans } from '@lingui/macro'; + +import { TextWithTooltip, TextWithTooltipProps } from '../TextWithTooltip'; + +export const SlippageTooltip = ({ ...rest }: TextWithTooltipProps) => { + return ( + + + Slippage is the difference between the quoted and received amounts from changing market + conditions between the moment the transaction is submitted and its verification. + + + ); +}; diff --git a/src/components/transactions/AssetInput.tsx b/src/components/transactions/AssetInput.tsx index f2c7620c3c..aaa4fdb9d5 100644 --- a/src/components/transactions/AssetInput.tsx +++ b/src/components/transactions/AssetInput.tsx @@ -3,6 +3,7 @@ import { Trans } from '@lingui/macro'; import { Box, Button, + CircularProgress, FormControl, IconButton, InputBase, @@ -74,7 +75,8 @@ export interface AssetInputProps { maxValue?: string; isMaxSelected?: boolean; inputTitle?: ReactNode; - balanceText?: ReactNode; // Support translated text + balanceText?: ReactNode; + loading?: boolean; } export const AssetInput = ({ @@ -91,6 +93,7 @@ export const AssetInput = ({ isMaxSelected, inputTitle, balanceText, + loading = false, }: AssetInputProps) => { const handleSelect = (event: SelectChangeEvent) => { const newAsset = assets.find((asset) => asset.symbol === event.target.value) as T; @@ -121,35 +124,41 @@ export const AssetInput = ({ })} > - { - if (!onChange) return; - if (Number(e.target.value) > Number(maxValue)) { - onChange('-1'); - } else { - onChange(e.target.value); - } - }} - inputProps={{ - 'aria-label': 'amount input', - style: { - fontSize: '21px', - lineHeight: '28,01px', - padding: 0, - height: '28px', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - overflow: 'hidden', - }, - }} - // eslint-disable-next-line - inputComponent={NumberFormatCustom as any} - /> + {loading ? ( + + + + ) : ( + { + if (!onChange) return; + if (Number(e.target.value) > Number(maxValue)) { + onChange('-1'); + } else { + onChange(e.target.value); + } + }} + inputProps={{ + 'aria-label': 'amount input', + style: { + fontSize: '21px', + lineHeight: '28,01px', + padding: 0, + height: '28px', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + overflow: 'hidden', + }, + }} + // eslint-disable-next-line + inputComponent={NumberFormatCustom as any} + /> + )} {value !== '' && !disableInput && ( ({ '&.AssetInput__select .MuiOutlinedInput-notchedOutline': { display: 'none' }, '&.AssetInput__select .MuiSelect-icon': { color: 'text.primary', + right: '0%', }, }} renderValue={(symbol) => { @@ -245,15 +255,19 @@ export const AssetInput = ({ - + {loading ? ( + + ) : ( + + )} {asset.balance && onChange && ( <> @@ -267,14 +281,16 @@ export const AssetInput = ({ symbolsColor="text.disabled" /> - + {!disableInput && ( + + )} )} diff --git a/src/components/transactions/Borrow/BorrowModalContent.tsx b/src/components/transactions/Borrow/BorrowModalContent.tsx index 6dd39395f2..99fd5bfd0f 100644 --- a/src/components/transactions/Borrow/BorrowModalContent.tsx +++ b/src/components/transactions/Borrow/BorrowModalContent.tsx @@ -74,7 +74,7 @@ const BorrowModeSwitch = ({ value={interestRateMode} exclusive onChange={(_, value) => setInterestRateMode(value)} - sx={{ width: '100%', mt: 0.5 }} + sx={{ width: '100%', height: '36px', p: '2px', mt: 0.5 }} > void; +} + +export const ApprovalMethodToggleButton = ({ + currentMethod, + setMethod, +}: ApprovalMethodToggleButtonProps) => { + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + return ( + <> + + + {currentMethod} + + + + + + + + { + if (currentMethod === ApprovalMethod.APPROVE) { + setMethod(ApprovalMethod.PERMIT); + } + handleClose(); + }} + > + + {ApprovalMethod.PERMIT} + + + {currentMethod === ApprovalMethod.PERMIT && } + + + + { + if (currentMethod === ApprovalMethod.PERMIT) { + setMethod(ApprovalMethod.APPROVE); + } + handleClose(); + }} + > + + {ApprovalMethod.APPROVE} + + + {currentMethod === ApprovalMethod.APPROVE && } + + + + + ); +}; diff --git a/src/components/transactions/FlowCommons/LeftHelperText.tsx b/src/components/transactions/FlowCommons/LeftHelperText.tsx deleted file mode 100644 index 1d34488358..0000000000 --- a/src/components/transactions/FlowCommons/LeftHelperText.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { CheckIcon } from '@heroicons/react/outline'; -import { Trans } from '@lingui/macro'; -import { Box, Typography, useTheme } from '@mui/material'; - -import { ApprovalInfoContent } from '../../infoModalContents/ApprovalInfoContent'; -import { RetryWithApprovalInfoContent } from '../../infoModalContents/RetryWithApprovalInfoContent'; -import { TextWithModal } from '../../TextWithModal'; - -export type LeftHelperTextProps = { - error?: string; - approvalHash?: string; - amount?: string; -}; - -export const LeftHelperText = ({ error, approvalHash, amount }: LeftHelperTextProps) => { - const theme = useTheme(); - - return ( - - {approvalHash && ( - <> - - - Approve confirmed - - - )} - - {error && ( - Retry What?} - iconSize={13} - iconColor={theme.palette.text.secondary} - withContentButton - variant="helperText" - color="text.secondary" - > - - - )} - - {!approvalHash && !error && amount && ( - Why do I need to approve?} - iconSize={13} - iconColor={theme.palette.text.secondary} - withContentButton - variant="helperText" - color="text.secondary" - > - - - )} - - ); -}; diff --git a/src/components/transactions/FlowCommons/RightHelperText.tsx b/src/components/transactions/FlowCommons/RightHelperText.tsx index 1fc34fcaa9..d950dff498 100644 --- a/src/components/transactions/FlowCommons/RightHelperText.tsx +++ b/src/components/transactions/FlowCommons/RightHelperText.tsx @@ -1,11 +1,15 @@ import { ExternalLinkIcon } from '@heroicons/react/outline'; import { Trans } from '@lingui/macro'; -import { Box, Link, SvgIcon } from '@mui/material'; +import { Box, Link, SvgIcon, Typography } from '@mui/material'; +import { ApprovalMethodToggleButton } from 'src/components/transactions/FlowCommons/ApprovalMethodToggleButton'; import { MOCK_SIGNED_HASH } from 'src/helpers/useTransactionHandler'; import { useProtocolDataContext } from 'src/hooks/useProtocolDataContext'; +import { useRootStore } from 'src/store/root'; +import { ApprovalMethod } from 'src/store/walletSlice'; export type RightHelperTextProps = { approvalHash?: string; + tryPermit?: boolean; }; const ExtLinkIcon = () => ( @@ -14,32 +18,48 @@ const ExtLinkIcon = () => ( ); -export const RightHelperText = ({ approvalHash }: RightHelperTextProps) => { +export const RightHelperText = ({ approvalHash, tryPermit }: RightHelperTextProps) => { + const { walletApprovalMethodPreference, setWalletApprovalMethodPreference } = useRootStore(); + const usingPermit = tryPermit && walletApprovalMethodPreference; const { currentNetworkConfig } = useProtocolDataContext(); const isSigned = approvalHash === MOCK_SIGNED_HASH; - // a signature will not be reviewable on etherscan - if (!approvalHash || isSigned) return null; - return ( - - {approvalHash && ( - - Review approval tx details - - - )} - - ); + // a signature is not submitted on-chain so there is no link to review + if (!approvalHash && !isSigned && tryPermit) + return ( + + + Approve with  + + setWalletApprovalMethodPreference(method)} + /> + + ); + if (approvalHash && !usingPermit) + return ( + + {approvalHash && ( + + Review approval tx details + + + )} + + ); + return <>; }; diff --git a/src/components/transactions/FlowCommons/TxModalDetails.tsx b/src/components/transactions/FlowCommons/TxModalDetails.tsx index bd83563e7b..7fd4c0bdc8 100644 --- a/src/components/transactions/FlowCommons/TxModalDetails.tsx +++ b/src/components/transactions/FlowCommons/TxModalDetails.tsx @@ -2,7 +2,7 @@ import { ReserveIncentiveResponse } from '@aave/math-utils/dist/esm/formatters/i import { CheckIcon, ExclamationIcon } from '@heroicons/react/outline'; import { ArrowNarrowRightIcon } from '@heroicons/react/solid'; import { Trans } from '@lingui/macro'; -import { Box, FormControlLabel, SvgIcon, Switch, Typography } from '@mui/material'; +import { Box, FormControlLabel, Skeleton, SvgIcon, Switch, Typography } from '@mui/material'; import { parseUnits } from 'ethers/lib/utils'; import React, { ReactNode } from 'react'; import { CollateralType } from 'src/helpers/types'; @@ -16,9 +16,20 @@ import { GasStation } from '../GasStation/GasStation'; export interface TxModalDetailsProps { gasLimit?: string; + slippageSelector?: ReactNode; } -export const TxModalDetails: React.FC = ({ gasLimit, children }) => { +const ArrowRightIcon = ( + + + +); + +export const TxModalDetails: React.FC = ({ + gasLimit, + slippageSelector, + children, +}) => { return ( @@ -37,8 +48,10 @@ export const TxModalDetails: React.FC = ({ gasLimit, childr > {children} - - + + + {slippageSelector} + ); }; @@ -49,6 +62,7 @@ interface DetailsNumberLineProps extends FormattedNumberProps { futureValue?: FormattedNumberProps['value']; numberPrefix?: ReactNode; iconSymbol?: string; + loading?: boolean; } export const DetailsNumberLine = ({ @@ -57,20 +71,25 @@ export const DetailsNumberLine = ({ futureValue, numberPrefix, iconSymbol, + loading = false, ...rest }: DetailsNumberLineProps) => { return ( - {iconSymbol && } - {numberPrefix && {numberPrefix}} - - {futureValue && ( + {loading ? ( + + ) : ( <> - - - - + {iconSymbol && } + {numberPrefix && {numberPrefix}} + + {futureValue && ( + <> + {ArrowRightIcon} + + + )} )} @@ -87,6 +106,8 @@ interface DetailsNumberLineWithSubProps { futureValueUSD: string; hideSymbolSuffix?: boolean; color?: string; + tokenIcon?: string; + loading?: boolean; } export const DetailsNumberLineWithSub = ({ @@ -98,42 +119,55 @@ export const DetailsNumberLineWithSub = ({ futureValueUSD, hideSymbolSuffix, color, + tokenIcon, + loading = false, }: DetailsNumberLineWithSubProps) => { return ( - - {value && ( - <> - + {loading ? ( + <> + + + + ) : ( + <> + + {value && ( + <> + + {!hideSymbolSuffix && ( + + {symbol} + + )} + {ArrowRightIcon} + + )} + {tokenIcon && } + {!hideSymbolSuffix && ( {symbol} )} - - - - - )} - - {!hideSymbolSuffix && ( - - {symbol} - - )} - - - {valueUSD && ( - <> - - - - - - )} - - + + + {valueUSD && ( + <> + + {ArrowRightIcon} + + )} + + + + )} ); @@ -198,6 +232,7 @@ interface DetailsIncentivesLineProps { incentives?: ReserveIncentiveResponse[]; // the token yielding the incentive, not the incentive itself symbol: string; + loading?: boolean; } export const DetailsIncentivesLine = ({ @@ -205,19 +240,31 @@ export const DetailsIncentivesLine = ({ symbol, futureIncentives, futureSymbol, + loading = false, }: DetailsIncentivesLineProps) => { if (!incentives || incentives.filter((i) => i.incentiveAPR !== '0').length === 0) return null; return ( Rewards APR} captionVariant="description" mb={4} minHeight={24}> - - {futureSymbol && ( - <> - - - - - - )} + + {loading ? ( + + ) : ( + <> + + {futureSymbol && ( + <> + {ArrowRightIcon} + + {futureIncentives && futureIncentives.length === 0 && ( + + None + + )} + + )} + + )} + ); }; @@ -226,12 +273,14 @@ export interface DetailsHFLineProps { healthFactor: string; futureHealthFactor: string; visibleHfChange: boolean; + loading?: boolean; } export const DetailsHFLine = ({ healthFactor, futureHealthFactor, visibleHfChange, + loading = false, }: DetailsHFLineProps) => { if (healthFactor === '-1' && futureHealthFactor === '-1') return null; return ( @@ -243,18 +292,22 @@ export const DetailsHFLine = ({ > - - - {visibleHfChange && ( + {loading ? ( + + ) : ( <> - - - + + + {visibleHfChange && ( + <> + {ArrowRightIcon} - + + + )} )} diff --git a/src/components/transactions/MigrateV3/MigrateV3Actions.tsx b/src/components/transactions/MigrateV3/MigrateV3Actions.tsx index ddfb3432e9..ab0e5f1fed 100644 --- a/src/components/transactions/MigrateV3/MigrateV3Actions.tsx +++ b/src/components/transactions/MigrateV3/MigrateV3Actions.tsx @@ -1,3 +1,4 @@ +import { ProtocolAction } from '@aave/contract-helpers'; import { Trans } from '@lingui/macro'; import { useTransactionHandler } from 'src/helpers/useTransactionHandler'; import { useRootStore } from 'src/store/root'; @@ -20,6 +21,7 @@ export const MigrateV3Actions = ({ isWrongNetwork, blocked }: MigrateV3ActionsPr handleGetTxns: async () => migrateWithoutPermits(), handleGetPermitTxns: async (signatures, deadline) => migrateWithPermits(signatures, deadline), tryPermit: true, + permitAction: ProtocolAction.migrateV3, }); const handleApproval = async () => { @@ -39,6 +41,7 @@ export const MigrateV3Actions = ({ isWrongNetwork, blocked }: MigrateV3ActionsPr blocked={blocked} actionText={Migrate} actionInProgressText={Migrating} + tryPermit /> ); }; diff --git a/src/components/transactions/Repay/CollateralRepayActions.tsx b/src/components/transactions/Repay/CollateralRepayActions.tsx index f7a08f5220..044c53df9a 100644 --- a/src/components/transactions/Repay/CollateralRepayActions.tsx +++ b/src/components/transactions/Repay/CollateralRepayActions.tsx @@ -1,14 +1,19 @@ -import { InterestRate } from '@aave/contract-helpers'; +import { + API_ETH_MOCK_ADDRESS, + gasLimitRecommendations, + InterestRate, + ProtocolAction, +} from '@aave/contract-helpers'; import { Trans } from '@lingui/macro'; import { BoxProps } from '@mui/material'; -import { OptimalRate } from 'paraswap-core'; -import { useTransactionHandler } from 'src/helpers/useTransactionHandler'; +import { useParaSwapTransactionHandler } from 'src/helpers/useParaSwapTransactionHandler'; import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider'; +import { SwapTransactionParams } from 'src/hooks/paraswap/common'; import { useRootStore } from 'src/store/root'; import { TxActionsWrapper } from '../TxActionsWrapper'; -export interface RepayActionProps extends BoxProps { +interface CollateralRepayBaseProps extends BoxProps { rateMode: InterestRate; repayAmount: string; repayWithAmount: string; @@ -17,65 +22,79 @@ export interface RepayActionProps extends BoxProps { isWrongNetwork: boolean; customGasPrice?: string; symbol: string; - priceRoute: OptimalRate | null; repayAllDebt: boolean; useFlashLoan: boolean; blocked: boolean; - maxSlippage: number; + loading?: boolean; +} + +// Used in poolSlice +export interface CollateralRepayActionProps extends CollateralRepayBaseProps { + augustus: string; + swapCallData: string; } export const CollateralRepayActions = ({ repayAmount, - repayWithAmount, poolReserve, fromAssetData, isWrongNetwork, sx, symbol, rateMode, - priceRoute, repayAllDebt, useFlashLoan, blocked, - maxSlippage, + loading, + repayWithAmount, + buildTxFn, ...props -}: RepayActionProps) => { +}: CollateralRepayBaseProps & { buildTxFn: () => Promise }) => { const paraswapRepayWithCollateral = useRootStore((state) => state.paraswapRepayWithCollateral); const { approval, action, requiresApproval, loadingTxns, approvalTxState, mainTxState } = - useTransactionHandler({ + useParaSwapTransactionHandler({ handleGetTxns: async () => { + const route = await buildTxFn(); + return paraswapRepayWithCollateral({ + repayAllDebt, + repayAmount: route.outputAmount, + rateMode, + repayWithAmount: route.inputAmount, + fromAssetData, + poolReserve, + isWrongNetwork, + symbol, + useFlashLoan, + blocked, + swapCallData: route.swapCallData, + augustus: route.augustus, + }); + }, + handleGetApprovalTxns: async () => { return paraswapRepayWithCollateral({ repayAllDebt, repayAmount, - maxSlippage, rateMode, repayWithAmount, fromAssetData, poolReserve, isWrongNetwork, symbol, - priceRoute, useFlashLoan, blocked, + swapCallData: '0x', + augustus: API_ETH_MOCK_ADDRESS, }); }, - skip: !repayAmount || parseFloat(repayAmount) === 0 || blocked, - deps: [ - repayWithAmount, - repayAmount, - priceRoute, - poolReserve.underlyingAsset, - fromAssetData.underlyingAsset, - repayAllDebt, - useFlashLoan, - ], + gasLimitRecommendation: gasLimitRecommendations[ProtocolAction.repayCollateral].limit, + skip: loading || !repayAmount || parseFloat(repayAmount) === 0 || blocked, }); return ( Repay {symbol}} actionInProgressText={Repaying {symbol}} + fetchingData={loading} + errorParams={{ + loading: false, + disabled: blocked, + content: Repay {symbol}, + handleClick: action, + }} /> ); }; diff --git a/src/components/transactions/Repay/CollateralRepayModalContent.tsx b/src/components/transactions/Repay/CollateralRepayModalContent.tsx index 35f40899fe..b9cd6a1f0f 100644 --- a/src/components/transactions/Repay/CollateralRepayModalContent.tsx +++ b/src/components/transactions/Repay/CollateralRepayModalContent.tsx @@ -1,22 +1,22 @@ import { InterestRate } from '@aave/contract-helpers'; -import { USD_DECIMALS, valueToBigNumber } from '@aave/math-utils'; +import { valueToBigNumber } from '@aave/math-utils'; +import { ArrowDownIcon } from '@heroicons/react/outline'; import { Trans } from '@lingui/macro'; -import { Box, Typography } from '@mui/material'; +import { Box, SvgIcon, Typography } from '@mui/material'; import BigNumber from 'bignumber.js'; import { useRef, useState } from 'react'; -import { FormattedNumber } from 'src/components/primitives/FormattedNumber'; -import { Row } from 'src/components/primitives/Row'; -import StyledToggleButton from 'src/components/StyledToggleButton'; -import StyledToggleButtonGroup from 'src/components/StyledToggleButtonGroup'; +import { PriceImpactTooltip } from 'src/components/infoTooltips/PriceImpactTooltip'; import { ComputedReserveData, ComputedUserReserveData, useAppDataContext, } from 'src/hooks/app-data-provider/useAppDataProvider'; +import { SwapVariant } from 'src/hooks/paraswap/common'; +import { useCollateralRepaySwap } from 'src/hooks/paraswap/useCollateralRepaySwap'; import { useModalContext } from 'src/hooks/useModal'; import { useProtocolDataContext } from 'src/hooks/useProtocolDataContext'; -import { useSwap } from 'src/hooks/useSwap'; import { useWeb3Context } from 'src/libs/hooks/useWeb3Context'; +import { ListSlippageButton } from 'src/modules/dashboard/lists/SlippageList'; import { calculateHFAfterRepay } from 'src/utils/hfUtils'; import { Asset, AssetInput } from '../AssetInput'; @@ -38,11 +38,12 @@ export function CollateralRepayModalContent({ userReserve, isWrongNetwork, }: ModalWrapperProps & { debtType: InterestRate }) { - const { user, marketReferencePriceInUsd, reserves, userReserves } = useAppDataContext(); + const { user, reserves, userReserves } = useAppDataContext(); const { gasLimit, txError, mainTxState } = useModalContext(); const { currentChainId, currentNetworkConfig } = useProtocolDataContext(); const { currentAccount } = useWeb3Context(); + // List of tokens eligble to repay with, ordered by USD value const repayTokens = user.userReservesData .filter( (userReserve) => @@ -52,69 +53,104 @@ export function CollateralRepayModalContent({ .map((userReserve) => ({ address: userReserve.underlyingAsset, balance: userReserve.underlyingBalance, + balanceUSD: userReserve.underlyingBalanceUSD, symbol: userReserve.reserve.symbol, iconSymbol: userReserve.reserve.iconSymbol, - aToken: true, - })); + })) + .sort((a, b) => Number(b.balanceUSD) - Number(a.balanceUSD)); const [tokenToRepayWith, setTokenToRepayWith] = useState(repayTokens[0]); + const tokenToRepayWithBalance = tokenToRepayWith.balance || '0'; - const fromAssetData = reserves.find( - (reserve) => reserve.underlyingAsset === tokenToRepayWith.address - ) as ComputedReserveData; - - const repayWithUserReserve = userReserves.find( - (userReserve) => userReserve.underlyingAsset === tokenToRepayWith.address - ) as ComputedUserReserveData; - - const [_amount, setAmount] = useState(''); + const [swapVariant, setSwapVariant] = useState('exactOut'); + const [amount, setAmount] = useState(''); const [maxSlippage, setMaxSlippage] = useState('0.5'); const amountRef = useRef(''); + const collateralReserveData = reserves.find( + (reserve) => reserve.underlyingAsset === tokenToRepayWith.address + ) as ComputedReserveData; + const debt = debtType === InterestRate.Stable ? userReserve?.stableBorrows || '0' : userReserve?.variableBorrows || '0'; const safeAmountToRepayAll = valueToBigNumber(debt).multipliedBy('1.0025'); - const isMaxSelected = _amount === '-1'; - const amount = isMaxSelected ? safeAmountToRepayAll.toString() : _amount; - const usdValue = valueToBigNumber(amount).multipliedBy(poolReserve.priceInUSD); + const isMaxSelected = amount === '-1'; + const repayAmount = isMaxSelected ? safeAmountToRepayAll.toString() : amount; + const repayAmountUsdValue = valueToBigNumber(repayAmount) + .multipliedBy(poolReserve.priceInUSD) + .toString(); + + // The slippage is factored into the collateral amount because when we swap for 'exactOut', positive slippage is applied on the collateral amount. + const collateralAmountRequiredToCoverDebt = safeAmountToRepayAll + .multipliedBy(poolReserve.priceInUSD) + .multipliedBy(100 + Number(maxSlippage)) + .dividedBy(100) + .dividedBy(collateralReserveData.priceInUSD); - const { priceRoute, inputAmountUSD, inputAmount, outputAmount, outputAmountUSD } = useSwap({ + const swapIn = { ...collateralReserveData, amount: tokenToRepayWithBalance }; + const swapOut = { ...poolReserve, amount: amountRef.current }; + if (swapVariant === 'exactIn') { + swapIn.amount = tokenToRepayWithBalance; + swapOut.amount = '0'; + } + + const repayAllDebt = + isMaxSelected && + valueToBigNumber(tokenToRepayWithBalance).gte(collateralAmountRequiredToCoverDebt); + + const { + inputAmountUSD, + inputAmount, + outputAmount, + outputAmountUSD, + loading: routeLoading, + error, + buildTxFn, + } = useCollateralRepaySwap({ chainId: currentNetworkConfig.underlyingChainId || currentChainId, - userId: currentAccount, - variant: 'exactOut', - swapIn: { ...fromAssetData, amount: '0' }, - swapOut: { ...poolReserve, amount: amountRef.current }, - max: isMaxSelected, - skip: mainTxState.loading, + userAddress: currentAccount, + swapVariant: swapVariant, + swapIn, + swapOut, + max: repayAllDebt, + skip: mainTxState.loading || false, + maxSlippage: Number(maxSlippage), }); - // Calculations to get the max repayable debt depending on the balance and value of the - // selected collateral - const maxCollateral = valueToBigNumber(tokenToRepayWith?.balance || 0).multipliedBy( - fromAssetData.priceInMarketReferenceCurrency - ); - const maxDebtThatCanBeRepaidWithSelectedCollateral = maxCollateral.dividedBy( - poolReserve.priceInMarketReferenceCurrency - ); - const maxRepayableDebt = BigNumber.min( - maxDebtThatCanBeRepaidWithSelectedCollateral, - safeAmountToRepayAll - ); - const handleChange = (value: string) => { + const loadingSkeleton = routeLoading && inputAmountUSD === '0'; + + const handleRepayAmountChange = (value: string) => { const maxSelected = value === '-1'; - amountRef.current = maxSelected ? maxRepayableDebt.toString(10) : value; - setAmount(value); + + if ( + maxSelected && + valueToBigNumber(tokenToRepayWithBalance).lt(collateralAmountRequiredToCoverDebt) + ) { + // The selected collateral amount is not enough to pay the full debt. We'll try to do a swap using the exact amount of collateral. + // The amount won't be known until we fetch the swap data, so we'll clear it out. Once the swap data is fetched, we'll set the amount. + amountRef.current = ''; + setAmount(''); + setSwapVariant('exactIn'); + } else { + amountRef.current = maxSelected ? safeAmountToRepayAll.toString(10) : value; + setAmount(value); + setSwapVariant('exactOut'); + } }; + // for v3 we need hf after withdraw collateral, because when removing collateral to repay // debt, hf could go under 1 then it would fail. If that is the case then we need // to use flashloan path + const repayWithUserReserve = userReserves.find( + (userReserve) => userReserve.underlyingAsset === tokenToRepayWith.address + ) as ComputedUserReserveData; const { hfAfterSwap, hfEffectOfFromAmount } = calculateHFAfterRepay({ amountToReceiveAfterSwap: outputAmount, amountToSwap: inputAmount, - fromAssetData, + fromAssetData: collateralReserveData, user, toAssetData: poolReserve, repayWithUserReserve, @@ -134,24 +170,26 @@ export function CollateralRepayModalContent({ // a safe amount to repay all. When this happens amountAfterRepay would be < 0 and // this would show as certain amount left to repay when we are actually repaying all debt const amountAfterRepay = valueToBigNumber(debt).minus(BigNumber.min(outputAmount, debt)); - const displayAmountAfterRepayInUsd = amountAfterRepay - .multipliedBy(poolReserve.formattedPriceInMarketReferenceCurrency) - .multipliedBy(marketReferencePriceInUsd) - .shiftedBy(-USD_DECIMALS); + const displayAmountAfterRepayInUsd = amountAfterRepay.multipliedBy(poolReserve.priceInUSD); + const collateralAmountAfterRepay = tokenToRepayWithBalance + ? valueToBigNumber(tokenToRepayWithBalance).minus(inputAmount) + : valueToBigNumber('0'); + const collateralAmountAfterRepayUSD = collateralAmountAfterRepay.multipliedBy( + collateralReserveData.priceInUSD + ); // calculate impact based on $ difference - const priceImpact = + let priceImpact = outputAmountUSD && outputAmountUSD !== '0' - ? new BigNumber(1) - .minus(new BigNumber(inputAmountUSD).dividedBy(outputAmountUSD)) - .toString(10) + ? new BigNumber(1).minus(new BigNumber(inputAmountUSD).dividedBy(outputAmountUSD)).toFixed(2) : '0'; + if (priceImpact === '-0.00') { + priceImpact = '0.00'; + } let blockingError: ErrorType | undefined = undefined; - const tokenToRepayWithUsdValue = valueToBigNumber(tokenToRepayWith?.balance || '0').multipliedBy( - fromAssetData.priceInUSD - ); - if (Number(usdValue) > Number(tokenToRepayWithUsdValue.toString(10))) { + + if (valueToBigNumber(tokenToRepayWithBalance).lt(inputAmount)) { blockingError = ErrorType.NOT_ENOUGH_COLLATERAL_TO_REPAY_WITH; } else if (disableFlashLoan) { blockingError = ErrorType.FLASH_LOAN_NOT_AVAILABLE; @@ -176,18 +214,17 @@ export function CollateralRepayModalContent({ if (mainTxState.success) return ( repaid} - amount={amountRef.current} + action={Repaid} + amount={swapVariant === 'exactIn' ? outputAmount : repayAmount} symbol={poolReserve.symbol} /> ); - return ( <> Debt amount to repay} + inputTitle={Expected amount to repay} balanceText={Borrow balance} /> + + + + + + + Collateral amount to repay with} + onChange={handleRepayAmountChange} + inputTitle={Collateral to repay with} balanceText={Borrow balance} + maxValue={tokenToRepayWithBalance} + loading={loadingSkeleton} disableInput /> + {error && !loadingSkeleton && ( + + {error} + + )} {blockingError !== undefined && ( {handleBlocked()} )} - + } > - Price impact} captionVariant="subheader1"> - - - Minimum received} captionVariant="subheader1" sx={{ mt: 4 }}> - - - - Max slippage rate - - setMaxSlippage(value)} - exclusive - > - - 0.1% - - - 0.5% - - - 1% - - - - + Remaining debt} + description={Borrow balance after repay} futureValue={amountAfterRepay.toString()} futureValueUSD={displayAmountAfterRepayInUsd.toString()} symbol={symbol} + tokenIcon={poolReserve.iconSymbol} + loading={loadingSkeleton} + hideSymbolSuffix /> - Collateral balance after repay} + futureValue={collateralAmountAfterRepay.toString()} + futureValueUSD={collateralAmountAfterRepayUSD.toString()} + symbol={tokenToRepayWith.symbol} + tokenIcon={tokenToRepayWith.iconSymbol} + loading={loadingSkeleton} + hideSymbolSuffix /> @@ -270,17 +306,17 @@ export function CollateralRepayModalContent({ ); diff --git a/src/components/transactions/Repay/RepayActions.tsx b/src/components/transactions/Repay/RepayActions.tsx index 60016affba..84be4acee8 100644 --- a/src/components/transactions/Repay/RepayActions.tsx +++ b/src/components/transactions/Repay/RepayActions.tsx @@ -1,12 +1,9 @@ -import { InterestRate } from '@aave/contract-helpers'; +import { InterestRate, ProtocolAction } from '@aave/contract-helpers'; import { Trans } from '@lingui/macro'; import { BoxProps } from '@mui/material'; -import { utils } from 'ethers'; import { useTransactionHandler } from 'src/helpers/useTransactionHandler'; import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider'; -import { useProtocolDataContext } from 'src/hooks/useProtocolDataContext'; import { useRootStore } from 'src/store/root'; -import { permitByChainAndToken } from 'src/ui-config/permitConfig'; import { TxActionsWrapper } from '../TxActionsWrapper'; @@ -34,15 +31,13 @@ export const RepayActions = ({ blocked, ...props }: RepayActionProps) => { - const { currentChainId: chainId, currentMarketData } = useProtocolDataContext(); - const repay = useRootStore((state) => state.repay); - const repayWithPermit = useRootStore((state) => state.repayWithPermit); + const { repay, repayWithPermit, tryPermit } = useRootStore(); + const usingPermit = tryPermit(poolAddress); const { approval, action, requiresApproval, loadingTxns, approvalTxState, mainTxState } = useTransactionHandler({ - // move tryPermit to store - tryPermit: - currentMarketData.v3 && permitByChainAndToken[chainId]?.[utils.getAddress(poolAddress)], + tryPermit: usingPermit, + permitAction: ProtocolAction.repayWithPermit, handleGetTxns: async () => { return repay({ amountToRepay, @@ -88,6 +83,7 @@ export const RepayActions = ({ handleApproval={() => approval([{ amount: amountToRepay, underlyingAsset: poolAddress }])} actionText={Repay {symbol}} actionInProgressText={Repaying {symbol}} + tryPermit={usingPermit} /> ); }; diff --git a/src/components/transactions/Repay/RepayModal.tsx b/src/components/transactions/Repay/RepayModal.tsx index 782cc23e7d..5ebaf5f3db 100644 --- a/src/components/transactions/Repay/RepayModal.tsx +++ b/src/components/transactions/Repay/RepayModal.tsx @@ -16,6 +16,7 @@ export const RepayModal = () => { const { type, close, args, mainTxState } = useModalContext() as ModalContextType<{ underlyingAsset: string; currentRateMode: InterestRate; + isFrozen: boolean; }>; const { userReserves } = useAppDataContext(); const { currentMarketData } = useProtocolDataContext(); @@ -25,12 +26,14 @@ export const RepayModal = () => { // 1. on chains with paraswap deployed // 2. when you have a different supplied(not necessarily collateral) asset then the one your debt is in // For repaying your debt with the same assets aToken you can use repayWithAToken on aave protocol v3 + // 3. the supplied asset is not frozen const collateralRepayPossible = isFeatureEnabled.collateralRepay(currentMarketData) && userReserves.some( (userReserve) => userReserve.scaledATokenBalance !== '0' && - userReserve.underlyingAsset !== args.underlyingAsset + userReserve.underlyingAsset !== args.underlyingAsset && + !args.isFrozen ); return ( diff --git a/src/components/transactions/Repay/RepayModalContent.tsx b/src/components/transactions/Repay/RepayModalContent.tsx index 01f0c27e5f..58abfc4152 100644 --- a/src/components/transactions/Repay/RepayModalContent.tsx +++ b/src/components/transactions/Repay/RepayModalContent.tsx @@ -171,13 +171,12 @@ export const RepayModalContent = ({ const amountAfterRepay = valueToBigNumber(debt) .minus(amount || '0') .toString(10); - const displayAmountAfterRepay = BigNumber.min(amountAfterRepay, maxAmountToRepay); - const displayAmountAfterRepayInUsd = displayAmountAfterRepay + const amountAfterRepayInUsd = new BigNumber(amountAfterRepay) .multipliedBy(poolReserve.formattedPriceInMarketReferenceCurrency) .multipliedBy(marketReferencePriceInUsd) .shiftedBy(-USD_DECIMALS); - const maxRepayWithDustRemaining = isMaxSelected && displayAmountAfterRepayInUsd.toNumber() > 0; + const maxRepayWithDustRemaining = isMaxSelected && amountAfterRepayInUsd.toNumber() > 0; // health factor calculations // we use usd values instead of MarketreferenceCurrency so it has same precision @@ -236,7 +235,7 @@ export const RepayModalContent = ({ Remaining debt} futureValue={amountAfterRepay} - futureValueUSD={displayAmountAfterRepayInUsd.toString(10)} + futureValueUSD={amountAfterRepayInUsd.toString(10)} value={debt} valueUSD={debtUSD.toString()} symbol={ diff --git a/src/components/transactions/Repay/RepayTypeSelector.tsx b/src/components/transactions/Repay/RepayTypeSelector.tsx index fb573c6f24..97981ea306 100644 --- a/src/components/transactions/Repay/RepayTypeSelector.tsx +++ b/src/components/transactions/Repay/RepayTypeSelector.tsx @@ -28,7 +28,7 @@ export function RepayTypeSelector({ value={repayType} exclusive onChange={(_, value) => setRepayType(value)} - sx={{ width: '100%' }} + sx={{ width: '100%', height: '36px', p: '2px' }} > diff --git a/src/components/transactions/Supply/SupplyActions.tsx b/src/components/transactions/Supply/SupplyActions.tsx index cf1df15c67..66caf649f9 100644 --- a/src/components/transactions/Supply/SupplyActions.tsx +++ b/src/components/transactions/Supply/SupplyActions.tsx @@ -1,10 +1,8 @@ +import { ProtocolAction } from '@aave/contract-helpers'; import { Trans } from '@lingui/macro'; import { BoxProps } from '@mui/material'; -import { utils } from 'ethers'; import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider'; -import { useProtocolDataContext } from 'src/hooks/useProtocolDataContext'; import { useRootStore } from 'src/store/root'; -import { permitByChainAndToken } from 'src/ui-config/permitConfig'; import { useTransactionHandler } from '../../../helpers/useTransactionHandler'; import { TxActionsWrapper } from '../TxActionsWrapper'; @@ -28,15 +26,13 @@ export const SupplyActions = ({ blocked, ...props }: SupplyActionProps) => { - const { currentChainId: chainId, currentMarketData } = useProtocolDataContext(); - const supply = useRootStore((state) => state.supply); - const supplyWithPermit = useRootStore((state) => state.supplyWithPermit); + const { supply, supplyWithPermit, tryPermit } = useRootStore(); + const usingPermit = tryPermit(poolAddress); const { approval, action, requiresApproval, loadingTxns, approvalTxState, mainTxState } = useTransactionHandler({ - // TODO: move tryPermit - tryPermit: - currentMarketData.v3 && permitByChainAndToken[chainId]?.[utils.getAddress(poolAddress)], + tryPermit: usingPermit, + permitAction: ProtocolAction.supplyWithPermit, handleGetTxns: async () => { return supply({ amountToSupply, @@ -66,12 +62,14 @@ export const SupplyActions = ({ isWrongNetwork={isWrongNetwork} requiresAmount amount={amountToSupply} + symbol={symbol} preparingTransactions={loadingTxns} actionText={Supply {symbol}} actionInProgressText={Supplying {symbol}} handleApproval={() => approval([{ amount: amountToSupply, underlyingAsset: poolAddress }])} handleAction={action} requiresApproval={requiresApproval} + tryPermit={usingPermit} sx={sx} {...props} /> diff --git a/src/components/transactions/Swap/SwapActions.tsx b/src/components/transactions/Swap/SwapActions.tsx index e12c716ec0..6f30b187c8 100644 --- a/src/components/transactions/Swap/SwapActions.tsx +++ b/src/components/transactions/Swap/SwapActions.tsx @@ -1,13 +1,18 @@ +import { + API_ETH_MOCK_ADDRESS, + gasLimitRecommendations, + ProtocolAction, +} from '@aave/contract-helpers'; import { Trans } from '@lingui/macro'; import { BoxProps } from '@mui/material'; -import { OptimalRate } from 'paraswap-core'; +import { useParaSwapTransactionHandler } from 'src/helpers/useParaSwapTransactionHandler'; import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider'; +import { SwapTransactionParams } from 'src/hooks/paraswap/common'; import { useRootStore } from 'src/store/root'; -import { useTransactionHandler } from '../../../helpers/useTransactionHandler'; import { TxActionsWrapper } from '../TxActionsWrapper'; -export interface SwapActionProps extends BoxProps { +interface SwapBaseProps extends BoxProps { amountToSwap: string; amountToReceive: string; poolReserve: ComputedReserveData; @@ -16,9 +21,14 @@ export interface SwapActionProps extends BoxProps { customGasPrice?: string; symbol: string; blocked: boolean; - priceRoute: OptimalRate | null; isMaxSelected: boolean; useFlashLoan: boolean; + loading?: boolean; +} + +export interface SwapActionProps extends SwapBaseProps { + swapCallData: string; + augustus: string; } export const SwapActions = ({ @@ -28,18 +38,35 @@ export const SwapActions = ({ sx, poolReserve, targetReserve, - priceRoute, isMaxSelected, useFlashLoan, + loading, symbol, blocked, + buildTxFn, ...props -}: SwapActionProps) => { +}: SwapBaseProps & { buildTxFn: () => Promise }) => { const swapCollateral = useRootStore((state) => state.swapCollateral); const { approval, action, requiresApproval, approvalTxState, mainTxState, loadingTxns } = - useTransactionHandler({ + useParaSwapTransactionHandler({ handleGetTxns: async () => { + const route = await buildTxFn(); + return swapCollateral({ + amountToSwap: route.inputAmount, + amountToReceive: route.outputAmount, + poolReserve, + targetReserve, + isWrongNetwork, + symbol, + blocked, + isMaxSelected, + useFlashLoan, + swapCallData: route.swapCallData, + augustus: route.augustus, + }); + }, + handleGetApprovalTxns: async () => { return swapCollateral({ amountToSwap, amountToReceive, @@ -48,21 +75,14 @@ export const SwapActions = ({ isWrongNetwork, symbol, blocked, - priceRoute, isMaxSelected, useFlashLoan, + swapCallData: '0x', + augustus: API_ETH_MOCK_ADDRESS, }); }, - skip: !priceRoute || !amountToSwap || parseFloat(amountToSwap) === 0, - deps: [ - amountToSwap, - amountToReceive, - priceRoute, - poolReserve.underlyingAsset, - targetReserve.underlyingAsset, - isMaxSelected, - useFlashLoan, - ], + gasLimitRecommendation: gasLimitRecommendations[ProtocolAction.swapCollateral].limit, + skip: loading || !amountToSwap || parseFloat(amountToSwap) === 0, }); return ( @@ -74,13 +94,18 @@ export const SwapActions = ({ handleAction={action} requiresAmount amount={amountToSwap} - handleApproval={() => - approval([{ amount: amountToSwap, underlyingAsset: poolReserve.aTokenAddress }]) - } + handleApproval={() => approval()} requiresApproval={requiresApproval} actionText={Swap} actionInProgressText={Swapping} sx={sx} + fetchingData={loading} + errorParams={{ + loading: false, + disabled: blocked, + content: Swap, + handleClick: action, + }} {...props} /> ); diff --git a/src/components/transactions/Swap/SwapModalContent.tsx b/src/components/transactions/Swap/SwapModalContent.tsx index fe628778ed..b7516d4ca9 100644 --- a/src/components/transactions/Swap/SwapModalContent.tsx +++ b/src/components/transactions/Swap/SwapModalContent.tsx @@ -1,34 +1,29 @@ +import { SwitchVerticalIcon } from '@heroicons/react/outline'; import { Trans } from '@lingui/macro'; -import { Box, Typography } from '@mui/material'; +import { Box, SvgIcon, Typography } from '@mui/material'; import BigNumber from 'bignumber.js'; import React, { useRef, useState } from 'react'; -import { FormattedNumber } from 'src/components/primitives/FormattedNumber'; -import { Row } from 'src/components/primitives/Row'; -import StyledToggleButton from 'src/components/StyledToggleButton'; -import StyledToggleButtonGroup from 'src/components/StyledToggleButtonGroup'; +import { PriceImpactTooltip } from 'src/components/infoTooltips/PriceImpactTooltip'; import { Asset, AssetInput } from 'src/components/transactions/AssetInput'; import { GasEstimationError } from 'src/components/transactions/FlowCommons/GasEstimationError'; -import { - DetailsHFLine, - DetailsIncentivesLine, - DetailsNumberLine, - TxModalDetails, -} from 'src/components/transactions/FlowCommons/TxModalDetails'; +import { TxModalDetails } from 'src/components/transactions/FlowCommons/TxModalDetails'; +import { useCollateralSwap } from 'src/hooks/paraswap/useCollateralSwap'; import { useModalContext } from 'src/hooks/useModal'; import { useProtocolDataContext } from 'src/hooks/useProtocolDataContext'; -import { useSwap } from 'src/hooks/useSwap'; import { useWeb3Context } from 'src/libs/hooks/useWeb3Context'; +import { ListSlippageButton } from 'src/modules/dashboard/lists/SlippageList'; import { remainingCap } from 'src/utils/getMaxAmountAvailableToSupply'; import { calculateHFAfterSwap } from 'src/utils/hfUtils'; import { - ComputedReserveData, + ComputedUserReserveData, useAppDataContext, } from '../../../hooks/app-data-provider/useAppDataProvider'; import { ModalWrapperProps } from '../FlowCommons/ModalWrapper'; import { TxSuccessView } from '../FlowCommons/Success'; import { ErrorType, flashLoanNotAvailable, useFlashloan } from '../utils'; import { SwapActions } from './SwapActions'; +import { SwapModalDetails } from './SwapModalDetails'; export type SupplyProps = { underlyingAsset: string; @@ -58,9 +53,9 @@ export const SwapModalContent = ({ const [targetReserve, setTargetReserve] = useState(swapTargets[0]); const [maxSlippage, setMaxSlippage] = useState('0.1'); - const swapTarget = reserves.find( + const swapTarget = user.userReservesData.find( (r) => r.underlyingAsset === targetReserve.address - ) as ComputedReserveData; + ) as ComputedUserReserveData; // a user can never swap more then 100% of available as the txn would fail on withdraw step const maxAmountToSwap = BigNumber.min( @@ -68,26 +63,30 @@ export const SwapModalContent = ({ new BigNumber(poolReserve.availableLiquidity).multipliedBy(0.99) ).toString(10); - const remainingCapBn = remainingCap(swapTarget); + const remainingCapBn = remainingCap(swapTarget.reserve); const isMaxSelected = _amount === '-1'; const amount = isMaxSelected ? maxAmountToSwap : _amount; - const { priceRoute, inputAmountUSD, inputAmount, outputAmount, outputAmountUSD, error } = useSwap( - { - chainId: currentNetworkConfig.underlyingChainId || currentChainId, - userId: currentAccount, - variant: 'exactIn', - swapIn: { ...poolReserve, amount: amountRef.current }, - swapOut: { ...swapTarget, amount: '0' }, - max: isMaxSelected, - skip: supplyTxState.loading, - } - ); + const { + inputAmountUSD, + inputAmount, + outputAmount, + outputAmountUSD, + error, + loading: routeLoading, + buildTxFn, + } = useCollateralSwap({ + chainId: currentNetworkConfig.underlyingChainId || currentChainId, + userAddress: currentAccount, + swapIn: { ...poolReserve, amount: amountRef.current }, + swapOut: { ...swapTarget.reserve, amount: '0' }, + max: isMaxSelected, + skip: supplyTxState.loading || false, + maxSlippage: Number(maxSlippage), + }); - const minimumReceived = new BigNumber(outputAmount || '0') - .multipliedBy(new BigNumber(100).minus(maxSlippage).dividedBy(100)) - .toString(10); + const loadingSkeleton = routeLoading && outputAmountUSD === '0'; const handleChange = (value: string) => { const maxSelected = value === '-1'; @@ -100,8 +99,8 @@ export const SwapModalContent = ({ fromAssetData: poolReserve, fromAssetUserData: userReserve, user, - toAmountAfterSlippage: minimumReceived, - toAssetData: swapTarget, + toAmountAfterSlippage: outputAmount, + toAssetData: swapTarget.reserve, }); // if the hf would drop below 1 from the hf effect a flashloan should be used to mitigate liquidation @@ -165,9 +164,7 @@ export const SwapModalContent = ({ // calculate impact based on $ difference const priceImpact = outputAmountUSD && outputAmountUSD !== '0' - ? new BigNumber(1) - .minus(new BigNumber(inputAmountUSD).dividedBy(outputAmountUSD)) - .toString(10) + ? new BigNumber(1).minus(new BigNumber(inputAmountUSD).dividedBy(outputAmountUSD)).toFixed(2) : '0'; return ( @@ -189,87 +186,55 @@ export const SwapModalContent = ({ }, ]} maxValue={maxAmountToSwap} - isMaxSelected={isMaxSelected} + inputTitle={Supplied asset amount} balanceText={Supply balance} + isMaxSelected={isMaxSelected} /> + + + + + + + Swap to} balanceText={Supply balance} disableInput + loading={loadingSkeleton} /> - - Price impact} captionVariant="subheader1"> - - - Minimum received} captionVariant="subheader1" sx={{ mt: 4 }}> - - - - Max slippage rate - - setMaxSlippage(value)} - exclusive - > - - 0.1% - - - 0.5% - - - 1% - - - - {blockingError !== undefined && ( + {error && !loadingSkeleton && ( - {handleBlocked()} + {error} )} - {error && ( + {!error && blockingError !== undefined && ( - {error} + {handleBlocked()} )} - - Supply apy} - value={poolReserve.supplyAPY} - futureValue={swapTarget.supplyAPY} - percent - /> - + } + > + - {showHealthFactor && ( - - )} {txError && } @@ -278,13 +243,14 @@ export const SwapModalContent = ({ isMaxSelected={isMaxSelected} poolReserve={poolReserve} amountToSwap={inputAmount} - amountToReceive={minimumReceived} + amountToReceive={outputAmount} isWrongNetwork={isWrongNetwork} - targetReserve={swapTarget} + targetReserve={swapTarget.reserve} symbol={poolReserve.symbol} - blocked={blockingError !== undefined} - priceRoute={priceRoute} + blocked={blockingError !== undefined || error !== ''} useFlashLoan={shouldUseFlashloan} + loading={routeLoading} + buildTxFn={buildTxFn} /> ); diff --git a/src/components/transactions/Swap/SwapModalDetails.tsx b/src/components/transactions/Swap/SwapModalDetails.tsx new file mode 100644 index 0000000000..f55fbb9363 --- /dev/null +++ b/src/components/transactions/Swap/SwapModalDetails.tsx @@ -0,0 +1,211 @@ +import { valueToBigNumber } from '@aave/math-utils'; +import { ArrowNarrowRightIcon } from '@heroicons/react/outline'; +import { Trans } from '@lingui/macro'; +import { Box, Skeleton, SvgIcon, Typography, useTheme } from '@mui/material'; +import React from 'react'; +import { FormattedNumber } from 'src/components/primitives/FormattedNumber'; +import { Row } from 'src/components/primitives/Row'; +import { TokenIcon } from 'src/components/primitives/TokenIcon'; +import { + DetailsHFLine, + DetailsIncentivesLine, + DetailsNumberLine, +} from 'src/components/transactions/FlowCommons/TxModalDetails'; + +import { ComputedUserReserveData } from '../../../hooks/app-data-provider/useAppDataProvider'; + +export type SupplyModalDetailsProps = { + showHealthFactor: boolean; + healthFactor: string; + healthFactorAfterSwap: string; + swapSource: ComputedUserReserveData; + swapTarget: ComputedUserReserveData; + toAmount: string; + fromAmount: string; + loading: boolean; +}; + +export const SwapModalDetails = ({ + showHealthFactor, + healthFactor, + healthFactorAfterSwap, + swapSource, + swapTarget, + toAmount, + fromAmount, + loading, +}: SupplyModalDetailsProps) => { + const { palette } = useTheme(); + + const parseUsageString = (usage: boolean) => { + if (usage) { + return ( + + Yes + + ); + } else { + return ( + + No + + ); + } + }; + + const sourceAmountAfterSwap = valueToBigNumber(swapSource.underlyingBalance).minus( + valueToBigNumber(fromAmount) + ); + + const targetAmountAfterSwap = valueToBigNumber(swapTarget.underlyingBalance).plus( + valueToBigNumber(toAmount) + ); + + const skeleton: JSX.Element = ( + <> + + + + ); + + return ( + <> + {healthFactorAfterSwap && ( + + )} + Supply apy} + value={swapSource.reserve.supplyAPY} + futureValue={swapTarget.reserve.supplyAPY} + percent + loading={loading} + /> + Can be collateral} captionVariant="description" mb={4}> + + {loading ? ( + + ) : ( + <> + {parseUsageString(swapSource.reserve.usageAsCollateralEnabled)} + + + + + + {parseUsageString(swapTarget.reserve.usageAsCollateralEnabled)} + + )} + + + + Liquidation threshold} + value={swapSource.reserve.formattedReserveLiquidationThreshold} + futureValue={swapTarget.reserve.formattedReserveLiquidationThreshold} + percent + visibleDecimals={0} + loading={loading} + /> + + Supply balance after swap} + captionVariant="description" + mb={4} + align="flex-start" + > + + + {loading ? ( + skeleton + ) : ( + <> + + + + + + + )} + + + + {loading ? ( + skeleton + ) : ( + <> + + + + + + + )} + + + + + ); +}; diff --git a/src/components/transactions/TxActionsWrapper.tsx b/src/components/transactions/TxActionsWrapper.tsx index 26c3f0b846..fd117d2869 100644 --- a/src/components/transactions/TxActionsWrapper.tsx +++ b/src/components/transactions/TxActionsWrapper.tsx @@ -1,12 +1,13 @@ +import { CheckIcon } from '@heroicons/react/solid'; import { Trans } from '@lingui/macro'; -import { Box, BoxProps, Button, CircularProgress, Typography } from '@mui/material'; +import { Box, BoxProps, Button, CircularProgress, SvgIcon, Typography } from '@mui/material'; import isEmpty from 'lodash/isEmpty'; import { ReactNode } from 'react'; import { TxStateType, useModalContext } from 'src/hooks/useModal'; import { useWeb3Context } from 'src/libs/hooks/useWeb3Context'; import { TxAction } from 'src/ui-config/errorMapping'; -import { LeftHelperText } from './FlowCommons/LeftHelperText'; +import { ApprovalTooltip } from '../infoTooltips/ApprovalTooltip'; import { RightHelperText } from './FlowCommons/RightHelperText'; interface TxActionsWrapperProps extends BoxProps { @@ -23,6 +24,14 @@ interface TxActionsWrapperProps extends BoxProps { requiresApproval: boolean; symbol?: string; blocked?: boolean; + fetchingData?: boolean; + errorParams?: { + loading: boolean; + disabled: boolean; + content: ReactNode; + handleClick: () => Promise; + }; + tryPermit?: boolean; } export const TxActionsWrapper = ({ @@ -40,22 +49,30 @@ export const TxActionsWrapper = ({ sx, symbol, blocked, + fetchingData = false, + errorParams, + tryPermit, ...rest }: TxActionsWrapperProps) => { - const { txError, retryWithApproval } = useModalContext(); + const { txError } = useModalContext(); const { watchModeOnlyAddress } = useWeb3Context(); const hasApprovalError = - requiresApproval && txError && txError.txAction === TxAction.APPROVAL && txError.actionBlocked; + requiresApproval && txError?.txAction === TxAction.APPROVAL && txError?.actionBlocked; const isAmountMissing = requiresAmount && requiresAmount && Number(amount) === 0; function getMainParams() { if (blocked) return { disabled: true, content: actionText }; - if (txError && txError.txAction === TxAction.GAS_ESTIMATION && txError.actionBlocked) - return { loading: false, disabled: true, content: actionText }; - if (txError && txError.txAction === TxAction.MAIN_ACTION && txError.actionBlocked) + if ( + (txError?.txAction === TxAction.GAS_ESTIMATION || + txError?.txAction === TxAction.MAIN_ACTION) && + txError?.actionBlocked + ) { + if (errorParams) return errorParams; return { loading: false, disabled: true, content: actionText }; + } if (isWrongNetwork) return { disabled: true, content: Wrong Network }; + if (fetchingData) return { disabled: true, content: Fetching data... }; if (isAmountMissing) return { disabled: true, content: Enter an amount }; if (preparingTransactions || isEmpty(mainTxState)) return { disabled: true, loading: true }; // if (hasApprovalError && handleRetry) @@ -78,21 +95,40 @@ export const TxActionsWrapper = ({ return null; if (approvalTxState?.loading) return { loading: true, disabled: true, content: Approving {symbol}... }; - if (approvalTxState?.success) return { disabled: true, content: Approved }; - if (retryWithApproval) - return { content: Retry with approval, handleClick: handleApproval }; - return { content: Approve to continue, handleClick: handleApproval }; + if (approvalTxState?.success) + return { + disabled: true, + content: ( + <> + Approve Confirmed + + + + + ), + }; + + return { + content: ( + Approve {symbol} to continue} + /> + ), + handleClick: handleApproval, + }; } const { content, disabled, loading, handleClick } = getMainParams(); const approvalParams = getApprovalParams(); - return ( {requiresApproval && !watchModeOnlyAddress && ( - - - + + )} @@ -100,7 +136,7 @@ export const TxActionsWrapper = ({ diff --git a/src/modules/dashboard/lists/BorrowedPositionsList/BorrowedPositionsListMobileItem.tsx b/src/modules/dashboard/lists/BorrowedPositionsList/BorrowedPositionsListMobileItem.tsx index b6c24b8659..bcd7843098 100644 --- a/src/modules/dashboard/lists/BorrowedPositionsList/BorrowedPositionsListMobileItem.tsx +++ b/src/modules/dashboard/lists/BorrowedPositionsList/BorrowedPositionsListMobileItem.tsx @@ -87,7 +87,7 @@ export const BorrowedPositionsListMobileItem = ({