import BigNumber from "bignumber.js"; import abis from "~/constant/abis"; import addresses from "~/constant/addresses"; import { defineStrategy, defineStrategyComponent, StrategyComponentType, StrategyProtocol } from "../../helpers"; export default defineStrategy({ protocol: StrategyProtocol.LIQUITY, name: "Payback & Withdraw", description: "Payback debt & withdraw collateral in a single txn.", author: "Instadapp Team", submitText: "Payback & Withdraw", details: `

This strategy executes:

`, components: [ defineStrategyComponent({ type: StrategyComponentType.INPUT_WITH_TOKEN, name: "Debt", placeholder: ({ component: input }) => input.token ? `${input.token.symbol} to Payback` : "", validate: ({ component: input, toBN, position, positionExtra }) => { if (!input.token) { return "Debt token is required"; } if (!input.value) { return "Debt amount is required"; } const troveOverallDetails = positionExtra["troveOverallDetails"]; const borrowFeeAmount = toBN(input.value) .times(troveOverallDetails.borrowFee) .toFixed(); const debtInputAmountWithFee = toBN(input.value) .plus(borrowFeeAmount) .toFixed(); const changedDebt = toBN(position.debt).plus(debtInputAmountWithFee); const totalDebt = toBN(changedDebt).plus( troveOverallDetails.liquidationReserve ); if (totalDebt.isZero()) return `Minimum total debt requirement is ${troveOverallDetails.minDebt} LUSD`; if (totalDebt.lt(troveOverallDetails.minDebt) && totalDebt.gt("0")) { return `Minimum total debt requirement is ${troveOverallDetails.minDebt} LUSD`; } }, defaults: ({ getTokenByKey }) => ({ token: getTokenByKey?.("lusd") }) }), defineStrategyComponent({ type: StrategyComponentType.INPUT_WITH_TOKEN, name: "Collateral", placeholder: ({ component: input }) => input.token ? `${input.token.symbol} to Withdraw` : "", validate: ({ component: input, dsaBalances, toBN }) => { if (!input.token) { return "Collateral token is required"; } if (!input.value) { return "Collateral amount is required"; } const collateralBalance = toBN( dsaBalances[input.token.address]?.balance ); if (toBN(collateralBalance).lt(input.value)) { const collateralBalanceFormatted = collateralBalance.toFixed(2); return `Your amount exceeds your maximum limit of ${collateralBalanceFormatted} ${input.token.symbol}`; } }, defaults: ({ getTokenByKey }) => ({ token: getTokenByKey?.("eth") }) }), defineStrategyComponent({ type: StrategyComponentType.HEADING, name: "Projected Debt Position" }), defineStrategyComponent({ type: StrategyComponentType.STATUS, name: "Status", update: ({ position, positionExtra, component, components, toBN }) => { if (!position && !positionExtra) { return; } const troveOverallDetails = positionExtra["troveOverallDetails"]; component.liquidation = troveOverallDetails.liquidation; if ( toBN(components[1].value).isZero() || !components[1].value || toBN(components[0].value).isZero() || !components[0].value ) { component.status = position.ratio; } else { const changedDebt = BigNumber.max(toBN(position.debt).minus(components[0].value), '0') const changedCollateral = toBN(position.collateral).minus(components[1].value); component.status = changedDebt .div(toBN(changedCollateral).times(position.price)) .toFixed(); } } }), defineStrategyComponent({ type: StrategyComponentType.VALUE, name: "LIQUIDATION PRICE (IN ETH)", value: "-", update: ({ position, component, components, toBN, formatting, positionExtra }) => { if (!position && !positionExtra) { return; } const troveOverallDetails = positionExtra["troveOverallDetails"]; let liquidationPrice = "0"; if ( toBN(components[1].value).isZero() || !components[1].value || toBN(components[0].value).isZero() || !components[0].value ) { liquidationPrice = BigNumber.max( toBN(position.debt) .div(position.collateral) .div(troveOverallDetails.liquidation), "0" ).toFixed(); } else { const changedDebt = BigNumber.max(toBN(position.debt).minus(components[0].value), '0') const changedCollateral = toBN(position.collateral).minus(components[1].value); liquidationPrice = BigNumber.max( toBN(changedDebt) .div(changedCollateral) .div(troveOverallDetails.liquidation), "0" ).toFixed(); } component.value = `${formatting.formatUsdMax( isNaN(parseInt(liquidationPrice)) ? "0" : liquidationPrice, position.price )} / ${formatting.formatUsd(position.price)}`; } }) ], validate: async ({ position, components: inputs, toBN }) => { if (toBN(inputs[0].value).isZero() && toBN(inputs[1].value).isZero()) { return; } const troveOpened = !toBN(position.collateral).isZero() && !toBN(position.debt).isZero(); if (!troveOpened) { return "You should open new trove first"; } }, spells: async ({ components: inputs, position, positionExtra, getTokenByKey, convertTokenAmountToWei, web3, toBN }) => { const troveOverallDetails = positionExtra["troveOverallDetails"]; const collateralToken = getTokenByKey("eth"); const collateralInWei = convertTokenAmountToWei( position.collateral, collateralToken.decimals ); const withdrawAmountInWei = convertTokenAmountToWei( inputs[0].value, inputs[0].token.decimals ); const totalDepositAmountInWei = toBN(withdrawAmountInWei) .plus(collateralInWei) .toFixed(); const borrowFeeAmount = toBN(inputs[1].value) .times(troveOverallDetails.borrowFee) .toFixed(); const debtInputAmountWithFee = toBN(inputs[1].value) .plus(borrowFeeAmount) .toFixed(); const changedDebt = toBN(position.debt) .plus(debtInputAmountWithFee) .toFixed(); const paybackAmountInWei = convertTokenAmountToWei( inputs[1].value, inputs[1].value.decimals ); const totalBorrowAmountInWei = convertTokenAmountToWei( changedDebt, inputs[1].value.decimals ); const depositAmountInWei = "0"; const borrowAmountInWei = "0"; const liquityInstance = new web3.eth.Contract( abis.resolver.liquity as any, addresses.mainnet.resolver.liquity ); const { upperHint, lowerHint } = await liquityInstance.methods .getTrovePositionHints( totalDepositAmountInWei.toString(), totalBorrowAmountInWei.toString(), 0, 0 ) .call(); const getIds = [0, 0, 0, 0]; const setIds = [0, 0, 0, 0]; return [ { connector: "LIQUITY-A", method: "adjust", args: [ toBN(troveOverallDetails.borrowFee) .times("100") .times("1e18") .toFixed(), depositAmountInWei, withdrawAmountInWei, borrowAmountInWei, paybackAmountInWei, upperHint, lowerHint, getIds, setIds ] } ]; } });