mirror of
https://github.com/Instadapp/assembly.git
synced 2024-07-29 22:37:06 +00:00
basic compound strategies
This commit is contained in:
parent
e052cbe70d
commit
52ebdbfb3f
|
|
@ -14,6 +14,7 @@ import addresses from "~/constant/addresses";
|
|||
import ctokens from "~/constant/ctokens";
|
||||
import tokenIdMapping from "~/constant/tokenIdMapping";
|
||||
import { useSorting } from "~/composables/useSorting";
|
||||
import useEventBus from "../useEventBus";
|
||||
|
||||
const {
|
||||
times,
|
||||
|
|
@ -62,6 +63,7 @@ export function useCompoundPosition(
|
|||
) {
|
||||
overridePosition = overridePosition || (pos => pos);
|
||||
|
||||
const { onEvent } = useEventBus()
|
||||
const { web3, networkName } = useWeb3();
|
||||
const { activeAccount } = useDSA();
|
||||
const { getTokenByKey } = useToken();
|
||||
|
|
@ -100,6 +102,8 @@ export function useCompoundPosition(
|
|||
position.value = await fetchPosition();
|
||||
};
|
||||
|
||||
onEvent("protocol::compound::refresh", refreshPosition);
|
||||
|
||||
watch(
|
||||
web3,
|
||||
async val => {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ BigNumber.config({ POW_PRECISION: 200 });
|
|||
import abis from "~/constant/abis";
|
||||
import addresses from "~/constant/addresses";
|
||||
import { useDSA } from "../useDSA";
|
||||
import useEventBus from "../useEventBus";
|
||||
|
||||
export const trove = ref<any>({
|
||||
collateral: "0",
|
||||
|
|
@ -49,6 +50,7 @@ export function useLiquityPosition(
|
|||
collateralAmountRef: Ref = null,
|
||||
debtAmountRef: Ref = null
|
||||
) {
|
||||
const { onEvent } = useEventBus()
|
||||
const { web3 } = useWeb3();
|
||||
const { activeAccount } = useDSA();
|
||||
|
||||
|
|
@ -178,6 +180,9 @@ export function useLiquityPosition(
|
|||
}
|
||||
}
|
||||
|
||||
onEvent("protocol::liquity::refresh", fetchPosition);
|
||||
|
||||
|
||||
watch(
|
||||
web3,
|
||||
async val => {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { useDSA } from "~/composables/useDSA";
|
|||
import { useToken } from "~/composables/useToken";
|
||||
import { useWeb3 } from "~/composables/useWeb3";
|
||||
import { AbiItem } from "web3-utils";
|
||||
import useEventBus from "../useEventBus";
|
||||
|
||||
const defaultVault = {
|
||||
id: null,
|
||||
|
|
@ -54,6 +55,7 @@ export function useMakerdaoPosition(
|
|||
collateralAmountRef: Ref = null,
|
||||
debtAmountRef: Ref = null
|
||||
) {
|
||||
const { onEvent } = useEventBus()
|
||||
const { web3, chainId, networkName } = useWeb3();
|
||||
const { activeAccount } = useDSA();
|
||||
const { isZero, ensureValue, times, div, max, gt } = useBigNumber();
|
||||
|
|
@ -132,6 +134,8 @@ export function useMakerdaoPosition(
|
|||
}
|
||||
};
|
||||
|
||||
onEvent("protocol::makerdao::refresh", fetchPosition);
|
||||
|
||||
watch(
|
||||
web3,
|
||||
async val => {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { useSidebar } from "./useSidebar";
|
|||
import { useToken } from "./useToken";
|
||||
import { useWeb3 } from "./useWeb3";
|
||||
import { useBigNumber } from "./useBigNumber";
|
||||
import tokenIdMapping from "~/constant/tokenIdMapping";
|
||||
|
||||
export function useStrategy(defineStrategy: DefineStrategy) {
|
||||
const { web3, networkName, account } = useWeb3();
|
||||
|
|
@ -84,7 +85,8 @@ export function useStrategy(defineStrategy: DefineStrategy) {
|
|||
convertTokenAmountToWei: valInt,
|
||||
getTokenByKey,
|
||||
toBN,
|
||||
position
|
||||
position,
|
||||
tokenIdMapping
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,16 @@ import Web3 from "web3";
|
|||
import slugify from "slugify";
|
||||
import { Strategy } from "./strategy";
|
||||
import BigNumber from "bignumber.js";
|
||||
import tokenIdMapping from "~/constant/tokenIdMapping";
|
||||
|
||||
export interface IStrategyContext {
|
||||
dsa: DSA;
|
||||
web3: Web3;
|
||||
inputs: IStrategyInput<StrategyInputType>[];
|
||||
|
||||
|
||||
|
||||
// TODO: add types in useStrategy.ts
|
||||
dsaBalances?: { [address: string]: IStrategyToken };
|
||||
userBalances?: { [address: string]: IStrategyToken };
|
||||
tokens?: { [address: string]: IStrategyToken };
|
||||
|
|
@ -15,6 +21,8 @@ export interface IStrategyContext {
|
|||
position?: any;
|
||||
variables?: { [key: string]: any };
|
||||
toBN?: (value: any) => BigNumber;
|
||||
tokenIdMapping?: typeof tokenIdMapping;
|
||||
|
||||
}
|
||||
|
||||
export interface IStrategyToken {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import AaveV2 from "./protocols/aave-v2"
|
||||
import Compound from "./protocols/compound"
|
||||
|
||||
export const protocolStrategies = {
|
||||
aaveV2 : AaveV2,
|
||||
compound : Compound,
|
||||
}
|
||||
|
||||
export * from "./helpers"
|
||||
182
core/strategies/protocols/compound/deposit-and-borrow.ts
Normal file
182
core/strategies/protocols/compound/deposit-and-borrow.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
import BigNumber from "bignumber.js";
|
||||
import {
|
||||
defineStrategy,
|
||||
defineInput,
|
||||
StrategyInputType,
|
||||
StrategyProtocol
|
||||
} from "../../helpers";
|
||||
|
||||
export default defineStrategy({
|
||||
protocol: StrategyProtocol.COMPOUND,
|
||||
name: "Deposit & Borrow",
|
||||
description: "Deposit collateral & borrow asset in a single txn.",
|
||||
|
||||
details: `<p class="text-center">This strategy executes:</p>
|
||||
<ul>
|
||||
<li>Deposit collateral</li>
|
||||
<li>Borrow Debt</li>
|
||||
</ul>`,
|
||||
|
||||
submitText: "Deposit & Borrow",
|
||||
|
||||
author: "Instadapp Team",
|
||||
|
||||
variables: {
|
||||
collateralTokenKey: "eth",
|
||||
debtTokenKey: "dai"
|
||||
},
|
||||
|
||||
inputs: [
|
||||
defineInput({
|
||||
type: StrategyInputType.INPUT_WITH_TOKEN,
|
||||
name: "Collateral",
|
||||
placeholder: ({ input }) =>
|
||||
input.token ? `${input.token.symbol} to Deposit` : "",
|
||||
validate: ({ 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, variables }) => ({
|
||||
token: getTokenByKey?.(variables.collateralTokenKey)
|
||||
})
|
||||
}),
|
||||
|
||||
defineInput({
|
||||
type: StrategyInputType.INPUT_WITH_TOKEN,
|
||||
name: "Debt",
|
||||
placeholder: ({ input }) =>
|
||||
input.token ? `${input.token.symbol} to Borrow` : "",
|
||||
validate: ({ input }) => {
|
||||
if (!input.token) {
|
||||
return "Debt token is required";
|
||||
}
|
||||
|
||||
if (!input.value) {
|
||||
return "Debt amount is required";
|
||||
}
|
||||
},
|
||||
defaults: ({ getTokenByKey, variables }) => ({
|
||||
token: getTokenByKey?.(variables.debtTokenKey)
|
||||
})
|
||||
})
|
||||
],
|
||||
|
||||
validate: async ({ position, inputs, toBN, tokenIdMapping }) => {
|
||||
if (toBN(inputs[0].value).isZero() && toBN(inputs[1].value).isZero()) {
|
||||
return;
|
||||
}
|
||||
const { tokenToId } = tokenIdMapping;
|
||||
|
||||
const newPositionData = position.data.map(position => {
|
||||
const changedPosition = { ...position };
|
||||
if (tokenToId.compound[inputs[1].token.key] === position.cTokenId) {
|
||||
changedPosition.borrow = BigNumber.max(
|
||||
toBN(position.borrow).plus(inputs[1].value),
|
||||
"0"
|
||||
).toFixed();
|
||||
}
|
||||
|
||||
if (tokenToId.compound[inputs[0].token.key] === position.cTokenId) {
|
||||
changedPosition.supply = BigNumber.max(
|
||||
toBN(position.supply).plus(inputs[0].value),
|
||||
"0"
|
||||
).toFixed();
|
||||
}
|
||||
|
||||
return changedPosition;
|
||||
});
|
||||
|
||||
const stats = newPositionData.reduce(
|
||||
(stats, { key, supply, borrow, priceInEth, factor }) => {
|
||||
if (key === "eth") {
|
||||
stats.ethSupplied = supply;
|
||||
}
|
||||
|
||||
|
||||
stats.totalSupplyInEth = toBN(supply)
|
||||
.times(priceInEth)
|
||||
.plus(stats.totalSupplyInEth)
|
||||
.toFixed();
|
||||
stats.totalBorrowInEth = toBN(borrow)
|
||||
.times(priceInEth)
|
||||
.plus(stats.totalBorrowInEth)
|
||||
.toFixed();
|
||||
|
||||
stats.totalMaxBorrowLimitInEth = toBN(priceInEth)
|
||||
.times(factor)
|
||||
.times(supply)
|
||||
.plus(stats.totalMaxBorrowLimitInEth)
|
||||
.toFixed();
|
||||
|
||||
return stats;
|
||||
},
|
||||
{
|
||||
totalSupplyInEth: "0",
|
||||
totalBorrowInEth: "0",
|
||||
totalMaxBorrowLimitInEth: "0",
|
||||
ethSupplied: "0"
|
||||
}
|
||||
);
|
||||
|
||||
let liquidation = "0";
|
||||
if (!toBN(stats.totalSupplyInEth).isZero()) {
|
||||
liquidation = BigNumber.max(
|
||||
toBN(stats.totalMaxBorrowLimitInEth).div(stats.totalSupplyInEth),
|
||||
"0"
|
||||
).toFixed();
|
||||
}
|
||||
|
||||
const status = BigNumber.max(
|
||||
toBN(stats.totalBorrowInEth).div(stats.totalSupplyInEth),
|
||||
"0"
|
||||
);
|
||||
|
||||
if (status.gt(toBN(liquidation).minus("0.0001"))) {
|
||||
return "Position will liquidate.";
|
||||
}
|
||||
},
|
||||
|
||||
spells: async ({ inputs, convertTokenAmountToWei, tokenIdMapping }) => {
|
||||
const { tokenToId } = tokenIdMapping;
|
||||
|
||||
const collateralTokenId = tokenToId.compound[inputs[0].token.key];
|
||||
const debtTokenId = tokenToId.compound[inputs[1].token.key];
|
||||
|
||||
return [
|
||||
{
|
||||
connector: "compound",
|
||||
method: "deposit",
|
||||
args: [
|
||||
collateralTokenId,
|
||||
convertTokenAmountToWei(inputs[0].value, inputs[0].token.decimals),
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
connector: "compound",
|
||||
method: "borrow",
|
||||
args: [
|
||||
debtTokenId,
|
||||
convertTokenAmountToWei(inputs[1].value, inputs[1].token.decimals),
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
8
core/strategies/protocols/compound/index.ts
Normal file
8
core/strategies/protocols/compound/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import depositAndBorrow from "./deposit-and-borrow"
|
||||
import paybackAndWithdraw from "./payback-and-withdraw"
|
||||
|
||||
export default [
|
||||
depositAndBorrow,
|
||||
paybackAndWithdraw,
|
||||
]
|
||||
|
||||
186
core/strategies/protocols/compound/payback-and-withdraw.ts
Normal file
186
core/strategies/protocols/compound/payback-and-withdraw.ts
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import BigNumber from "bignumber.js";
|
||||
import {
|
||||
defineStrategy,
|
||||
defineInput,
|
||||
StrategyInputType,
|
||||
StrategyProtocol
|
||||
} from "../../helpers";
|
||||
|
||||
export default defineStrategy({
|
||||
protocol: StrategyProtocol.COMPOUND,
|
||||
name: "Payback & Withdraw",
|
||||
description: "Payback debt & withdraw collateral in a single txn.",
|
||||
author: "Instadapp Team",
|
||||
|
||||
submitText: "Payback & Withdraw",
|
||||
|
||||
details: `<p class="text-center">This strategy executes:</p>
|
||||
<ul>
|
||||
<li>Payback debt</li>
|
||||
<li>Withdraw collateral</li>
|
||||
</ul>`,
|
||||
|
||||
inputs: [
|
||||
defineInput({
|
||||
type: StrategyInputType.INPUT_WITH_TOKEN,
|
||||
name: "Debt",
|
||||
placeholder: ({ input }) =>
|
||||
input.token ? `${input.token.symbol} to Payback` : "",
|
||||
validate: ({ input, toBN, dsaBalances }) => {
|
||||
if (!input.token) {
|
||||
return "Debt token is required";
|
||||
}
|
||||
|
||||
const balance = toBN(dsaBalances[input.token.address]?.balance);
|
||||
|
||||
if (toBN(balance).lt(input.value)) {
|
||||
return "You don't have enough balance to payback.";
|
||||
}
|
||||
},
|
||||
defaults: ({ getTokenByKey }) => ({
|
||||
token: getTokenByKey?.("dai")
|
||||
})
|
||||
}),
|
||||
defineInput({
|
||||
type: StrategyInputType.INPUT_WITH_TOKEN,
|
||||
name: "Collateral",
|
||||
placeholder: ({ input }) =>
|
||||
input.token ? `${input.token.symbol} to Withdraw` : "",
|
||||
validate: ({ input, position, toBN, tokenIdMapping }) => {
|
||||
if (!input.token) {
|
||||
return "Collateral token is required";
|
||||
}
|
||||
|
||||
if (!input.value) {
|
||||
return "Collateral amount is required";
|
||||
}
|
||||
|
||||
const { tokenToId } = tokenIdMapping;
|
||||
|
||||
if (position) {
|
||||
const collateralBalance = toBN(
|
||||
position.data.find(
|
||||
pos => pos.cTokenId === tokenToId.compound[input.token.key]
|
||||
)?.supply || "0"
|
||||
);
|
||||
|
||||
if (collateralBalance.lt(input.value)) {
|
||||
const collateralBalanceFormatted = collateralBalance.toFixed(
|
||||
2,
|
||||
BigNumber.ROUND_FLOOR
|
||||
);
|
||||
return `Your amount exceeds your maximum limit of ${collateralBalanceFormatted} ${input.token.symbol}`;
|
||||
}
|
||||
}
|
||||
},
|
||||
defaults: ({ getTokenByKey }) => ({
|
||||
token: getTokenByKey?.("eth")
|
||||
})
|
||||
})
|
||||
],
|
||||
|
||||
validate: async ({ position, inputs, toBN, tokenIdMapping }) => {
|
||||
if (toBN(inputs[0].value).isZero() && toBN(inputs[1].value).isZero()) {
|
||||
return;
|
||||
}
|
||||
const { tokenToId } = tokenIdMapping;
|
||||
|
||||
const newPositionData = position.data.map(position => {
|
||||
const changedPosition = { ...position };
|
||||
if (tokenToId.compound[inputs[0].token.key] === position.cTokenId) {
|
||||
changedPosition.borrow = BigNumber.max(
|
||||
toBN(position.borrow).minus(inputs[0].value),
|
||||
"0"
|
||||
).toFixed();
|
||||
}
|
||||
|
||||
if (tokenToId.compound[inputs[1].token.key] === position.cTokenId) {
|
||||
changedPosition.supply = BigNumber.max(
|
||||
toBN(position.supply).minus(inputs[1].value),
|
||||
"0"
|
||||
).toFixed();
|
||||
}
|
||||
|
||||
return changedPosition;
|
||||
});
|
||||
|
||||
const stats = newPositionData.reduce(
|
||||
(stats, { key, supply, borrow, priceInEth, factor }) => {
|
||||
if (key === "eth") {
|
||||
stats.ethSupplied = supply;
|
||||
}
|
||||
|
||||
stats.totalSupplyInEth = toBN(supply)
|
||||
.times(priceInEth)
|
||||
.plus(stats.totalSupplyInEth)
|
||||
.toFixed();
|
||||
stats.totalBorrowInEth = toBN(borrow)
|
||||
.times(priceInEth)
|
||||
.plus(stats.totalBorrowInEth)
|
||||
.toFixed();
|
||||
|
||||
stats.totalMaxBorrowLimitInEth = toBN(priceInEth)
|
||||
.times(factor)
|
||||
.times(supply)
|
||||
.plus(stats.totalMaxBorrowLimitInEth)
|
||||
.toFixed();
|
||||
|
||||
return stats;
|
||||
},
|
||||
{
|
||||
totalSupplyInEth: "0",
|
||||
totalBorrowInEth: "0",
|
||||
totalMaxBorrowLimitInEth: "0",
|
||||
ethSupplied: '0',
|
||||
}
|
||||
);
|
||||
|
||||
let liquidation = "0";
|
||||
|
||||
if (!toBN(stats.totalSupplyInEth).isZero()) {
|
||||
liquidation = BigNumber.max(
|
||||
toBN(stats.totalMaxBorrowLimitInEth).div(stats.totalSupplyInEth),
|
||||
"0"
|
||||
).toFixed();
|
||||
}
|
||||
|
||||
const status = BigNumber.max(
|
||||
toBN(stats.totalBorrowInEth).div(stats.totalSupplyInEth),
|
||||
"0"
|
||||
);
|
||||
|
||||
if (status.gt(toBN(liquidation).minus("0.0001"))) {
|
||||
return "Position will liquidate.";
|
||||
}
|
||||
},
|
||||
|
||||
spells: async ({ inputs, convertTokenAmountToWei, tokenIdMapping }) => {
|
||||
const { tokenToId } = tokenIdMapping;
|
||||
|
||||
const debtTokenId = tokenToId.compound[inputs[0].token.key];
|
||||
const collateralTokenId = tokenToId.compound[inputs[1].token.key];
|
||||
|
||||
return [
|
||||
{
|
||||
connector: "compound",
|
||||
method: "payback",
|
||||
args: [
|
||||
debtTokenId,
|
||||
convertTokenAmountToWei(inputs[0].value, inputs[0].token.decimals),
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
connector: "compound",
|
||||
method: "withdraw",
|
||||
args: [
|
||||
collateralTokenId,
|
||||
convertTokenAmountToWei(inputs[1].value, inputs[1].token.decimals),
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
|
|
@ -10,18 +10,43 @@
|
|||
</nuxt-link>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 flex items-center">
|
||||
<div
|
||||
style="background: radial-gradient(42.15% 42.15% at 48.94% 48.94%, #D6DAE0 75.67%, #F0F3F9 100%), #C4C4C4;"
|
||||
class="w-16 h-16 rounded-full flex items-center justify-center border border-[#CCDCF3]"
|
||||
>
|
||||
<div class="mt-10 flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="w-12 h-12 rounded-full flex items-center justify-center bg-[#1874FF]"
|
||||
style="background: radial-gradient(42.15% 42.15% at 48.94% 48.94%, #D6DAE0 75.67%, #F0F3F9 100%), #C4C4C4;"
|
||||
class="w-16 h-16 rounded-full flex items-center justify-center border border-[#CCDCF3]"
|
||||
>
|
||||
<CompoundIcon class="w-8 h-8 text-white" />
|
||||
<div
|
||||
class="w-12 h-12 rounded-full flex items-center justify-center bg-[#1874FF]"
|
||||
>
|
||||
<CompoundIcon class="w-8 h-8 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="ml-4 text-primary-black text-2xl font-semibold">Compound</h1>
|
||||
</div>
|
||||
<h1 class="ml-4 text-primary-black text-2xl font-semibold">Compound</h1>
|
||||
|
||||
<ButtonCTAOutlined
|
||||
class="px-4 h-9 w-[173px]"
|
||||
@click="$router.push({ hash: 'strategies?protocol=compound' })"
|
||||
>
|
||||
Strategies
|
||||
|
||||
<svg
|
||||
class="ml-auto"
|
||||
width="11"
|
||||
height="10"
|
||||
viewBox="0 0 11 10"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M4.3815 8.76139C4.38149 8.99703 4.64068 9.1407 4.8405 9.01581L10.859 5.25425C11.047 5.13675 11.047 4.86295 10.859 4.74545L4.84088 0.984069C4.64108 0.859187 4.38189 1.00283 4.38188 1.23845L4.38179 2.97869C4.38178 3.14437 4.24747 3.27867 4.08179 3.27867L1.23894 3.27867C1.07325 3.27867 0.93894 3.41299 0.93894 3.57867L0.93894 6.42096C0.93894 6.58664 1.07325 6.72096 1.23894 6.72096L4.08159 6.72096C4.24728 6.72096 4.3816 6.85528 4.38159 7.02097L4.3815 8.76139Z"
|
||||
fill="#1874FF"
|
||||
/>
|
||||
</svg>
|
||||
</ButtonCTAOutlined>
|
||||
</div>
|
||||
|
||||
<div class="mt-10" v-if="position">
|
||||
|
|
@ -163,13 +188,15 @@ import { useSearchFilter } from "~/composables/useSearchFilter";
|
|||
import { useStatus } from "~/composables/useStatus";
|
||||
import { useBigNumber } from "~/composables/useBigNumber";
|
||||
import CardCompound from "~/components/protocols/CardCompound.vue";
|
||||
import CompoundIcon from '~/assets/icons/compound.svg?inline'
|
||||
import CompoundIcon from "~/assets/icons/compound.svg?inline";
|
||||
import ButtonCTAOutlined from "~/components/common/input/ButtonCTAOutlined.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
BackIcon,
|
||||
CardCompound,
|
||||
CompoundIcon,
|
||||
ButtonCTAOutlined,
|
||||
},
|
||||
setup() {
|
||||
const {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user