Merge branch 'master' into vue-web3

This commit is contained in:
Georges KABBOUCHI 2021-09-06 01:37:07 +03:00
commit f2e032f2ac
36 changed files with 2914 additions and 270 deletions

View File

@ -0,0 +1,81 @@
<template>
<Dropdown>
<template #trigger="{ toggle }">
<Button
:disabled="disabled"
color="grey"
class="flex px-3 py-1"
:class="{ 'pointer-events-none': disabled }"
:style="{ background: disabled ? 'none' : '' }"
@click="toggle"
>
<div class="flex flex-col items-center">
<IconCurrency :currency="tokenKey" />
<div
class="mt-2 font-semibold text-center text-11"
:class="{
'dark:text-grey-pure': disabled
}"
>
{{ label }}
</div>
</div>
</Button>
</template>
<template #menu="{ close }">
<DropdownMenu class="px-1 max-h-60">
<span v-if="!tokens.length" class="font-medium whitespace-no-wrap"
>No tokens available!</span
>
<div
v-else
class="flex flex-col overflow-x-hidden overflow-y-auto scrollbar-hover' pl-1 space-y-2 scrollbar"
>
<Button
v-for="(tokenKey, index) in tokens"
:key="index"
color="grey"
class="py-2 pl-2 pr-4 mr-1"
@click="select(tokenKey, close)"
>
<TokenSelectOption :token-key="tokenKey" class="w-full" />
</Button>
</div>
</DropdownMenu>
</template>
</Dropdown>
</template>
<script>
import { computed, defineComponent } from '@nuxtjs/composition-api'
import { useToken } from '~/composables/useToken'
import DropdownMenu from '~/components/protocols/DropdownMenu.vue'
import Dropdown from './common/input/Dropdown.vue'
export default defineComponent({
components: {
DropdownMenu,
Dropdown
},
model: {
prop: 'tokenKey',
event: 'change',
},
props: {
tokenKey: { type: String, default: null },
tokens: { type: Array, default: () => [] },
disabled: { type: Boolean, default: false },
},
setup(props, { emit }) {
const { getTokenByKey } = useToken()
const token = computed(() => getTokenByKey(props.tokenKey))
const label = computed(() => (props.disabled ? token.value?.symbol : 'Change'))
function select(tokenKey, callback) {
emit('change', tokenKey)
callback()
}
return { select, label }
},
})
</script>

View File

@ -0,0 +1,25 @@
<template>
<div class="flex items-center">
<IconCurrency :currency="tokenKey" no-height class="w-8 h-8" />
<div
class="ml-2 font-semibold text-center text-ocean-blue-pure text-12 dark:text-light"
>
{{ symbol }}
</div>
</div>
</template>
<script>
import { computed, defineComponent } from '@nuxtjs/composition-api'
import { useToken } from '~/composables/useToken'
export default defineComponent({
props: {
tokenKey: { type: String, required: true },
},
setup(props) {
const { getTokenByKey } = useToken()
return { symbol: computed(() => getTokenByKey(props.tokenKey)?.symbol) }
},
})
</script>

View File

@ -92,7 +92,7 @@
</template>
<script>
import { defineComponent, watch, ref, toRef } from '@nuxtjs/composition-api'
import { defineComponent, watch, ref, toRef, computed } from '@nuxtjs/composition-api'
import { useInputListeners } from '@/composables/useInputListeners'
import SVGSpinner from '@/assets/img/icons/spinner.svg'
import { v4 as uuid } from 'uuid'
@ -124,7 +124,9 @@ export default defineComponent({
setup(props, context) {
const id = uuid()
const { symbol, getTokenByKey } = useToken(null, toRef(props, 'tokenKey'))
const { getTokenByKey } = useToken()
const token = computed(() => getTokenByKey(props.tokenKey))
const symbol = computed(() => token.value?.symbol)
const { formatDecimal } = useFormatting()
const { amountPattern } = usePattern()

View File

@ -0,0 +1,172 @@
<template>
<SidebarContextContainer class="h-full overflow-hidden">
<SidebarContextHeader>
<h1 class="font-bold text-primary-black text-lg">
{{ activeStrategy.schema.name }}
</h1>
<p
v-if="activeStrategy.schema.author"
class="font-medium text-[#1874FF] text-sm mt-2.5"
>
{{ activeStrategy.schema.author }}
</p>
</SidebarContextHeader>
<div class="h-full overflow-y-scroll scrollbar-hover flex flex-col">
<div class="mx-auto mb-6" style="width: 296px">
<div
class="flex-shrink-0 font-medium prose prose-sm text-primary-gray"
v-if="activeStrategy.schema.details"
v-html="activeStrategy.schema.details"
></div>
</div>
<div class="flex-1 bg-[#1874FF] bg-opacity-[0.04]">
<div class="mx-auto h-full" style="max-width: 296px">
<div class="space-y-4 py-9 h-full flex flex-col">
<div class="flex-1">
<div
v-for="(component, index) in components"
:key="index"
class="mb-6"
>
<input-amount
v-if="component.type === 'input-with-token'"
:key="index"
:value="component.value"
:token-key="component.token ? component.token.key : 'eth'"
:token-keys="
component.tokenKeys
? component.tokenKeys
: activeStrategy.getContext()['tokenKeys']
"
:error="component.error"
:placeholder="component.placeholder()"
@input="$event => component.onInput($event)"
@tokenKeyChanged="
tokenKey => {
component.onCustomInput({
token: getTokenByKey(tokenKey)
});
}
"
/>
<input-amount
v-else-if="component.type === 'input-amount'"
:key="index"
:value="component.value"
:token-key="
component.tokenKey
? component.tokenKey
: component.token
? component.token.key
: 'eth'
"
:error="component.error"
:placeholder="component.placeholder()"
@input="$event => component.onInput($event)"
/>
<input-numeric
v-else-if="component.type === 'input-numeric'"
:key="index"
:value="component.value"
:error="component.error"
:placeholder="component.placeholder()"
@input="$event => component.onInput($event)"
/>
<SidebarContextHeading
v-else-if="component.type === 'heading'"
:key="index"
>
{{ component.name }}
</SidebarContextHeading>
<div v-else-if="component.type === 'value'" :key="index">
<value-display :label="component.name">
{{ component.value }}
</value-display>
</div>
<SidebarSectionStatus
v-else-if="component.type === 'status'"
:key="index"
:liquidation="component.liquidation || '0'"
:status="component.status || '0'"
/>
</div>
</div>
<div class="mt-auto">
<ValidationErrors
:error-messages="error ? [error] : []"
class="mb-6"
/>
<ButtonCTA
class="w-full"
@click="submit"
:loading="pending"
:disabled="pending"
>
{{ activeStrategy.schema.submitText || "Submit" }}
</ButtonCTA>
</div>
</div>
</div>
</div>
</div>
</SidebarContextContainer>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import { useSidebar } from "~/composables/useSidebar";
import { protocolStrategies, DefineStrategy } from "~/core/strategies";
import { useStrategy } from "~/composables/useStrategy";
import InputAmount from "~/components/common/input/InputAmount.vue";
import { useToken } from "~/composables/useToken";
import ButtonCTA from "~/components/common/input/ButtonCTA.vue";
import ValueDisplay from "../components/ValueDisplay.vue";
import InputNumeric from "~/components/common/input/InputNumeric.vue";
export default defineComponent({
components: { InputAmount, ButtonCTA, ValueDisplay, InputNumeric },
props: {
protocol: {
type: String,
required: true
},
strategy: {
type: String,
required: true
}
},
setup(props) {
const { close } = useSidebar();
const { getTokenByKey } = useToken();
const strategies: DefineStrategy[] =
protocolStrategies[props.protocol] || [];
const {
components,
submit,
error,
strategy: activeStrategy,
pending
} = useStrategy(
strategies.find(strategy => strategy.id === props.strategy)
);
return {
components,
error,
submit,
activeStrategy,
getTokenByKey,
pending
};
}
});
</script>

View File

@ -0,0 +1,56 @@
<template>
<SidebarContextContainer class="h-full overflow-hidden">
<SidebarContextHeader class="xxl:hidden">Strategies</SidebarContextHeader>
<div class="h-full overflow-y-scroll scrollbar-hover">
<div class="mx-auto" style="max-width: 296px">
<div class="py-2 sm:py-4">
<div
v-for="strategy in strategies"
:key="strategy.name"
@click="selectStrategy(strategy)"
class="flex-shrink-0 flex flex-col px-6 py-4 mt-2 bg-background rounded cursor-pointer select-none first:mt-0 sm:mt-6 group hover:bg-selection"
>
<div class="flex items-start justify-between mb-3">
<h4
class="font-bold text-17 text-navi-pure-light dark:text-light"
>
{{ strategy.name }}
</h4>
</div>
<div class="font-medium leading-snug text-12 text-grey-pure">
{{ strategy.description }}
</div>
</div>
</div>
</div>
</div>
</SidebarContextContainer>
</template>
<script>
import { defineComponent, useRouter } from '@nuxtjs/composition-api'
import { protocolStrategies } from '~/core/strategies'
export default defineComponent({
props: {
protocol: {
type: String,
required: true,
}
},
setup(props) {
const router = useRouter()
const strategies = protocolStrategies[props.protocol] || [];
function selectStrategy(strategy) {
router.push({
hash: `#strategy?protocol=${props.protocol}&strategy=${strategy.id}`
})
}
return { strategies, selectStrategy, protocolStrategies }
},
})
</script>

View File

@ -12,6 +12,7 @@ import { useBigNumber } from "~/composables/useBigNumber";
import { usePosition } from "~/composables/usePosition";
import { useToken } from "~/composables/useToken";
import { useSorting } from "~/composables/useSorting";
import useEventBus from "../useEventBus";
const {
times,
@ -25,7 +26,7 @@ const {
} = useBigNumber();
const { getType } = usePosition();
const position = ref<any>({
export const position = ref<any>({
totalSupplyInEth: new BigNumber(0),
totalBorrowInEth: new BigNumber(0),
totalBorrowStableInEth: new BigNumber(0),
@ -69,8 +70,9 @@ export function useAaveV2Position(
const { activeNetworkId } = useNetwork();
const { activeAccount } = useDSA();
const { getTokenByKey, allATokensV2 } = useToken();
const { byMaxSupplyOrBorrowDesc } = useSorting();
const { byMaxSupplyOrBorrowDesc } = useSorting()
const { onEvent } = useEventBus()
const resolver = computed(() =>
chainId.value === 1
? "0xFb3a1D56eD56F046721B9aCa749895100754578b"
@ -108,6 +110,8 @@ export function useAaveV2Position(
position.value = await fetchPosition();
};
onEvent("protocol::aaveV2::refresh", refreshPosition);
watch(
library,
async val => {

View File

@ -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,
@ -27,7 +28,7 @@ const {
} = useBigNumber();
const { getType } = usePosition();
const position = ref<any>({
export const position = ref<any>({
totalSupplyInEth: new BigNumber(0),
totalBorrowInEth: new BigNumber(0),
totalBorrowStableInEth: new BigNumber(0),
@ -64,6 +65,7 @@ export function useCompoundPosition(
const { library } = useWeb3();
const { activeNetworkId } = useNetwork()
const { onEvent } = useEventBus()
const { activeAccount } = useDSA();
const { getTokenByKey } = useToken();
const { byMaxSupplyOrBorrowDesc } = useSorting()
@ -101,6 +103,8 @@ export function useCompoundPosition(
position.value = await fetchPosition();
};
onEvent("protocol::compound::refresh", refreshPosition);
watch(
library,
async val => {

View File

@ -9,8 +9,9 @@ BigNumber.config({ POW_PRECISION: 200 });
import abis from "~/constant/abis";
import addresses from "~/constant/addresses";
import { useDSA } from "../useDSA";
import useEventBus from "../useEventBus";
const trove = ref<any>({
export const trove = ref<any>({
collateral: "0",
debt: "0",
stabilityAmount: "0",
@ -26,7 +27,7 @@ const trove = ref<any>({
liquidation: "0"
});
const troveTypes = ref([
export const troveTypes = ref([
{
totalCollateral: "0",
price: "0",
@ -41,7 +42,7 @@ const troveTypes = ref([
}
]);
const troveOverallDetails = computed(() =>
export const troveOverallDetails = computed(() =>
troveTypes.value.find(t => t.tokenKey === trove.value.tokenKey)
);
@ -50,6 +51,7 @@ export function useLiquityPosition(
debtAmountRef: Ref = null
) {
const { library } = useWeb3();
const { onEvent } = useEventBus()
const { activeAccount } = useDSA();
const { isZero, times, div, max, minus, plus } = useBigNumber();
@ -178,6 +180,9 @@ export function useLiquityPosition(
}
}
onEvent("protocol::liquity::refresh", fetchPosition);
watch(
library,
async val => {

View File

@ -9,6 +9,7 @@ import { useDSA } from "~/composables/useDSA";
import { useToken } from "~/composables/useToken";
import { useWeb3 } from "@instadapp/vue-web3";
import { AbiItem } from "web3-utils";
import useEventBus from "../useEventBus";
const defaultVault = {
id: null,
@ -31,7 +32,7 @@ const isNewVault = ref(false);
const vaultTypes = ref([]);
const vaultType = ref("");
const vault = computed(() => {
export const vault = computed(() => {
const vlt = vaults.value.find(v => v.id === vaultId.value);
if (!isNewVault.value && !!vlt) {
return vlt;
@ -55,6 +56,7 @@ export function useMakerdaoPosition(
debtAmountRef: Ref = null
) {
const { library } = useWeb3();
const { onEvent } = useEventBus()
const { activeAccount } = useDSA();
const { isZero, ensureValue, times, div, max, gt } = useBigNumber();
const { getTokenByKey } = useToken();
@ -132,6 +134,8 @@ export function useMakerdaoPosition(
}
};
onEvent("protocol::makerdao::refresh", fetchPosition);
watch(
library,
async val => {

View File

@ -20,8 +20,14 @@ import { useBigNumber } from "./useBigNumber";
import { useSorting } from "./useSorting";
const balances = reactive({
user: null,
dsa: null
user: {
mainnet: {},
polygon: {}
},
dsa: {
mainnet: {},
polygon: {}
}
});
const prices = reactive({
@ -207,6 +213,7 @@ async function getBalances(
}
const { name, symbol, decimals, type, isStableCoin, key } = tokenData;
tokensBalObj[tokenAddress] = {
address: tokenAddress,
name,
symbol,
decimals,

View File

@ -0,0 +1,24 @@
import { onBeforeUnmount } from "@nuxtjs/composition-api";
import { TinyEmitter } from "tiny-emitter";
const eventEmitter = new TinyEmitter();
export default function useEventBus() {
const eventHandlers = [];
onBeforeUnmount(() =>
eventHandlers.forEach(eventHandler =>
eventEmitter.off(eventHandler.event, eventHandler.handler)
)
);
return {
onEvent(event, handler) {
eventHandlers.push({ event, handler });
eventEmitter.on(event, handler);
},
emitEvent(event, payload) {
eventEmitter.emit(event, payload);
}
};
}

View File

@ -35,11 +35,16 @@ import SidebarLiquityTroveWithdraw from '~/components/sidebar/context/liquity/Si
import SidebarLiquityTroveBorrow from '~/components/sidebar/context/liquity/SidebarLiquityTroveBorrow.vue'
import SidebarLiquityTrovePayback from '~/components/sidebar/context/liquity/SidebarLiquityTrovePayback.vue'
import SidebarStrategySelection from '~/components/sidebar/context/strategy/SidebarStrategySelection.vue'
import SidebarStrategy from '~/components/sidebar/context/strategy/SidebarStrategy.vue'
const sidebars = {
"#overview" : {component: SidebarOverview, back : false, close : true },
"#deposit-overview": {component: SidebarDepositOverview, back: { hash: 'overview' } },
'#withdraw-token': { component: SidebarWithdraw, back: { hash: 'overview' } },
'#strategies': { component: SidebarStrategySelection },
'#strategy': { component: SidebarStrategy },
"/aave-v2": { component: null },
"/aave-v2#supply": { component: SidebarAaveV2Supply },
"/aave-v2#borrow": { component: SidebarAaveV2Borrow },

157
composables/useStrategy.ts Normal file
View File

@ -0,0 +1,157 @@
import {
nextTick,
onMounted,
ref,
watch,
watchEffect
} from "@nuxtjs/composition-api";
import tokens from "~/constant/tokens";
import {
buildStrategy,
DefineStrategy,
StrategyProtocol
} from "~/core/strategies";
import { position as aaveV2Position } from "./protocols/useAaveV2Position";
import { position as compoundPosition } from "./protocols/useCompoundPosition";
import { vault as makerPosition } from "./protocols/useMakerdaoPosition";
import {
trove as liquityPosition,
troveTypes,
troveOverallDetails
} from "./protocols/useLiquityPosition";
import { useBalances } from "./useBalances";
import { useDSA } from "./useDSA";
import useEventBus from "./useEventBus";
import { useNotification } from "./useNotification";
import { useSidebar } from "./useSidebar";
import { useToken } from "./useToken";
import { useWeb3 } from "./useWeb3";
import { useBigNumber } from "./useBigNumber";
import tokenIdMapping from "~/constant/tokenIdMapping";
import { useFormatting } from "./useFormatting";
export function useStrategy(defineStrategy: DefineStrategy) {
const { web3, networkName, account } = useWeb3();
const { dsa } = useDSA();
const { prices, balances, fetchBalances } = useBalances();
const { close } = useSidebar();
const { valInt, getTokenByKey } = useToken();
const { emitEvent } = useEventBus();
const { toBN } = useBigNumber();
const formatting = useFormatting();
const {
showPendingTransaction,
showConfirmedTransaction
} = useNotification();
const strategy = buildStrategy(defineStrategy);
const components = ref(strategy.components);
const error = ref("");
const pending = ref(false);
// strategy.onUpdated(async () => {
// await nextTick();
// });
const submit = async () => {
error.value = "";
pending.value = true;
try {
const tx = await strategy.submit({
onReceipt: async () => {
showConfirmedTransaction(tx);
await fetchBalances(true);
emitEvent(`protocol::${strategy.schema.protocol}::refresh`, {});
},
from: account.value
});
showPendingTransaction(tx);
close();
} catch (e) {
error.value = e.message;
}
pending.value = false;
};
watch(
() => [
aaveV2Position,
makerPosition,
compoundPosition,
liquityPosition,
troveTypes,
troveOverallDetails
],
() => {
let position = null;
let positionExtra = {};
if (strategy.schema.protocol == StrategyProtocol.AAVE_V2) {
position = aaveV2Position.value;
} else if (strategy.schema.protocol == StrategyProtocol.MAKERDAO) {
position = makerPosition.value;
} else if (strategy.schema.protocol == StrategyProtocol.COMPOUND) {
position = compoundPosition.value;
} else if (strategy.schema.protocol == StrategyProtocol.LIQUITY) {
position = liquityPosition.value;
positionExtra["troveTypes"] = troveTypes.value;
positionExtra["troveOverallDetails"] = troveOverallDetails.value;
}
strategy.setProps({
convertTokenAmountToWei: valInt,
getTokenByKey,
toBN,
position,
positionExtra,
tokenIdMapping,
formatting
});
},
{ immediate: true }
);
watch(web3, () => strategy.setWeb3(web3.value), { immediate: true });
watch(dsa, () => strategy.setDSA(dsa.value), { immediate: true });
watch(
prices,
() => strategy.setProps({ prices: prices[networkName.value] }),
{ immediate: true }
);
watch(
balances,
() => {
strategy.setProps({
dsaBalances: balances.dsa[networkName.value],
userBalances: balances.user[networkName.value]
});
},
{ immediate: true }
);
watch(
networkName,
() =>
strategy.setProps({
tokens: tokens[networkName.value].allTokens,
tokenKeys: tokens[networkName.value].tokenKeys
}),
{ immediate: true }
);
// testing
onMounted(() => {
//@ts-ignore
window.strategy = strategy;
});
return {
strategy,
components,
submit,
error,
pending
};
}

View File

@ -55,14 +55,16 @@ export function useTenderly() {
loading.value = true;
try {
await axios({
method: "delete",
url: `https://api.tenderly.co/api/v1/account/${$config.TENDERLY_FORK_PATH}/fork/${forkId.value}`,
headers: {
"X-Access-key": $config.TENDERLY_KEY,
"Content-Type": "application/json"
}
});
if (forkId.value) {
await axios({
method: "delete",
url: `https://api.tenderly.co/api/v1/account/${$config.TENDERLY_FORK_PATH}/fork/${forkId.value}`,
headers: {
"X-Access-key": $config.TENDERLY_KEY,
"Content-Type": "application/json"
}
});
}
} catch (error) {}
forkId.value = null;

View File

@ -1,4 +1,4 @@
import { computed, onMounted, ref, watch } from "@nuxtjs/composition-api";
import { computed, onMounted, ref } from "@nuxtjs/composition-api";
import Web3 from "web3";
import { SafeAppWeb3Modal } from "@gnosis.pm/safe-apps-web3modal";
import { Network } from "./useNetwork";
@ -134,10 +134,6 @@ export function useWeb3() {
web3.value = newWeb3;
};
watch(web3, () => {
window.web3 = web3.value;
});
return {
account,
chainId,

View File

@ -0,0 +1,151 @@
import DSA, { Spell } from "dsa-connect";
import Web3 from "web3";
import slugify from "slugify";
import { Strategy } from "./strategy";
import BigNumber from "bignumber.js";
import tokenIdMapping from "~/constant/tokenIdMapping";
import { useFormatting } from "~/composables/useFormatting";
export interface IStrategyContext {
dsa: DSA;
web3: Web3;
components: IStrategyComponent<StrategyComponentType>[];
// TODO: add types in useStrategy.ts
dsaBalances?: { [address: string]: IStrategyToken };
userBalances?: { [address: string]: IStrategyToken };
tokens?: { [address: string]: IStrategyToken };
convertTokenAmountToWei?: (value: any, decimals: any) => string;
getTokenByKey?: (key: string) => IStrategyToken;
position?: any;
positionExtra?: { [key: string]: any };
variables?: { [key: string]: any };
toBN?: (value: any) => BigNumber;
tokenIdMapping?: typeof tokenIdMapping;
formatting?: ReturnType<typeof useFormatting>;
}
export interface IStrategyToken {
address: string;
key: string;
symbol: string;
balance: string;
decimals: string;
// supply: string;
// borrow: string;
}
export enum StrategyComponentType {
// INPUT = "input",
INPUT_NUMERIC = "input-numeric",
INPUT_AMOUNT = "input-amount",
INPUT_WITH_TOKEN = "input-with-token",
HEADING = "heading",
VALUE = "value",
STATUS = "status"
}
export type StrategyComponentParameterMap = {
// [StrategyInputType.INPUT]: {};
[StrategyComponentType.INPUT_NUMERIC]: {};
[StrategyComponentType.INPUT_AMOUNT]: {
tokenKey: string;
};
[StrategyComponentType.INPUT_WITH_TOKEN]: {
token?: IStrategyToken;
};
[StrategyComponentType.HEADING]: {};
[StrategyComponentType.VALUE]: {};
[StrategyComponentType.STATUS]: {
liquidation?: any;
status?: any;
};
};
export interface IStrategyComponent<
ComponentType extends StrategyComponentType
> {
type: ComponentType;
name: string;
variables?: { [key: string]: any };
placeholder?: (
context: IStrategyContext & {
component: IStrategyComponent<ComponentType> &
StrategyComponentParameterMap[ComponentType];
}
) => string;
validate?: (
context: IStrategyContext & {
component: IStrategyComponent<ComponentType> &
StrategyComponentParameterMap[ComponentType];
}
) => string | void;
defaults?: (context: Omit<IStrategyContext, "components">) => object;
update?: (
context: IStrategyContext & {
component: IStrategyComponent<ComponentType> &
StrategyComponentParameterMap[ComponentType];
}
) => void;
value?: any;
[key: string]: any;
}
export enum StrategyProtocol {
AAVE_V2 = "aaveV2",
COMPOUND = "compound",
MAKERDAO = "makerdao",
LIQUITY = "liquity"
}
export interface IStrategy {
protocol: StrategyProtocol;
id?: string;
name: string;
description: string;
details?: string;
author?: string;
components: IStrategyComponent<StrategyComponentType>[];
variables?: object;
spells: (context: IStrategyContext) => Promise<Spell[]> | Spell[];
validate?: (
context: IStrategyContext
) => Promise<void | string> | void | string;
submitText?: string;
}
export function defineStrategyComponent<
ComponentType extends StrategyComponentType
>(
component: IStrategyComponent<ComponentType> &
StrategyComponentParameterMap[ComponentType]
) {
return component as IStrategyComponent<ComponentType> &
StrategyComponentParameterMap[ComponentType];
}
export function defineStrategy(strategy: IStrategy) {
return {
...strategy,
id: strategy.id ? strategy.id : slugify(strategy.name).toLowerCase()
};
}
export function buildStrategy(schema: DefineStrategy) {
return new Strategy(schema);
}
export type DefineStrategy = ReturnType<typeof defineStrategy>;

View File

@ -0,0 +1,197 @@
import DSA from "dsa-connect";
import Web3 from "web3";
import { DefineStrategy, IStrategyContext } from ".";
export class Strategy {
schema: DefineStrategy;
components = [];
context = {
web3: null as Web3,
dsa: null as DSA
};
listeners = [];
props: object = {
prices: {},
dsaTokens: {},
userTokens: {}
};
constructor(schema: DefineStrategy) {
this.schema = schema;
this.components = this.generateComponents(this.schema.components);
}
getBaseContext(): Omit<IStrategyContext, "components"> {
return {
...this.context,
...this.props,
variables: this.schema.variables || {}
};
}
getContext(): IStrategyContext {
return {
...this.getBaseContext(),
components: this.components
};
}
setProps(props: object) {
Object.assign(this.props, props);
const components = this.components;
for (const component of components) {
if (typeof component.defaults !== "function") {
continue;
}
if (component.defaulted) {
continue;
}
Object.assign(component, component.defaults(this.getBaseContext()));
component.defaulted = true;
}
this.notifyListeners("SET_PROPS");
}
generateComponents(components) {
return components.map((component, idx) => {
const computedComponent = {
...component,
value: component.value || "",
error: component.error || "",
placeholder: () => {
return component.placeholder
? component.placeholder({
...this.getContext(),
component: this.components[idx]
})
: null;
},
onInput: (val: any) => {
this.components[idx].error = "";
this.components[idx].value = val;
if (val) {
this.components[idx].error = this.components[idx].validate({
...this.getContext(),
component: this.components[idx]
});
}
this.notifyListeners("onInput");
},
onCustomInput: (values: object) => {
this.components[idx] = Object.assign(this.components[idx], values);
this.components[idx].error = this.components[idx].validate({
...this.getContext(),
component: this.components[idx]
});
this.notifyListeners("onCustomInput");
}
};
let defaults = {};
if (component.defaults) {
defaults = component.defaults(this.getBaseContext());
}
return {
...computedComponent,
...defaults
};
});
}
async spells() {
return await this.schema.spells(this.getContext());
}
async submit(options) {
await this.validate();
const allSpells = await this.spells();
const spells = this.context.dsa.Spell();
for (const spell of allSpells) {
spells.add(spell);
}
return await this.context.dsa.cast({
spells,
onReceipt: options?.onReceipt,
from: options?.from
});
}
async validate() {
const components = this.components;
for (const component of components) {
if (typeof component.validate !== "function") {
continue;
}
const result = await component.validate({
...this.getContext(),
component
});
if (typeof result === "string") {
throw new Error(result || "Error has occurred");
}
}
if (this.schema.validate) {
const result = await this.schema.validate(this.getContext());
if (typeof result === "string") {
throw new Error(result || "Error has occurred");
}
}
}
setWeb3(web3: Web3) {
this.context.web3 = web3;
this.notifyListeners("WEB3");
}
setDSA(dsa: DSA) {
this.context.dsa = dsa;
this.notifyListeners("DSA");
}
async notifyListeners( from = "") {
if(from && process.env.NODE_ENV === "development") {
console.log(`${from} updated`);
}
for (const listener of this.listeners) {
await listener(this);
}
this.components.forEach(component =>
component.update?.({
...this.getContext(),
component
})
);
}
onUpdated(cb) {
this.listeners.push(cb);
}
}

11
core/strategies/index.ts Normal file
View File

@ -0,0 +1,11 @@
import AaveV2 from "./protocols/aave-v2";
import Compound from "./protocols/compound";
import Liquity from "./protocols/liquity";
export const protocolStrategies = {
aaveV2: AaveV2,
compound: Compound,
liquity: Liquity
};
export * from "./helpers";

View File

@ -0,0 +1,266 @@
import BigNumber from "bignumber.js";
import {
defineStrategy,
defineStrategyComponent,
StrategyComponentType,
StrategyProtocol
} from "../../helpers";
export default defineStrategy({
protocol: StrategyProtocol.AAVE_V2,
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",
debtRateMode: 2
},
components: [
defineStrategyComponent({
type: StrategyComponentType.INPUT_WITH_TOKEN,
name: "Collateral",
placeholder: ({ component: input }) =>
input.token ? `${input.token.symbol} to Deposit` : "",
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, variables }) => ({
token: getTokenByKey?.(variables.collateralTokenKey)
})
}),
defineStrategyComponent({
type: StrategyComponentType.INPUT_WITH_TOKEN,
name: "Debt",
placeholder: ({ component: input }) =>
input.token ? `${input.token.symbol} to Borrow` : "",
validate: ({ component: input }) => {
if (!input.token) {
return "Debt token is required";
}
if (!input.value) {
return "Debt amount is required";
}
},
defaults: ({ getTokenByKey, variables }) => ({
token: getTokenByKey?.(variables.debtTokenKey)
})
}),
defineStrategyComponent({
type: StrategyComponentType.HEADING,
name: "Projected Debt Position"
}),
defineStrategyComponent({
type: StrategyComponentType.STATUS,
name: "Status",
update: ({ position, component, components, toBN }) => {
if (
toBN(components[0].value).isZero() &&
toBN(components[1].value).isZero()
) {
return;
}
if (!position) {
return;
}
const newPositionData = changedPositionData(position, components);
const stats = calculateStats(newPositionData);
component.liquidation = BigNumber.max(
toBN(stats.totalMaxBorrowLimitInEth).div(stats.totalSupplyInEth),
"0"
).toFixed();
component.status = BigNumber.max(
toBN(stats.totalBorrowInEth).div(stats.totalSupplyInEth),
"0"
).toFixed();
}
}),
defineStrategyComponent({
type: StrategyComponentType.VALUE,
name: "LIQUIDATION PRICE (IN ETH)",
value: "-",
update: ({ position, component, components, toBN, formatting }) => {
if (!position) {
return;
}
const newPositionData = changedPositionData(position, components);
const initialStats = calculateStats(position.data);
const newStats = calculateStats(newPositionData);
const stats =
toBN(components[0].value).isZero() &&
toBN(components[1].value).isZero()
? initialStats
: newStats;
let liquidationPrice = "0";
if (!toBN(stats.ethSupplied).isZero()) {
liquidationPrice = BigNumber.max(
toBN(stats.totalBorrowInEth)
.div(stats.totalMaxLiquidationLimitInEth)
.times(position.ethPriceInUsd),
"0"
).toFixed();
}
component.value = `${formatting.formatUsdMax(
liquidationPrice,
position.ethPriceInUsd
)} / ${formatting.formatUsd(position.ethPriceInUsd)}`;
}
})
],
validate: async ({ position, components: inputs, toBN }) => {
if (toBN(inputs[0].value).isZero() && toBN(inputs[1].value).isZero()) {
return;
}
const newPositionData = changedPositionData(position, inputs);
const stats = calculateStats(newPositionData);
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 ({
components: inputs,
convertTokenAmountToWei,
variables
}) => {
return [
{
connector: "aave_v2",
method: "deposit",
args: [
inputs[0].token.address,
convertTokenAmountToWei(inputs[0].value, inputs[0].token.decimals),
0,
0
]
},
{
connector: "aave_v2",
method: "borrow",
args: [
inputs[1].token.address,
convertTokenAmountToWei(inputs[1].value, inputs[1].token.decimals),
variables.debtRateMode,
0,
0
]
}
];
}
});
const changedPositionData = (position, inputs) => {
return position.data.map(position => {
const changedPosition = { ...position };
if (inputs[1].token.key === position.key) {
changedPosition.borrow = BigNumber.max(
new BigNumber(position.borrow).plus(inputs[1].value || "0"),
"0"
).toFixed();
}
if (inputs[0].token.key === position.key) {
changedPosition.supply = BigNumber.max(
new BigNumber(position.supply).plus(inputs[0].value || "0"),
"0"
).toFixed();
}
return changedPosition;
});
};
const calculateStats = positionData => {
return positionData.reduce(
(
stats,
{ key, supply, borrow, borrowStable, priceInEth, factor, liquidation }
) => {
if (key === "eth") {
stats.ethSupplied = supply;
}
const borrowTotal = new BigNumber(borrow).plus(borrowStable);
stats.totalSupplyInEth = new BigNumber(supply)
.times(priceInEth)
.plus(stats.totalSupplyInEth)
.toFixed();
stats.totalBorrowInEth = new BigNumber(borrowTotal)
.times(priceInEth)
.plus(stats.totalBorrowInEth)
.toFixed();
stats.totalMaxBorrowLimitInEth = new BigNumber(priceInEth)
.times(factor)
.times(supply)
.plus(stats.totalMaxBorrowLimitInEth)
.toFixed();
stats.totalMaxLiquidationLimitInEth = new BigNumber(priceInEth)
.times(liquidation)
.times(supply)
.plus(stats.totalMaxLiquidationLimitInEth)
.toFixed();
return stats;
},
{
totalSupplyInEth: "0",
totalBorrowInEth: "0",
totalMaxBorrowLimitInEth: "0",
totalMaxLiquidationLimitInEth: "0"
}
);
};

View File

@ -0,0 +1,8 @@
import depositAndBorrow from "./deposit-and-borrow"
import paybackAndWithdraw from "./payback-and-withdraw"
export default [
depositAndBorrow,
paybackAndWithdraw,
]

View File

@ -0,0 +1,262 @@
import BigNumber from "bignumber.js";
import tokens from "~/constant/tokens";
import {
defineStrategy,
defineStrategyComponent,
StrategyComponentType,
StrategyProtocol
} from "../../helpers";
export default defineStrategy({
protocol: StrategyProtocol.AAVE_V2,
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>`,
components: [
defineStrategyComponent({
type: StrategyComponentType.INPUT_WITH_TOKEN,
name: "Debt",
placeholder: ({ component: input }) =>
input.token ? `${input.token.symbol} to Payback` : "",
validate: ({ component: 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")
})
}),
defineStrategyComponent({
type: StrategyComponentType.INPUT_WITH_TOKEN,
name: "Collateral",
placeholder: ({ component: input }) =>
input.token ? `${input.token.symbol} to Withdraw` : "",
validate: ({ component: input, position, toBN }) => {
if (!input.token) {
return "Collateral token is required";
}
if (!input.value) {
return "Collateral amount is required";
}
if (position) {
const collateralPosition = position.data.find(
item => item.key === input.token.key
);
if (collateralPosition) {
const collateralBalance = toBN(collateralPosition.supply);
if (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, component, components, toBN }) => {
if (
toBN(components[0].value).isZero() &&
toBN(components[1].value).isZero()
) {
return;
}
if (!position) {
return;
}
const newPositionData = changedPositionData(position, components);
const stats = calculateStats(newPositionData);
component.liquidation = BigNumber.max(
toBN(stats.totalMaxLiquidationLimitInEth).div(stats.totalSupplyInEth),
"0"
).toFixed();
component.status = BigNumber.max(
toBN(stats.totalBorrowInEth).div(stats.totalSupplyInEth),
"0"
).toFixed();
}
}),
defineStrategyComponent({
type: StrategyComponentType.VALUE,
name: "LIQUIDATION PRICE (IN ETH)",
value: "-",
update: ({ position, component, components, toBN, formatting }) => {
if (!position) {
return;
}
const newPositionData = changedPositionData(position, components);
const initialStats = calculateStats(position.data);
const newStats = calculateStats(newPositionData);
const stats =
toBN(components[0].value).isZero() &&
toBN(components[1].value).isZero()
? initialStats
: newStats;
let liquidationPrice = "0";
if (!toBN(stats.ethSupplied).isZero()) {
liquidationPrice = BigNumber.max(
toBN(stats.totalBorrowInEth)
.div(stats.totalMaxLiquidationLimitInEth)
.times(position.ethPriceInUsd),
"0"
).toFixed();
}
component.value = `${formatting.formatUsdMax(
liquidationPrice,
position.ethPriceInUsd
)} / ${formatting.formatUsd(position.ethPriceInUsd)}`;
}
})
],
validate: async ({ position, components: inputs, toBN }) => {
if (toBN(inputs[0].value).isZero() && toBN(inputs[1].value).isZero()) {
return;
}
const newPositionData = changedPositionData(position, inputs);
const stats = calculateStats(newPositionData);
let maxLiquidation = "0";
if (!toBN(stats.totalSupplyInEth).isZero()) {
maxLiquidation = BigNumber.max(
toBN(stats.totalMaxLiquidationLimitInEth).div(stats.totalSupplyInEth),
"0"
).toFixed();
}
const status = BigNumber.max(
toBN(stats.totalBorrowInEth).div(stats.totalSupplyInEth),
"0"
);
if (status.gt(toBN(maxLiquidation).minus("0.0001"))) {
return "Position will liquidate.";
}
},
spells: async ({ components: inputs, convertTokenAmountToWei }) => {
return [
{
connector: "aave_v2",
method: "payback",
args: [
inputs[0].token.address,
convertTokenAmountToWei(inputs[0].value, inputs[0].token.decimals),
2,
0,
0
]
},
{
connector: "aave_v2",
method: "withdraw",
args: [
inputs[1].token.address,
convertTokenAmountToWei(inputs[1].value, inputs[1].token.decimals),
0,
0
]
}
];
}
});
const changedPositionData = (position, inputs) => {
return position.data.map(position => {
const changedPosition = { ...position };
if (inputs[0].token.key === position.key) {
changedPosition.borrow = BigNumber.max(
new BigNumber(position.borrow).minus(inputs[0].value),
"0"
).toFixed();
}
if (inputs[1].token.key === position.key) {
changedPosition.supply = BigNumber.max(
new BigNumber(position.supply).minus(inputs[1].value),
"0"
).toFixed();
}
return changedPosition;
});
};
const calculateStats = positionData => {
return positionData.reduce(
(
stats,
{ key, supply, borrow, borrowStable, priceInEth, factor, liquidation }
) => {
if (key === "eth") {
stats.ethSupplied = supply;
}
const borrowTotal = new BigNumber(borrow).plus(borrowStable);
stats.totalSupplyInEth = new BigNumber(supply)
.times(priceInEth)
.plus(stats.totalSupplyInEth)
.toFixed();
stats.totalBorrowInEth = new BigNumber(borrowTotal)
.times(priceInEth)
.plus(stats.totalBorrowInEth)
.toFixed();
stats.totalMaxBorrowLimitInEth = new BigNumber(priceInEth)
.times(factor)
.times(supply)
.plus(stats.totalMaxBorrowLimitInEth)
.toFixed();
stats.totalMaxLiquidationLimitInEth = new BigNumber(priceInEth)
.times(liquidation)
.times(supply)
.plus(stats.totalMaxLiquidationLimitInEth)
.toFixed();
return stats;
},
{
totalSupplyInEth: "0",
totalBorrowInEth: "0",
totalMaxBorrowLimitInEth: "0",
totalMaxLiquidationLimitInEth: "0"
}
);
};

View File

@ -0,0 +1,317 @@
import BigNumber from "bignumber.js";
import {
defineStrategy,
defineStrategyComponent,
StrategyComponentType,
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"
},
components: [
defineStrategyComponent({
type: StrategyComponentType.INPUT_WITH_TOKEN,
name: "Collateral",
placeholder: ({ component: input }) =>
input.token ? `${input.token.symbol} to Deposit` : "",
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, variables }) => ({
token: getTokenByKey?.(variables.collateralTokenKey)
})
}),
defineStrategyComponent({
type: StrategyComponentType.INPUT_WITH_TOKEN,
name: "Debt",
placeholder: ({ component: input }) =>
input.token ? `${input.token.symbol} to Borrow` : "",
validate: ({ component: input }) => {
if (!input.token) {
return "Debt token is required";
}
if (!input.value) {
return "Debt amount is required";
}
},
defaults: ({ getTokenByKey, variables }) => ({
token: getTokenByKey?.(variables.debtTokenKey)
})
}),
defineStrategyComponent({
type: StrategyComponentType.HEADING,
name: "Projected Debt Position"
}),
defineStrategyComponent({
type: StrategyComponentType.STATUS,
name: "Status",
update: ({ position, component, components, toBN, tokenIdMapping }) => {
if (
toBN(components[0].value).isZero() &&
toBN(components[1].value).isZero()
) {
return;
}
if (!position) {
return;
}
const newPositionData = changedPositionData(position, components, tokenIdMapping.tokenToId);
const stats = calculateStats(newPositionData);
component.liquidation = BigNumber.max(
toBN(stats.totalMaxBorrowLimitInEth).div(stats.totalSupplyInEth),
"0"
).toFixed();
component.status = BigNumber.max(
toBN(stats.totalBorrowInEth).div(stats.totalSupplyInEth),
"0"
).toFixed();
}
}),
defineStrategyComponent({
type: StrategyComponentType.VALUE,
name: "LIQUIDATION PRICE (IN ETH)",
value: "-",
update: ({
position,
component,
components,
toBN,
formatting,
tokenIdMapping
}) => {
if (!position) {
return;
}
const newPositionData = changedPositionData(
position,
components,
tokenIdMapping.tokenToId
);
const initialStats = calculateStats(position.data);
const newStats = calculateStats(newPositionData);
const stats =
toBN(components[0].value).isZero() &&
toBN(components[1].value).isZero()
? initialStats
: newStats;
let liquidationPrice = "0";
if (!toBN(stats.ethSupplied).isZero()) {
liquidationPrice = BigNumber.max(
toBN(stats.totalBorrowInEth)
.div(stats.totalMaxBorrowLimitInEth)
.times(position.ethPriceInUsd),
"0"
).toFixed();
}
component.value = `${formatting.formatUsdMax(
liquidationPrice,
position.ethPriceInUsd
)} / ${formatting.formatUsd(position.ethPriceInUsd)}`;
}
})
],
validate: async ({ position, components: 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 ({
components: 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
]
}
];
}
});
const changedPositionData = (position, inputs, tokenToId) => {
return position.data.map(position => {
const changedPosition = { ...position };
if (tokenToId.compound[inputs[1].token.key] === position.cTokenId) {
changedPosition.borrow = BigNumber.max(
new BigNumber(position.borrow).plus(inputs[1].value),
"0"
).toFixed();
}
if (tokenToId.compound[inputs[0].token.key] === position.cTokenId) {
changedPosition.supply = BigNumber.max(
new BigNumber(position.supply).plus(inputs[0].value),
"0"
).toFixed();
}
return changedPosition;
});
};
const calculateStats = positionData => {
return positionData.reduce(
(stats, { key, supply, borrow, priceInEth, factor }) => {
if (key === "eth") {
stats.ethSupplied = supply;
}
stats.totalSupplyInEth = new BigNumber(supply)
.times(priceInEth)
.plus(stats.totalSupplyInEth)
.toFixed();
stats.totalBorrowInEth = new BigNumber(borrow)
.times(priceInEth)
.plus(stats.totalBorrowInEth)
.toFixed();
stats.totalMaxBorrowLimitInEth = new BigNumber(priceInEth)
.times(factor)
.times(supply)
.plus(stats.totalMaxBorrowLimitInEth)
.toFixed();
return stats;
},
{
totalSupplyInEth: "0",
totalBorrowInEth: "0",
totalMaxBorrowLimitInEth: "0",
ethSupplied: "0"
}
);
};

View File

@ -0,0 +1,8 @@
import depositAndBorrow from "./deposit-and-borrow"
import paybackAndWithdraw from "./payback-and-withdraw"
export default [
depositAndBorrow,
paybackAndWithdraw,
]

View File

@ -0,0 +1,307 @@
import BigNumber from "bignumber.js";
import {
defineStrategy,
defineStrategyComponent,
StrategyComponentType,
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>`,
components: [
defineStrategyComponent({
type: StrategyComponentType.INPUT_WITH_TOKEN,
name: "Debt",
placeholder: ({ component: input }) =>
input.token ? `${input.token.symbol} to Payback` : "",
validate: ({ component: 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")
})
}),
defineStrategyComponent({
type: StrategyComponentType.INPUT_WITH_TOKEN,
name: "Collateral",
placeholder: ({ component: input }) =>
input.token ? `${input.token.symbol} to Withdraw` : "",
validate: ({ component: 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")
})
}),
defineStrategyComponent({
type: StrategyComponentType.HEADING,
name: "Projected Debt Position"
}),
defineStrategyComponent({
type: StrategyComponentType.STATUS,
name: "Status",
update: ({ position, component, components, toBN, tokenIdMapping }) => {
if (
toBN(components[0].value).isZero() &&
toBN(components[1].value).isZero()
) {
return;
}
if (!position) {
return;
}
const newPositionData = changedPositionData(position, components, tokenIdMapping.tokenToId);
const stats = calculateStats(newPositionData);
component.liquidation = BigNumber.max(
toBN(stats.totalMaxLiquidationLimitInEth).div(stats.totalSupplyInEth),
"0"
).toFixed();
component.status = BigNumber.max(
toBN(stats.totalBorrowInEth).div(stats.totalSupplyInEth),
"0"
).toFixed();
}
}),
defineStrategyComponent({
type: StrategyComponentType.VALUE,
name: "LIQUIDATION PRICE (IN ETH)",
value: "-",
update: ({ position, component, components, toBN, formatting, tokenIdMapping }) => {
if (!position) {
return;
}
const newPositionData = changedPositionData(position, components, tokenIdMapping.tokenToId);
const initialStats = calculateStats(position.data);
const newStats = calculateStats(newPositionData);
const stats =
toBN(components[0].value).isZero() &&
toBN(components[1].value).isZero()
? initialStats
: newStats;
let liquidationPrice = "0";
if (!toBN(stats.ethSupplied).isZero()) {
liquidationPrice = BigNumber.max(
toBN(stats.totalBorrowInEth)
.div(stats.totalMaxBorrowLimitInEth)
.times(position.ethPriceInUsd),
"0"
).toFixed();
}
component.value = `${formatting.formatUsdMax(
liquidationPrice,
position.ethPriceInUsd
)} / ${formatting.formatUsd(position.ethPriceInUsd)}`;
}
})
],
validate: async ({ position, components: 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 ({ components: 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
]
}
];
}
});
const changedPositionData = (position, inputs, tokenToId) => {
return position.data.map(position => {
const changedPosition = { ...position };
if (tokenToId.compound[inputs[0].token.key] === position.cTokenId) {
changedPosition.borrow = BigNumber.max(
new BigNumber(position.borrow).minus(inputs[0].value),
"0"
).toFixed();
}
if (tokenToId.compound[inputs[1].token.key] === position.cTokenId) {
changedPosition.supply = BigNumber.max(
new BigNumber(position.supply).minus(inputs[1].value),
"0"
).toFixed();
}
return changedPosition;
});
};
const calculateStats = positionData => {
return positionData.reduce(
(stats, { key, supply, borrow, priceInEth, factor }) => {
if (key === "eth") {
stats.ethSupplied = supply;
}
stats.totalSupplyInEth = new BigNumber(supply)
.times(priceInEth)
.plus(stats.totalSupplyInEth)
.toFixed();
stats.totalBorrowInEth = new BigNumber(borrow)
.times(priceInEth)
.plus(stats.totalBorrowInEth)
.toFixed();
stats.totalMaxBorrowLimitInEth = new BigNumber(priceInEth)
.times(factor)
.times(supply)
.plus(stats.totalMaxBorrowLimitInEth)
.toFixed();
return stats;
},
{
totalSupplyInEth: "0",
totalBorrowInEth: "0",
totalMaxBorrowLimitInEth: "0",
ethSupplied: "0"
}
);
};

View File

@ -0,0 +1,104 @@
import {
defineStrategy,
StrategyProtocol,
StrategyComponentType,
defineStrategyComponent
} from "../../helpers";
export default defineStrategy({
protocol: StrategyProtocol.LIQUITY,
name: "Close Trove",
description:
"Close trove: Payback debt, withdraw Collateral & Liquidation Reserve.",
author: "Instadapp Team",
submitText: "Close Trove",
details: `<p class="text-center">This strategy executes:</p>
<ul>
<li>Close Trove</li>
</ul>`,
components: [
defineStrategyComponent({
type: StrategyComponentType.HEADING,
name: "Payback"
}),
defineStrategyComponent({
type: StrategyComponentType.VALUE,
name: "Net Debt",
update: ({ position, positionExtra, component, formatting, toBN }) => {
const troveOverallDetails = positionExtra["troveOverallDetails"];
const netDebt = toBN(position.debt).minus(
troveOverallDetails.liquidationReserve
);
component.value = `${formatting.formatDecimal(netDebt, 2)} LUSD`;
}
}),
defineStrategyComponent({
type: StrategyComponentType.HEADING,
name: "Withdraw"
}),
defineStrategyComponent({
type: StrategyComponentType.VALUE,
name: "Collateral",
update: ({ position, component, formatting }) => {
component.value = `${formatting.formatDecimal(position.collateral, 2)} ETH`;
}
}),
defineStrategyComponent({
type: StrategyComponentType.VALUE,
name: "Liquidation Reserve",
update: ({ positionExtra, component, formatting }) => {
const troveOverallDetails = positionExtra["troveOverallDetails"];
component.value = `${formatting.formatDecimal(
troveOverallDetails.liquidationReserve,
2
)} LUSD`;
}
})
],
validate: async ({
position,
positionExtra,
toBN,
getTokenByKey,
dsaBalances
}) => {
const troveOpened =
!toBN(position.collateral).isZero() && !toBN(position.debt).isZero();
if (!troveOpened) {
return "You should open new trove first";
}
const troveOverallDetails = positionExtra["troveOverallDetails"];
const netDebt = toBN(position.debt).minus(
troveOverallDetails.liquidationReserve
);
const debtToken = getTokenByKey("lusd");
const debtTokenBalance = dsaBalances[debtToken.address].balance;
if (toBN(position.debt).gt(debtTokenBalance)) {
const lackOfBalance = netDebt.minus(debtTokenBalance).toPrecision(6, 0);
return `You need ${lackOfBalance} ${debtToken.symbol} more to close your Trove.`;
}
},
spells: async () => {
const setId = 0;
return [
{
connector: "LIQUITY-A",
method: "close",
args: [setId]
}
];
}
});

View File

@ -0,0 +1,284 @@
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: "Deposit & Borrow",
description: "Deposit collateral & borrow asset in a single txn.",
details: `<p class="text-center">This strategy executes:</p>
<ul>
<li>Deposit ETH as collateral</li>
<li>Borrow LUSD as Debt</li>
</ul>`,
submitText: "Deposit & Borrow",
author: "Instadapp Team",
variables: {
collateralTokenKey: "eth",
debtTokenKey: "lusd"
},
components: [
defineStrategyComponent({
type: StrategyComponentType.INPUT_WITH_TOKEN,
name: "Collateral",
placeholder: ({ component: input }) =>
input.token ? `${input.token.symbol} to Deposit` : "",
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, variables }) => ({
token: getTokenByKey?.(variables.collateralTokenKey)
})
}),
defineStrategyComponent({
type: StrategyComponentType.INPUT_WITH_TOKEN,
name: "Debt",
placeholder: ({ component: input }) =>
input.token ? `${input.token.symbol} to Borrow` : "",
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, variables }) => ({
token: getTokenByKey?.(variables.debtTokenKey)
})
}),
defineStrategyComponent({
type: StrategyComponentType.HEADING,
name: "Projected Debt Position"
}),
defineStrategyComponent({
type: StrategyComponentType.STATUS,
name: "Status",
update: ({ position, positionExtra, component, components, toBN }) => {
if (
toBN(components[0].value).isZero() &&
toBN(components[1].value).isZero()
) {
return;
}
if (!position && !positionExtra) {
return;
}
const troveOverallDetails = positionExtra["troveOverallDetails"];
component.liquidation = troveOverallDetails.liquidation;
component.status =
toBN(components[0].value).isZero() &&
!toBN(components[1].value).isZero()
? "1.1"
: toBN(components[1].value)
.div(toBN(components[0].value).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 =
toBN(components[0].value).isZero() &&
!toBN(components[1].value).isZero()
? toBN(position.price)
.times("1.1")
.toFixed()
: BigNumber.max(
toBN(components[1].value)
.div(components[0].value)
.div(troveOverallDetails.liquidation),
"0"
).toFixed();
component.value = `${formatting.formatUsdMax(
isNaN(parseInt(liquidationPrice)) ? "0" : liquidationPrice,
position.price
)} / ${formatting.formatUsd(position.price)}`;
}
})
],
validate: async ({ position, positionExtra, 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";
}
// const troveOverallDetails = positionExtra["troveOverallDetails"];
// const status =
// toBN(inputs[0].value).isZero() && !toBN(inputs[1].value).isZero()
// ? toBN("1.1")
// : toBN(inputs[0].value)
// .times(position.price)
// .div(inputs[1].value);
// console.log(status.toFixed(), troveOverallDetails.liquidation);
// if (status.gt(toBN(troveOverallDetails.liquidation).minus("0.0001"))) {
// return "Position will liquidate.";
// }
},
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 depositAmountInWei = convertTokenAmountToWei(
inputs[0].value,
inputs[0].token.decimals
);
const totalDepositAmountInWei = toBN(depositAmountInWei)
.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 borrowAmountInWei = convertTokenAmountToWei(
inputs[1].value,
inputs[1].value.decimals
);
const totalBorrowAmountInWei = convertTokenAmountToWei(
changedDebt,
inputs[1].value.decimals
);
const withdrawAmountInWei = "0";
const paybackAmountInWei = "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
]
}
];
}
});

View File

@ -0,0 +1,10 @@
import depositAndBorrow from "./deposit-and-borrow"
import paybackAndWithdraw from "./payback-and-withdraw"
import closeTrove from "./close-trove"
export default [
depositAndBorrow,
paybackAndWithdraw,
closeTrove,
]

View File

@ -0,0 +1,275 @@
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: `<p class="text-center">This strategy executes:</p>
<ul>
<li>Payback debt</li>
<li>Withdraw collateral</li>
</ul>`,
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
]
}
];
}
});

View File

@ -148,7 +148,8 @@ export default defineComponent({
}
}, { immediate: true })
onErrorCaptured(() => {
onErrorCaptured((error) => {
console.error(error)
return false
})

View File

@ -16,6 +16,7 @@
"@nuxtjs/composition-api": "^0.27.0",
"@portis/web3": "^4.0.5",
"@tailwindcss/forms": "^0.3.3",
"@tailwindcss/typography": "^0.4.1",
"@vueuse/core": "^5.1.4",
"@walletconnect/web3-provider": "^1.4.1",
"@web3-react/injected-connector": "^6.0.7",
@ -28,6 +29,8 @@
"dsa-connect": "^0.4.4",
"nuxt": "^2.15.7",
"qrcode": "^1.4.4",
"slugify": "^1.6.0",
"tiny-emitter": "^2.1.0",
"v-click-outside": "^3.1.2",
"v-tooltip": "^2.1.3",
"vue-clipboard2": "^0.3.1",

View File

@ -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]"
>
<AaveIcon class="w-8 h-8 text-white" />
<div
class="w-12 h-12 rounded-full flex items-center justify-center bg-[#1874FF]"
>
<AaveIcon class="w-8 h-8 text-white" />
</div>
</div>
<h1 class="ml-4 text-primary-black text-2xl font-semibold">Aave v2</h1>
</div>
<h1 class="ml-4 text-primary-black text-2xl font-semibold">Aave v2</h1>
<ButtonCTAOutlined
class="px-4 h-9 w-[173px]"
@click="$router.push({ hash: 'strategies?protocol=aaveV2' })"
>
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">
@ -166,12 +191,14 @@ import { useStatus } from "~/composables/useStatus";
import { useBigNumber } from "~/composables/useBigNumber";
import CardAave from "~/components/protocols/CardAave.vue";
import AaveIcon from "~/assets/icons/aave.svg?inline";
import ButtonCTAOutlined from "~/components/common/input/ButtonCTAOutlined.vue";
export default defineComponent({
components: {
BackIcon,
CardAave,
AaveIcon
AaveIcon,
ButtonCTAOutlined,
},
setup() {
const {

View File

@ -1,216 +0,0 @@
<template>
<div>
<div>
<nuxt-link
to="/"
class="text-[#C0C5D7] text-lg font-semibold flex items-center"
>
<BackIcon class="w-4 h-4 mr-3" />
Apps
</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="w-12 h-12 rounded-full flex items-center justify-center bg-[#1874FF]"
>
<AaveIcon class="w-8 h-8 text-white" />
</div>
</div>
<h1 class="ml-4 text-primary-black text-2xl font-semibold">Aave v2</h1>
</div>
<div class="mt-10" v-if="position">
<h2 class="text-primary-gray text-lg font-semibold">Overview</h2>
<div
class="px-1 mt-6 grid w-full grid-cols-1 gap-4 sm:grid-cols-3 xl:gap-[18px]"
>
<div class="shadow rounded-lg py-8 px-6 flex">
<div class="flex-1">
<h3 class="text-2xl text-primary-black font-medium">
{{ formatUsd(totalSupply) }}
</h3>
<p class="mt-4 text-primary-gray font-medium">Lend</p>
</div>
<div class="flex items-center">
<svg
width="27"
height="29"
viewBox="0 0 27 29"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.34917 12C0.603705 12 -7.62939e-06 12.6037 -7.62939e-06 13.3492V26.8498C-7.62939e-06 27.5953 0.603705 28.199 1.34917 28.199H25.6496C26.395 28.199 27 27.5953 27 26.8498V13.3492C27 12.6037 26.395 12 25.6496 12H1.34917ZM3.93109 14.6996H23.0676C23.1684 14.9836 23.3312 15.2415 23.5442 15.4545C23.7573 15.6675 24.0152 15.8304 24.2991 15.9311V24.2679C24.0154 24.3688 23.7578 24.5317 23.5449 24.7447C23.3321 24.9577 23.1695 25.2156 23.0689 25.4994H3.93109C3.83052 25.2153 3.66775 24.9574 3.45469 24.7443C3.24163 24.5312 2.98365 24.3685 2.69961 24.2679V15.9311C2.98365 15.8305 3.24163 15.6678 3.45469 15.4547C3.66775 15.2416 3.83052 14.9837 3.93109 14.6996ZM13.4994 16.0501C12.4252 16.0501 11.3951 16.4768 10.6355 17.2363C9.876 17.9958 9.4493 19.026 9.4493 20.1001C9.4493 21.1743 9.876 22.2044 10.6355 22.964C11.3951 23.7235 12.4252 24.1502 13.4994 24.1502C14.5735 24.1502 15.6037 23.7235 16.3632 22.964C17.1227 22.2044 17.5494 21.1743 17.5494 20.1001C17.5494 19.026 17.1227 17.9958 16.3632 17.2363C15.6037 16.4768 14.5735 16.0501 13.4994 16.0501ZM6.74968 18.7497C6.39185 18.7497 6.04868 18.8918 5.79566 19.1448C5.54264 19.3979 5.4005 19.741 5.4005 20.0989C5.4005 20.4567 5.54264 20.7999 5.79566 21.0529C6.04868 21.3059 6.39185 21.448 6.74968 21.448C7.1075 21.448 7.45067 21.3059 7.70369 21.0529C7.95671 20.7999 8.09886 20.4567 8.09886 20.0989C8.09886 19.741 7.95671 19.3979 7.70369 19.1448C7.45067 18.8918 7.1075 18.7497 6.74968 18.7497ZM20.2491 18.7497C19.8911 18.7497 19.5477 18.8919 19.2946 19.145C19.0415 19.3982 18.8992 19.7415 18.8992 20.0995C18.8992 20.4575 19.0415 20.8008 19.2946 21.054C19.5477 21.3071 19.8911 21.4493 20.2491 21.4493C20.607 21.4493 20.9504 21.3071 21.2035 21.054C21.4566 20.8008 21.5989 20.4575 21.5989 20.0995C21.5989 19.7415 21.4566 19.3982 21.2035 19.145C20.9504 18.8919 20.607 18.7497 20.2491 18.7497Z"
fill="#209B9F"
/>
<path
d="M9.26922 5.85639L12.8282 9.41665C12.9101 9.50186 13.0083 9.56957 13.1171 9.61569C13.2259 9.66181 13.3429 9.68538 13.461 9.68497C13.5793 9.68521 13.6963 9.66144 13.8051 9.61511C13.9139 9.56877 14.0121 9.50082 14.0939 9.41538L17.6529 5.85639C17.7382 5.77424 17.8061 5.67566 17.8524 5.5666C17.8986 5.45754 17.9224 5.34024 17.9221 5.22176C17.9219 5.10329 17.8977 4.98609 17.851 4.87721C17.8043 4.76833 17.736 4.67003 17.6503 4.58821C17.4829 4.42155 17.2575 4.32628 17.0213 4.32243C16.7834 4.32597 16.5564 4.42224 16.3885 4.59075L14.3634 6.61578V1.90125C14.3651 1.78233 14.343 1.66428 14.2982 1.55409C14.2534 1.44391 14.187 1.34383 14.1029 1.25979C14.0187 1.17575 13.9185 1.10946 13.8083 1.06486C13.698 1.02025 13.58 0.998235 13.461 1.00011C13.3422 0.998406 13.2243 1.02055 13.1142 1.06523C13.0041 1.10992 12.9041 1.17623 12.82 1.26025C12.736 1.34427 12.6697 1.44429 12.625 1.55439C12.5803 1.66449 12.5582 1.78244 12.5599 1.90125V6.61704L10.5349 4.59201C10.3672 4.42305 10.1401 4.3263 9.90204 4.32243C9.66419 4.32597 9.43713 4.42224 9.26922 4.59075C9.18408 4.67269 9.11636 4.77098 9.0701 4.87971C9.02384 4.98845 9 5.1054 9 5.22357C9 5.34173 9.02384 5.45869 9.0701 5.56742C9.11636 5.67616 9.18408 5.77444 9.26922 5.85639Z"
fill="#209B9F"
stroke="#209B9F"
stroke-width="0.3"
/>
</svg>
</div>
</div>
<div class="shadow rounded-lg py-8 px-6 flex">
<div class="flex-1">
<h3 class="text-2xl text-primary-black font-medium">
{{ formatUsd(totalBorrow) }}
</h3>
<p class="mt-4 text-primary-gray font-medium">Borrowed</p>
</div>
<div class="flex items-center">
<svg
width="27"
height="28"
viewBox="0 0 27 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.34511 11C0.601893 11 0 11.6019 0 12.3451V25.8051C0 26.5483 0.601893 27.1502 1.34511 27.1502H25.5722C26.3155 27.1502 26.9186 26.5483 26.9186 25.8051V12.3451C26.9186 11.6019 26.3155 11 25.5722 11H1.34511ZM3.91924 13.6915H22.9981C23.0985 13.9746 23.2609 14.2317 23.4733 14.4441C23.6857 14.6565 23.9428 14.8188 24.2259 14.9192V23.2309C23.943 23.3315 23.6861 23.4939 23.474 23.7063C23.2618 23.9187 23.0997 24.1757 22.9994 24.4587H3.91924C3.81898 24.1755 3.6567 23.9183 3.44428 23.7059C3.23186 23.4935 2.97466 23.3312 2.69148 23.2309V14.9192C2.97466 14.819 3.23186 14.6567 3.44428 14.4443C3.6567 14.2319 3.81898 13.9747 3.91924 13.6915ZM13.4587 15.0379C12.3878 15.0379 11.3607 15.4633 10.6035 16.2205C9.84624 16.9778 9.42082 18.0048 9.42082 19.0757C9.42082 20.1466 9.84624 21.1737 10.6035 21.9309C11.3607 22.6882 12.3878 23.1136 13.4587 23.1136C14.5296 23.1136 15.5566 22.6882 16.3139 21.9309C17.0711 21.1737 17.4965 20.1466 17.4965 19.0757C17.4965 18.0048 17.0711 16.9778 16.3139 16.2205C15.5566 15.4633 14.5296 15.0379 13.4587 15.0379ZM6.72934 17.7293C6.37259 17.7293 6.03046 17.8711 5.7782 18.1233C5.52594 18.3756 5.38423 18.7177 5.38423 19.0744C5.38423 19.4312 5.52594 19.7733 5.7782 20.0256C6.03046 20.2778 6.37259 20.4196 6.72934 20.4196C7.08608 20.4196 7.42822 20.2778 7.68048 20.0256C7.93273 19.7733 8.07445 19.4312 8.07445 19.0744C8.07445 18.7177 7.93273 18.3756 7.68048 18.1233C7.42822 17.8711 7.08608 17.7293 6.72934 17.7293ZM20.188 17.7293C19.8311 17.7293 19.4888 17.8711 19.2364 18.1235C18.9841 18.3759 18.8423 18.7182 18.8423 19.0751C18.8423 19.432 18.9841 19.7743 19.2364 20.0267C19.4888 20.279 19.8311 20.4208 20.188 20.4208C20.5449 20.4208 20.8872 20.279 21.1396 20.0267C21.392 19.7743 21.5338 19.432 21.5338 19.0751C21.5338 18.7182 21.392 18.3759 21.1396 18.1235C20.8872 17.8711 20.5449 17.7293 20.188 17.7293Z"
fill="#CA8700"
/>
<path
d="M17.5573 3.7854H10.6173C10.0946 3.7854 9.6709 4.20911 9.6709 4.73177C9.6709 5.25444 10.0946 5.67815 10.6173 5.67815H17.5573C18.08 5.67815 18.5037 5.25444 18.5037 4.73177C18.5037 4.20911 18.08 3.7854 17.5573 3.7854Z"
fill="#CA8700"
/>
<path
d="M17.5573 0H10.6173C10.0946 0 9.6709 0.423705 9.6709 0.946372C9.6709 1.46904 10.0946 1.89274 10.6173 1.89274H17.5573C18.08 1.89274 18.5037 1.46904 18.5037 0.946372C18.5037 0.423705 18.08 0 17.5573 0Z"
fill="#CA8700"
/>
</svg>
</div>
</div>
<div class="shadow rounded-lg py-8 px-6 flex">
<div class="flex-1">
<div class="flex justify-between items-center">
<h3 class="text-2xl text-primary-black font-medium">
{{ formatPercent(status) }}
</h3>
<Badge class="w-18 xxl:w-23" :color="color">{{ text }}</Badge>
</div>
<div class="mt-4 flex justify-between items-center text-primary-gray font-medium">
<div
class="flex items-center whitespace-no-wrap"
>
<div>D/C (%)</div>
<div class="ml-2"><Info text="Debt/Collateral ratio" /></div>
</div>
<span >Max - {{ formatPercent(position.maxLiquidation) }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="mt-[60px]">
<div
class="w-full flex flex-col mt-6 sm:flex-row sm:items-center sm:justify-between xl:mt-4"
>
<h2 class="text-primary-gray text-lg font-semibold">Your Positions</h2>
<div class="mt-4 sm:mt-0 sm:mr-1">
<SearchInput
v-model.trim="search"
dense
class="w-[200px]"
placeholder="Search positions"
/>
</div>
</div>
<div
class="mt-3 grid w-full grid-cols-1 gap-4 sm:grid-cols-2 xxl:gap-6 min-w-max-content px-1"
>
<div v-for="item in filteredPositions" :key="item.key">
<card-aave
:token-key="item.key"
:supply="item.supply"
:supply-usd="item.supplyUsd"
:supply-rate="item.supplyRate"
:borrow="item.borrow"
:borrow-usd="item.borrowUsd"
:borrow-rate="item.borrowRate"
:type="item.type"
:supply-reward-rate="item.supplyRewardRate"
:borrow-reward-rate="item.borrowRewardRate"
reward-token-name="MATIC"
reward-currency="matic"
:cf="item.cf"
:ll="item.ll"
:borrow-enabled="item.borrowEnabled"
:price-in-usd="item.priceInUsd"
/>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from "@nuxtjs/composition-api";
import BackIcon from "~/assets/icons/back.svg?inline";
import { useAaveV2Position } from "~/composables/protocols/useAaveV2Position";
import { useFormatting } from "~/composables/useFormatting";
import { useSearchFilter } from "~/composables/useSearchFilter";
import { useStatus } from "~/composables/useStatus";
import { useBigNumber } from "~/composables/useBigNumber";
import CardAave from "~/components/protocols/CardAave.vue";
import AaveIcon from '~/assets/icons/aave.svg?inline'
export default defineComponent({
components: {
BackIcon,
CardAave,
AaveIcon,
},
setup() {
const {
position,
displayPositions,
totalSupply,
totalBorrow,
status,
liquidation
} = useAaveV2Position();
const { div } = useBigNumber();
const statusLiquidationRatio = computed(() =>
div(status.value, liquidation.value).toFixed()
);
const { color, text } = useStatus(statusLiquidationRatio);
const { formatUsd, formatPercent } = useFormatting();
const { filtered: filteredPositions, search } = useSearchFilter(
displayPositions,
"key",
"type"
);
return {
position,
filteredPositions,
search,
totalSupply,
totalBorrow,
status,
formatUsd,
formatPercent,
color,
text
};
}
});
</script>

View File

@ -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 {

View File

@ -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]"
>
<LiquityIcon class="w-8 h-8 text-white" />
<div
class="w-12 h-12 rounded-full flex items-center justify-center bg-[#1874FF]"
>
<LiquityIcon class="w-8 h-8 text-white" />
</div>
</div>
<h1 class="ml-4 text-primary-black text-2xl font-semibold">Liquity</h1>
</div>
<h1 class="ml-4 text-primary-black text-2xl font-semibold">Liquity</h1>
<ButtonCTAOutlined
class="px-4 h-9 w-[173px]"
@click="$router.push({ hash: 'strategies?protocol=liquity' })"
>
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">
@ -150,6 +175,7 @@ import { useFormatting } from "~/composables/useFormatting";
import { useStatus } from "~/composables/useStatus";
import { useBigNumber } from "~/composables/useBigNumber";
import CardLiquityTrove from "~/components/protocols/liquity/CardLiquityTrove.vue";
import ButtonCTAOutlined from "~/components/common/input/ButtonCTAOutlined.vue";
export default defineComponent({
components: {
@ -159,7 +185,8 @@ export default defineComponent({
SVGAdd,
SVGBalance,
SVGPercent,
CardLiquityTrove
CardLiquityTrove,
ButtonCTAOutlined,
},
setup() {
const router = useRouter();

View File

@ -120,5 +120,6 @@ module.exports = {
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
}

View File

@ -1777,6 +1777,16 @@
dependencies:
mini-svg-data-uri "^1.2.3"
"@tailwindcss/typography@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.4.1.tgz#51ddbceea6a0ee9902c649dbe58871c81a831212"
integrity sha512-ovPPLUhs7zAIJfr0y1dbGlyCuPhpuv/jpBoFgqAc658DWGGrOBWBMpAWLw2KlzbNeVk4YBJMzue1ekvIbdw6XA==
dependencies:
lodash.castarray "^4.4.0"
lodash.isplainobject "^4.0.6"
lodash.merge "^4.6.2"
lodash.uniq "^4.5.0"
"@types/anymatch@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-3.0.0.tgz#c95ff14401dbb2869913afac3935af4ad0d37f1a"
@ -8343,11 +8353,21 @@ lodash._reinterpolate@^3.0.0:
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
lodash.castarray@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115"
integrity sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU=
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.kebabcase@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
@ -8358,6 +8378,11 @@ lodash.memoize@^4.1.2:
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash.template@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
@ -11564,6 +11589,11 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
slugify@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.0.tgz#6bdf8ed01dabfdc46425b67e3320b698832ff893"
integrity sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang==
snapdragon-node@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@ -12216,7 +12246,7 @@ timsort@^0.3.0:
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
tiny-emitter@^2.0.0:
tiny-emitter@^2.0.0, tiny-emitter@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==