mirror of
https://github.com/Instadapp/assembly.git
synced 2024-07-29 22:37:06 +00:00
Liquity - Open Trove
This commit is contained in:
parent
5d213e4ad7
commit
645bf1ecbf
29
components/common/input/ButtonDashed.vue
Normal file
29
components/common/input/ButtonDashed.vue
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<button
|
||||
class="flex items-center justify-center px-4 font-semibold leading-none transition-colors duration-150 border border-dashed cursor-pointer focus:outline-none rounded-[4px]"
|
||||
:class="{
|
||||
'w-full': fullWidth,
|
||||
'text-ocean-blue-pure dark:text-white border-ocean-blue-pure dark:border-grey-pure bg-ocean-blue-light border-opacity-25 bg-opacity-38 dark:bg-opacity-17 dark:hover:bg-opacity-25 hover:bg-opacity-75 active:bg-opacity-38 dark:active:bg-opacity-38 ':
|
||||
color === 'ocean-blue',
|
||||
}"
|
||||
:style="`height: ${height}`"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from '@nuxtjs/composition-api'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
color: { type: String, default: 'grey' },
|
||||
height: { type: String, default: '60px' },
|
||||
fullWidth: { type: Boolean, default: false },
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
155
components/common/input/InputAmount.vue
Normal file
155
components/common/input/InputAmount.vue
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
<template>
|
||||
<div class="flex flex-col flex-shrink-0 w-full">
|
||||
<div class="relative transition-all duration-150 rounded-sm" :class="{ 'shadow-sm': !disabled }">
|
||||
<input
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
inputmode="decimal"
|
||||
:value="value"
|
||||
v-bind="$attrs"
|
||||
class="w-full pl-4 rounded-[6px] transition-colors duration-75 ease-out border border-grey-dark border-opacity-[0.15]"
|
||||
:class="{
|
||||
'pr-12': !!symbol,
|
||||
'pr-8': !symbol,
|
||||
'rounded-b-none': badgeValue != null,
|
||||
'text-sm': size === 'lg',
|
||||
'bg-gray-50': backgroundColor === 'grey',
|
||||
}"
|
||||
:aria-invalid="(touched || showError) && !!error"
|
||||
:aria-describedby="id"
|
||||
:disabled="disabled"
|
||||
v-on="inputListeners"
|
||||
/>
|
||||
|
||||
<div v-if="!!tokenKeys && !!tokenKeys.length" class="absolute inset-y-0 right-0">
|
||||
<Dropdown class="h-full">
|
||||
<template #trigger="{ toggle }">
|
||||
<button
|
||||
v-if="!!symbol"
|
||||
class="flex items-center h-full px-1 font-semibold whitespace-no-wrap text-ocean-blue-pure text-11 dark:text-light"
|
||||
@click="toggle"
|
||||
>
|
||||
<span class="mr-1">{{ symbol }}</span>
|
||||
<Icon name="chevron-down" class="h-3" />
|
||||
</button>
|
||||
</template>
|
||||
<template #menu="{ close }">
|
||||
<DropdownMenu>
|
||||
<button
|
||||
v-for="(tokenKey, index) in tokenKeys"
|
||||
:key="index"
|
||||
class="flex items-center w-full px-4 py-2 font-semibold text-ocean-blue-pure text-11 dark:text-light hover:bg-opacity-50 focus:bg-opacity-50 hover:bg-grey-light dark:hover:bg-dark-300 dark:focus:bg-dark-300 focus:outline-none focus:bg-grey-light"
|
||||
@click="select(tokenKey, close)"
|
||||
>
|
||||
{{ getSymbol(tokenKey) }}
|
||||
</button>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="!!getSymbol(tokenKey)"
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-4 pointer-events-none select-none"
|
||||
>
|
||||
<span
|
||||
class="uppercase"
|
||||
:class="{
|
||||
'text-ocean-blue-pure dark:text-light': !disabled,
|
||||
'text-grey-pure': disabled,
|
||||
'text-11': size === 'md',
|
||||
'text-14 font-medium': size === 'lg',
|
||||
}"
|
||||
>
|
||||
{{ getSymbol(tokenKey) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Badge
|
||||
v-if="badgeValue != null"
|
||||
class="h-6 rounded-t-none rounded-b-sm bg-opacity-38 bg-grey-pure text-opacity-38"
|
||||
large
|
||||
>
|
||||
<SVGSpinner v-if="badgeLoading" class="animate-spin-loading" style="width: auto; height: 1em" />
|
||||
<span v-else>{{ badgeValue }}</span>
|
||||
</Badge>
|
||||
|
||||
<div class="h-4">
|
||||
<transition
|
||||
enter-active-class="duration-75 ease-out"
|
||||
enter-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="duration-75 ease-in"
|
||||
leave-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<p v-if="(touched || showError) && !!error" :id="id" class="mt-1 text-red-600 text-12">
|
||||
{{ error }}
|
||||
</p>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, watch, ref, toRef } from '@nuxtjs/composition-api'
|
||||
import { useInputListeners } from '@/composables/useInputListeners'
|
||||
import SVGSpinner from '@/assets/img/icons/spinner.svg'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { useToken } from '~/composables/useToken'
|
||||
import { useFormatting } from '~/composables/useFormatting'
|
||||
import { usePattern } from '~/composables/usePattern'
|
||||
import Dropdown from './Dropdown.vue'
|
||||
import DropdownMenu from '~/components/protocols/DropdownMenu.vue'
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
components: {
|
||||
SVGSpinner,
|
||||
Dropdown,
|
||||
DropdownMenu,
|
||||
},
|
||||
props: {
|
||||
value: { type: String, default: '' },
|
||||
badgeValue: { type: String, default: null },
|
||||
badgeLoading: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
showError: { type: Boolean, default: false },
|
||||
error: { type: String, default: null },
|
||||
tokenKey: { type: String, required: true },
|
||||
tokenKeys: { type: Array, default: () => null },
|
||||
size: { type: String, default: 'md' },
|
||||
backgroundColor: { type: String, default: 'white' },
|
||||
},
|
||||
|
||||
setup(props, context) {
|
||||
const id = uuid()
|
||||
const { symbol, getTokenByKey } = useToken(null, toRef(props, 'tokenKey'))
|
||||
const { formatDecimal } = useFormatting()
|
||||
const { amountPattern } = usePattern()
|
||||
|
||||
const numericFilter = (value) => amountPattern.test(value)
|
||||
const { inputListeners } = useInputListeners(props, context, numericFilter)
|
||||
|
||||
const touched = ref(false)
|
||||
const stopTouchedWatcher = watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
touched.value = true
|
||||
stopTouchedWatcher()
|
||||
}
|
||||
)
|
||||
|
||||
function getSymbol(key) {
|
||||
return getTokenByKey(key)?.symbol || key
|
||||
}
|
||||
|
||||
function select(tokenKey, cb) {
|
||||
context.emit('tokenKeyChanged', tokenKey)
|
||||
cb()
|
||||
}
|
||||
|
||||
return { inputListeners, touched, symbol, formatDecimal, id, getSymbol, select }
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
<template>
|
||||
<SidebarContextRootContainer>
|
||||
<template #title>Open Trove</template>
|
||||
|
||||
<SidebarSectionValueWithIcon label="Collateral Balance" center>
|
||||
<template #icon
|
||||
><IconCurrency
|
||||
:currency="collateralToken.key"
|
||||
class="w-16 h-16"
|
||||
noHeight
|
||||
/></template>
|
||||
<template #value
|
||||
>{{ formatNumber(balance) }} {{ collateralToken.symbol }}</template
|
||||
>
|
||||
</SidebarSectionValueWithIcon>
|
||||
|
||||
<div class="bg-[#C5CCE1] bg-opacity-[0.15] mt-10 p-8">
|
||||
<input-amount
|
||||
v-model="collateralAmount"
|
||||
:token-key="collateralToken.key"
|
||||
:disabled="pending"
|
||||
class="mt-4"
|
||||
placeholder="Collateral amount"
|
||||
:error="errors.collateralAmount.message"
|
||||
/>
|
||||
|
||||
<input-amount
|
||||
v-model="debtAmount"
|
||||
:token-key="debtToken.key"
|
||||
:disabled="pending"
|
||||
class="mt-4"
|
||||
placeholder="Borrow amount"
|
||||
:error="errors.debtAmount.message"
|
||||
/>
|
||||
|
||||
<ValueDisplay
|
||||
label="Liquidation Reserve"
|
||||
tooltip="An amount set aside to cover the liquidator’s gas costs if your Trove needs to be liquidated. The amount increases your debt and is refunded if you close your Trove by fully paying off its net debt."
|
||||
class="mt-4"
|
||||
>
|
||||
{{ liquidationReserve }} LUSD
|
||||
</ValueDisplay>
|
||||
<ValueDisplay
|
||||
label="Borrow Fee"
|
||||
tooltip="This amount is deducted from the borrowed amount as a one-time fee. There are no recurring fees for borrowing, which is thus interest-free."
|
||||
class="mt-4"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
{{ formatDecimal(borrowFeeAmount, 2) }} {{ debtToken.symbol }}
|
||||
</div>
|
||||
<div class="ml-1 text-sm">({{ formatPercent(borrowFee) }})</div>
|
||||
</div>
|
||||
</ValueDisplay>
|
||||
<ValueDisplay
|
||||
label="Total debt"
|
||||
tooltip="The total amount of LUSD your Trove will hold."
|
||||
class="mt-4"
|
||||
>
|
||||
<div>{{ formatDecimal(totalDebt, 2) }} {{ debtToken.symbol }}</div>
|
||||
</ValueDisplay>
|
||||
|
||||
<SidebarContextHeading class="mt-5">
|
||||
Projected Debt Position
|
||||
</SidebarContextHeading>
|
||||
|
||||
<SidebarSectionStatus
|
||||
class="mt-8"
|
||||
:liquidation="liquidation"
|
||||
:status="status"
|
||||
/>
|
||||
|
||||
<SidebarSectionValueWithIcon class="mt-8" label="Liquidation Price (ETH)">
|
||||
<template #value>
|
||||
{{ formatUsdMax(liquidationPrice, liquidationMaxPrice) }}
|
||||
<span class="text-primary-gray"
|
||||
>/ {{ formatUsd(liquidationMaxPrice) }}</span
|
||||
>
|
||||
</template>
|
||||
</SidebarSectionValueWithIcon>
|
||||
|
||||
<div class="flex flex-shrink-0 mt-10">
|
||||
<ButtonCTA
|
||||
class="w-full"
|
||||
:disabled="!isValid || pending"
|
||||
:loading="pending"
|
||||
@click="cast"
|
||||
>
|
||||
Open Trove
|
||||
</ButtonCTA>
|
||||
</div>
|
||||
|
||||
<ValidationErrors :error-messages="errorMessages" class="mt-6" />
|
||||
</div>
|
||||
</SidebarContextRootContainer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, defineComponent, ref } from '@nuxtjs/composition-api'
|
||||
import InputNumeric from '~/components/common/input/InputNumeric.vue'
|
||||
import { useBalances } from '~/composables/useBalances'
|
||||
import { useNotification } from '~/composables/useNotification'
|
||||
import { useBigNumber } from '~/composables/useBigNumber'
|
||||
import { useFormatting } from '~/composables/useFormatting'
|
||||
import { useValidators } from '~/composables/useValidators'
|
||||
import { useValidation } from '~/composables/useValidation'
|
||||
import { useToken } from '~/composables/useToken'
|
||||
import { useParsing } from '~/composables/useParsing'
|
||||
import { useMaxAmountActive } from '~/composables/useMaxAmountActive'
|
||||
import { useWeb3 } from '~/composables/useWeb3'
|
||||
import atokens from '~/constant/atokens'
|
||||
import ToggleButton from '~/components/common/input/ToggleButton.vue'
|
||||
import { useDSA } from '~/composables/useDSA'
|
||||
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
|
||||
import Button from '~/components/Button.vue'
|
||||
import { useSidebar } from '~/composables/useSidebar'
|
||||
import { useLiquityPosition } from '~/composables/protocols/useLiquityPosition'
|
||||
import InputAmount from '~/components/common/input/InputAmount.vue'
|
||||
import ValueDisplay from '../components/ValueDisplay.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: { InputNumeric, ToggleButton, ButtonCTA, Button, InputAmount, ValueDisplay },
|
||||
setup() {
|
||||
const { account } = useWeb3()
|
||||
const { close } = useSidebar()
|
||||
const { formatPercent, formatNumber, formatDecimal, formatUsdMax, formatUsd } = useFormatting()
|
||||
const { plus, times, isZero } = useBigNumber()
|
||||
const { parseSafeFloat } = useParsing()
|
||||
const { getBalanceByKey } = useBalances()
|
||||
const { valInt } = useToken()
|
||||
const { showPendingTransaction, showWarning } = useNotification()
|
||||
const { dsa } = useDSA()
|
||||
|
||||
const {
|
||||
collateralToken,
|
||||
debtToken,
|
||||
liquidation,
|
||||
liquidationReserve,
|
||||
liquidationMaxPrice,
|
||||
borrowFee,
|
||||
maxFeePercentageInWei,
|
||||
getTrovePositionHints,
|
||||
} = useLiquityPosition()
|
||||
|
||||
const collateralAmount = ref('')
|
||||
const debtAmount = ref('')
|
||||
|
||||
|
||||
const balance = computed(() => getBalanceByKey(collateralToken.value.key))
|
||||
|
||||
const collateralAmountParsed = computed(() => parseSafeFloat(collateralAmount.value))
|
||||
const debtAmountParsed = computed(() => parseSafeFloat(debtAmount.value))
|
||||
|
||||
const borrowFeeAmount = computed(() => times(debtAmountParsed.value, borrowFee.value).toFixed())
|
||||
const totalDebt = computed(() => {
|
||||
if (isZero(debtAmountParsed.value)) return '0'
|
||||
return plus(plus(debtAmountParsed.value, borrowFeeAmount.value), liquidationReserve.value).toFixed()
|
||||
})
|
||||
|
||||
const { liquidationPrice, status } = useLiquityPosition(collateralAmountParsed, totalDebt)
|
||||
|
||||
const { validateAmount, validateLiquidation, validateIsLoggedIn, validateLiquityDebt } = useValidators()
|
||||
|
||||
const errors = computed(() => {
|
||||
const hasCollateralAmountValue = !isZero(collateralAmount.value)
|
||||
const hasDebtAmountValue = !isZero(debtAmount.value)
|
||||
|
||||
return {
|
||||
collateralAmount: {
|
||||
message: validateAmount(collateralAmountParsed.value, balance.value),
|
||||
show: hasCollateralAmountValue,
|
||||
},
|
||||
debtAmount: { message: validateAmount(debtAmountParsed.value), show: hasDebtAmountValue },
|
||||
minDebt: { message: validateLiquityDebt(totalDebt.value, undefined, '0'), show: hasDebtAmountValue },
|
||||
liquidation: {
|
||||
message: validateLiquidation(status.value, liquidation.value),
|
||||
show: hasCollateralAmountValue && hasDebtAmountValue,
|
||||
},
|
||||
auth: { message: validateIsLoggedIn(!!account.value), show: true },
|
||||
}
|
||||
})
|
||||
const { errorMessages, isValid } = useValidation(errors)
|
||||
|
||||
const pending = ref(false)
|
||||
|
||||
async function cast() {
|
||||
pending.value = true
|
||||
try {
|
||||
const depositAmountInWei = valInt(collateralAmountParsed.value, collateralToken.value.decimals)
|
||||
const borrowAmountInWei = valInt(debtAmountParsed.value, debtToken.value.decimals)
|
||||
const totalBorrowAmountInWei = valInt(totalDebt.value, debtToken.value.decimals)
|
||||
|
||||
const { upperHint, lowerHint } = await getTrovePositionHints(depositAmountInWei, totalBorrowAmountInWei)
|
||||
|
||||
const spells = dsa.value.Spell()
|
||||
|
||||
const getIds = [0, 0]
|
||||
const setIds = [0, 0]
|
||||
|
||||
spells.add({
|
||||
connector: 'LIQUITY-A',
|
||||
method: 'open',
|
||||
args: [
|
||||
depositAmountInWei,
|
||||
maxFeePercentageInWei.value,
|
||||
borrowAmountInWei,
|
||||
upperHint,
|
||||
lowerHint,
|
||||
getIds,
|
||||
setIds,
|
||||
],
|
||||
})
|
||||
|
||||
const txHash = await dsa.value.cast({
|
||||
spells,
|
||||
from: account.value,
|
||||
})
|
||||
|
||||
showPendingTransaction(txHash)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
showWarning(error.message)
|
||||
}
|
||||
|
||||
pending.value = false
|
||||
|
||||
close()
|
||||
}
|
||||
|
||||
return {
|
||||
formatPercent, formatNumber, formatDecimal, formatUsdMax, formatUsd,
|
||||
balance,
|
||||
liquidationPrice,
|
||||
liquidationMaxPrice,
|
||||
status,
|
||||
liquidation,
|
||||
totalDebt,
|
||||
liquidationReserve,
|
||||
collateralToken,
|
||||
debtToken,
|
||||
borrowFee,
|
||||
borrowFeeAmount,
|
||||
collateralAmount,
|
||||
debtAmount,
|
||||
errors,
|
||||
errorMessages,
|
||||
isValid,
|
||||
pending,
|
||||
cast,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
300
composables/protocols/useLiquityPosition.ts
Normal file
300
composables/protocols/useLiquityPosition.ts
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
import { computed, Ref, ref, watch } from "@nuxtjs/composition-api";
|
||||
import { useBalances } from "../useBalances";
|
||||
import { useBigNumber } from "../useBigNumber";
|
||||
import { useToken } from "../useToken";
|
||||
import { useWeb3 } from "~/composables/useWeb3";
|
||||
import { AbiItem } from "web3-utils";
|
||||
import BigNumber from "bignumber.js";
|
||||
BigNumber.config({ POW_PRECISION: 200 });
|
||||
import abis from "~/constant/abis";
|
||||
import addresses from "~/constant/addresses";
|
||||
import { useDSA } from "../useDSA";
|
||||
|
||||
const trove = ref<any>({
|
||||
collateral: "0",
|
||||
debt: "0",
|
||||
stabilityAmount: "0",
|
||||
stabilityEthGain: "0",
|
||||
stabilityLqtyGain: "0",
|
||||
stakeAmount: "0",
|
||||
stakeEthGain: "0",
|
||||
stakeLqtyGain: "0",
|
||||
price: "0",
|
||||
ratio: "0",
|
||||
tokenKey: "eth",
|
||||
token: "ETH",
|
||||
liquidation: "0"
|
||||
});
|
||||
|
||||
const troveTypes = ref([
|
||||
{
|
||||
totalCollateral: "0",
|
||||
price: "0",
|
||||
totalRatio: "0",
|
||||
tokenKey: "eth",
|
||||
token: "ETH",
|
||||
isRecoveryMode: false,
|
||||
borrowFee: "0",
|
||||
liquidation: "0",
|
||||
minDebt: "2000",
|
||||
liquidationReserve: "200"
|
||||
}
|
||||
]);
|
||||
|
||||
const troveOverallDetails = computed(() =>
|
||||
troveTypes.value.find(t => t.tokenKey === trove.value.tokenKey)
|
||||
);
|
||||
|
||||
export function useLiquityPosition(
|
||||
collateralAmountRef: Ref = null,
|
||||
debtAmountRef: Ref = null
|
||||
) {
|
||||
const { web3 } = useWeb3();
|
||||
const { activeAccount } = useDSA();
|
||||
|
||||
const { isZero, times, div, max, minus, plus } = useBigNumber();
|
||||
const { getTokenByKey, valInt } = useToken();
|
||||
const { prices } = useBalances();
|
||||
|
||||
const collateralToken = computed(() => getTokenByKey("eth"));
|
||||
const debtToken = computed(() => getTokenByKey("lusd"));
|
||||
const stakingToken = computed(() => getTokenByKey("lqty"));
|
||||
|
||||
const collateral = computed(() => trove.value.collateral);
|
||||
const collateralInWei = computed(() =>
|
||||
valInt(collateral.value, collateralToken.value?.decimals)
|
||||
);
|
||||
const priceInUsd = computed(() => trove.value.price);
|
||||
const ratio = computed(() => trove.value.ratio);
|
||||
const debt = computed(() => trove.value.debt);
|
||||
const collateralUsd = computed(() =>
|
||||
times(collateral.value, priceInUsd.value).toFixed()
|
||||
);
|
||||
const stabilityAmount = computed(() => trove.value.stabilityAmount);
|
||||
const debtUsd = computed(() => times(debt.value, "1").toFixed());
|
||||
const stabilityAmountUsd = computed(() =>
|
||||
times(stabilityAmount.value, "1").toFixed()
|
||||
);
|
||||
|
||||
const stakingTokenPrice = computed(() =>
|
||||
stakingToken.value ? prices.mainnet[stakingToken.value.address] : "0"
|
||||
);
|
||||
const stakeAmount = computed(() => trove.value.stakeAmount);
|
||||
const stakeEthGain = computed(() => trove.value.stakeEthGain);
|
||||
const stakeLqtyGain = computed(() => trove.value.stakeLqtyGain);
|
||||
const stakingAmountUsd = computed(() =>
|
||||
times(stakeAmount.value, stakingTokenPrice.value).toFixed()
|
||||
);
|
||||
const netValue = computed(() =>
|
||||
plus(
|
||||
plus(minus(collateralUsd.value, debtUsd.value), stabilityAmountUsd.value),
|
||||
stakingAmountUsd.value
|
||||
).toFixed()
|
||||
);
|
||||
|
||||
const borrowFee = computed(() => troveOverallDetails.value.borrowFee);
|
||||
const maxFeePercentageInWei = computed(() =>
|
||||
times(times(borrowFee.value, "100"), "1e18").toFixed()
|
||||
);
|
||||
const liquidation = computed(() => troveOverallDetails.value.liquidation);
|
||||
|
||||
const status = computed(() => {
|
||||
if (!collateralAmountRef || !debtAmountRef) return ratio.value;
|
||||
return isZero(collateralAmountRef.value) && !isZero(debtAmountRef.value)
|
||||
? "1.1"
|
||||
: div(
|
||||
debtAmountRef.value,
|
||||
times(collateralAmountRef.value, priceInUsd.value)
|
||||
).toFixed();
|
||||
});
|
||||
|
||||
const liquidationPrice = computed(() => {
|
||||
if (!collateralAmountRef || !debtAmountRef) {
|
||||
return max(
|
||||
div(div(debt.value, collateral.value), liquidation.value),
|
||||
"0"
|
||||
).toFixed();
|
||||
}
|
||||
return isZero(collateralAmountRef.value) && !isZero(debtAmountRef.value)
|
||||
? times(priceInUsd.value, "1.1").toFixed()
|
||||
: max(
|
||||
div(
|
||||
div(debtAmountRef.value, collateralAmountRef.value),
|
||||
liquidation.value
|
||||
),
|
||||
"0"
|
||||
).toFixed();
|
||||
});
|
||||
|
||||
const troveOpened = computed(
|
||||
() => !isZero(collateral.value) && !isZero(debt.value)
|
||||
);
|
||||
|
||||
const minDebt = computed(() => troveOverallDetails.value.minDebt);
|
||||
const liquidationReserve = computed(
|
||||
() => troveOverallDetails.value.liquidationReserve
|
||||
);
|
||||
|
||||
const fetchPosition = async () => {
|
||||
if (!web3.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
troveTypes.value = await getTroveTypes(web3.value);
|
||||
|
||||
if (!activeAccount.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
trove.value = await getTrove(activeAccount.value.address, web3.value);
|
||||
};
|
||||
|
||||
async function getTrovePositionHints(collateralInWei, debtInWei) {
|
||||
try {
|
||||
const liquityInstance = new web3.value.eth.Contract(
|
||||
abis.resolver.liquity as AbiItem[],
|
||||
addresses.mainnet.resolver.liquity
|
||||
);
|
||||
|
||||
const {
|
||||
upperHint,
|
||||
lowerHint
|
||||
} = await liquityInstance.methods
|
||||
.getTrovePositionHints(
|
||||
collateralInWei.toString(),
|
||||
debtInWei.toString(),
|
||||
0,
|
||||
0
|
||||
)
|
||||
.call();
|
||||
|
||||
return {
|
||||
upperHint,
|
||||
lowerHint
|
||||
};
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
web3,
|
||||
async val => {
|
||||
if (val) {
|
||||
fetchPosition();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
activeAccount,
|
||||
async val => {
|
||||
if (val) {
|
||||
fetchPosition();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return {
|
||||
troveOpened,
|
||||
netValue,
|
||||
borrowFee,
|
||||
status,
|
||||
liquidation,
|
||||
liquidationPrice,
|
||||
liquidationMaxPrice: priceInUsd,
|
||||
collateralToken,
|
||||
debtToken,
|
||||
minDebt,
|
||||
liquidationReserve,
|
||||
maxFeePercentageInWei,
|
||||
getTrovePositionHints
|
||||
};
|
||||
}
|
||||
|
||||
async function getTrove(user, web3) {
|
||||
const resolveABI = abis.resolver.liquity;
|
||||
const resolveAddr = addresses.mainnet.resolver.liquity;
|
||||
|
||||
const liquityInstance = new web3.eth.Contract(
|
||||
resolveABI as AbiItem[],
|
||||
resolveAddr
|
||||
);
|
||||
|
||||
try {
|
||||
const {
|
||||
trove,
|
||||
stake,
|
||||
stability
|
||||
} = await liquityInstance.methods.getPosition(user).call();
|
||||
const { collateral, debt, icr, price } = trove;
|
||||
const ratio =
|
||||
icr ===
|
||||
"115792089237316195423570985008687907853269984665640564039457584007913129639935"
|
||||
? "0"
|
||||
: new BigNumber(1e18).dividedBy(icr).toString();
|
||||
|
||||
return {
|
||||
collateral: new BigNumber(collateral).dividedBy(1e18).toString(),
|
||||
debt: new BigNumber(debt).dividedBy(1e18).toString(),
|
||||
stabilityAmount: new BigNumber(stability.deposit)
|
||||
.dividedBy(1e18)
|
||||
.toString(),
|
||||
stabilityEthGain: new BigNumber(stability.ethGain)
|
||||
.dividedBy(1e18)
|
||||
.toString(),
|
||||
stabilityLqtyGain: new BigNumber(stability.lqtyGain)
|
||||
.dividedBy(1e18)
|
||||
.toString(),
|
||||
stakeAmount: new BigNumber(stake.amount).dividedBy(1e18).toString(),
|
||||
stakeEthGain: new BigNumber(stake.ethGain).dividedBy(1e18).toString(),
|
||||
stakeLqtyGain: new BigNumber(stake.lusdGain).dividedBy(1e18).toString(),
|
||||
price: new BigNumber(price).dividedBy(1e18).toString(),
|
||||
ratio,
|
||||
tokenKey: "eth",
|
||||
token: "ETH",
|
||||
liquidation: ratio
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async function getTroveTypes(web3) {
|
||||
try {
|
||||
const resolveABI = abis.resolver.liquity;
|
||||
const resolveAddr = addresses.mainnet.resolver.liquity;
|
||||
|
||||
const liquityInstance = new web3.eth.Contract(
|
||||
resolveABI as AbiItem[],
|
||||
resolveAddr
|
||||
);
|
||||
const {
|
||||
borrowFee,
|
||||
ethTvl,
|
||||
isInRecoveryMode: isRecoveryMode,
|
||||
tcr,
|
||||
price
|
||||
} = await liquityInstance.methods.getSystemState().call();
|
||||
|
||||
return [
|
||||
{
|
||||
totalCollateral: new BigNumber(ethTvl).dividedBy(1e18).toString(),
|
||||
price: new BigNumber(price).dividedBy(1e18).toString(),
|
||||
totalRatio: new BigNumber(1e18).dividedBy(tcr).toString(),
|
||||
tokenKey: "eth",
|
||||
token: "ETH",
|
||||
isRecoveryMode,
|
||||
borrowFee: new BigNumber(borrowFee).dividedBy(1e18).toString(),
|
||||
liquidation: new BigNumber(100).dividedBy(110).toString(),
|
||||
minDebt: new BigNumber(2000).toString(),
|
||||
liquidationReserve: "200"
|
||||
}
|
||||
];
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,9 @@ import SidebarMakerdaoWithdraw from '~/components/sidebar/context/makerdao/Sideb
|
|||
import SidebarMakerdaoBorrow from '~/components/sidebar/context/makerdao/SidebarMakerdaoBorrow.vue'
|
||||
import SidebarMakerdaoPayback from '~/components/sidebar/context/makerdao/SidebarMakerdaoPayback.vue'
|
||||
|
||||
import SidebarLiquityTroveOpenNew from '~/components/sidebar/context/liquity/SidebarLiquityTroveOpenNew.vue'
|
||||
|
||||
|
||||
const sidebars = {
|
||||
"#overview" : {component: SidebarOverview, back : false, close : true },
|
||||
"#deposit-overview": {component: SidebarDepositOverview, back: { hash: 'overview' } },
|
||||
|
|
@ -59,6 +62,8 @@ const sidebars = {
|
|||
"/mainnet/maker#withdraw": { component: SidebarMakerdaoWithdraw },
|
||||
"/mainnet/maker#borrow": { component: SidebarMakerdaoBorrow },
|
||||
"/mainnet/maker#payback": { component: SidebarMakerdaoPayback },
|
||||
|
||||
'/mainnet/liquity#trove-new': { component: SidebarLiquityTroveOpenNew },
|
||||
};
|
||||
|
||||
const sidebar = ref(null);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
import { useBigNumber } from "./useBigNumber";
|
||||
import { useFormatting } from "./useFormatting";
|
||||
import { useMakerdaoPosition } from "~/composables/protocols/useMakerdaoPosition";
|
||||
import { useLiquityPosition } from "./protocols/useLiquityPosition";
|
||||
|
||||
export function useValidators() {
|
||||
const { formatNumber } = useFormatting();
|
||||
const { isZero, minus, eq, gt, lt, gte, plus } = useBigNumber();
|
||||
const { minDebt: makerMinDebt, vaultTypes } = useMakerdaoPosition();
|
||||
const {
|
||||
minDebt: liquityMinDebt,
|
||||
liquidationReserve: liquityLiquidationReserve,
|
||||
troveOpened: liquityTroveOpened
|
||||
} = useLiquityPosition();
|
||||
|
||||
function validateAmount(amountParsed, balance = null, options = null) {
|
||||
const mergedOptions = Object.assign(
|
||||
|
|
@ -90,12 +96,39 @@ export function useValidators() {
|
|||
return null;
|
||||
}
|
||||
|
||||
function validateLiquityDebt(
|
||||
debtParsed,
|
||||
minDebt = liquityMinDebt.value,
|
||||
liquidationReserve = liquityLiquidationReserve.value
|
||||
) {
|
||||
const totalDebt = plus(debtParsed, liquidationReserve);
|
||||
|
||||
if (isZero(totalDebt))
|
||||
return `Minimum total debt requirement is ${minDebt} LUSD`;
|
||||
|
||||
if (lt(totalDebt, minDebt) && gt(totalDebt, "0")) {
|
||||
return `Minimum total debt requirement is ${minDebt} LUSD`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateLiquityTroveExists() {
|
||||
if (!liquityTroveOpened.value) {
|
||||
return "You should open new trove first";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
validateAmount,
|
||||
validateLiquidation,
|
||||
validateIsLoggedIn,
|
||||
validateLiquidity,
|
||||
validateMakerDebt,
|
||||
validateMakerDebtCeiling
|
||||
validateMakerDebtCeiling,
|
||||
validateLiquityDebt,
|
||||
validateLiquityTroveExists
|
||||
};
|
||||
}
|
||||
|
|
|
|||
361
constant/abi/read/liquity.json
Normal file
361
constant/abi/read/liquity.json
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
[
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "fetchETHPrice",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "getPosition",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "collateral",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "debt",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "icr",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "price",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Helpers.Trove",
|
||||
"name": "trove",
|
||||
"type": "tuple"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deposit",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "ethGain",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "lqtyGain",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Helpers.StabilityDeposit",
|
||||
"name": "stability",
|
||||
"type": "tuple"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "ethGain",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "lusdGain",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Helpers.Stake",
|
||||
"name": "stake",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Helpers.Position",
|
||||
"name": "",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "searchIterations",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "randomSeed",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getRedemptionPositionHints",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "partialHintNicr",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "firstHint",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "upperHint",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "lowerHint",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "oracleEthPrice",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "getStabilityDeposit",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deposit",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "ethGain",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "lqtyGain",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Helpers.StabilityDeposit",
|
||||
"name": "",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "getStake",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "ethGain",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "lusdGain",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Helpers.Stake",
|
||||
"name": "",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getSystemState",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "borrowFee",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "ethTvl",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tcr",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "isInRecoveryMode",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "price",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Helpers.System",
|
||||
"name": "",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "getTrove",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "collateral",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "debt",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "icr",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "price",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Helpers.Trove",
|
||||
"name": "",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "collateral",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "debt",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "searchIterations",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "randomSeed",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getTrovePositionHints",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "upperHint",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "lowerHint",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
|
|
@ -6,6 +6,7 @@ import compoundABI from "./abi/read/compound.json";
|
|||
import makerABI from "./abi/read/maker.json";
|
||||
import makerProxyRegistryABI from "./abi/makerProxyRegistry.json";
|
||||
import unipoolABI from "./abi/read/unipool.json";
|
||||
import liquityABI from "./abi/read/liquity.json";
|
||||
|
||||
const abis = {
|
||||
makerProxyRegistry: makerProxyRegistryABI,
|
||||
|
|
@ -17,7 +18,8 @@ const abis = {
|
|||
compound: compoundABI,
|
||||
maker: makerABI,
|
||||
unipool: unipoolABI,
|
||||
},
|
||||
liquity: liquityABI,
|
||||
}
|
||||
};
|
||||
|
||||
export default abis;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const addresses = {
|
|||
compound: "0xcCAa4b1b3931749b8b6EF19C6b0B2c496703321b",
|
||||
maker: "0x84addce4fac0b6ee4b0cd132120d6d4b700e35c0",
|
||||
unipool: "0x22bddA39D14eD0aafeee36B6e784602fdDE64723",
|
||||
liquity: '0xDAf2A39503463B0F41f899EDD82213b3c96b6Cf8',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export default {
|
|||
{ key: 'mkr', type: 'token', symbol: 'MKR', name: 'MakerDAO', address: '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', decimals: 18, isStableCoin: false},
|
||||
{ key: 'comp', type: 'token', symbol: 'COMP', name: 'Compound', address: '0xc00e94Cb662C3520282E6f5717214004A7f26888', decimals: 18, isStableCoin: false},
|
||||
{ key: 'rai', type: 'token', symbol: 'RAI', name: 'Rai Reflex Index', address: '0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919', decimals: 18, isStableCoin: false},
|
||||
{ key: 'lusd', type: 'token', symbol: 'LUSD', name: 'Liquity USD', address: '0x5f98805A4E8be255a32880FDeC7F6728C6568bA0', decimals: 18, isStableCoin: true},
|
||||
{ key: 'zrx', type: 'token', symbol: 'ZRX', name: '0x Protocol', address: '0xE41d2489571d322189246DaFA5ebDe1F4699F498', decimals: 18, isStableCoin: false},
|
||||
{ key: 'rep', type: 'token', symbol: 'REP', name: 'Augur', address: '0x1985365e9f78359a9B6AD760e32412f4a445E862', decimals: 18, isStableCoin: false},
|
||||
{ key: 'tusd', type: 'token', symbol: 'TUSD', name: 'TrueUSD', address: '0x0000000000085d4780B73119b644AE5ecd22b376', decimals: 18, isStableCoin: true},
|
||||
|
|
@ -44,7 +45,8 @@ export default {
|
|||
{ key: 'eth2xfli', type: 'token', symbol: 'ETH2x-FLI', name: 'ETH 2x Flexible Leverage Index', address: '0xAa6E8127831c9DE45ae56bB1b0d4D4Da6e5665BD', decimals: 18, isStableCoin: false, },
|
||||
{ key: 'btc2xfli', type: 'token', symbol: 'BTC2x-FLI', name: 'BTC 2x Flexible Leverage Index', address: '0x0B498ff89709d3838a063f1dFA463091F9801c2b', decimals: 18, isStableCoin: false, },
|
||||
{ key: 'stkaave', type: 'token', symbol: 'stkAAVE', name: 'Staked Aave', address: '0x4da27a545c0c5B758a6BA100e3a049001de870f5', decimals: 18, isStableCoin: false, },
|
||||
{ key: 'matic', type: 'token', symbol: 'MATIC', name: 'Matic Token', address: '0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0', decimals: 18, isStableCoin: false, }
|
||||
{ key: 'matic', type: 'token', symbol: 'MATIC', name: 'Matic Token', address: '0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0', decimals: 18, isStableCoin: false, },
|
||||
{ key: 'lqty', type: 'token', symbol: 'LQTY', name: 'LQTY', address: '0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D', decimals: 18, isStableCoin: false},
|
||||
]),
|
||||
|
||||
polygon: createTokenUtils([
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"bignumber.js": "^9.0.1",
|
||||
"core-js": "^3.15.1",
|
||||
"css-color-function": "^1.3.3",
|
||||
"dsa-connect": "^0.4.2",
|
||||
"dsa-connect": "^0.4.3",
|
||||
"nuxt": "^2.15.7",
|
||||
"qrcode": "^1.4.4",
|
||||
"v-click-outside": "^3.1.2",
|
||||
|
|
|
|||
|
|
@ -23,21 +23,177 @@
|
|||
</div>
|
||||
<h1 class="ml-4 text-primary-black text-2xl font-semibold">Liquity</h1>
|
||||
</div>
|
||||
|
||||
<div class="mt-10">
|
||||
<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(netValue) }}
|
||||
</h3>
|
||||
<p class="mt-4 text-primary-gray font-medium">Net Value</p>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<SVGBalance />
|
||||
</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">
|
||||
{{ formatPercent(borrowFee) }}
|
||||
</h3>
|
||||
<p class="mt-4 text-primary-gray font-medium">Borrow Fee</p>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<SVGPercent class="h-12" />
|
||||
</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(liquidation) }}</span>
|
||||
</div>
|
||||
</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">
|
||||
{{ formatUsdMax(liquidationPrice, liquidationMaxPrice) }} /
|
||||
{{ formatUsd(liquidationMaxPrice) }}
|
||||
</h3>
|
||||
<p class="mt-4 text-primary-gray font-medium">Liquidation (ETH)</p>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<IconBackground
|
||||
name="receipt-tax"
|
||||
class="bg-light-brown-pure text-light-brown-pure"
|
||||
/>
|
||||
</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>
|
||||
<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"
|
||||
>
|
||||
<button-dashed
|
||||
v-if="!troveOpened"
|
||||
color="ocean-blue"
|
||||
class="col-span-full"
|
||||
height="80px"
|
||||
full-width
|
||||
@click="openNewTrove"
|
||||
>
|
||||
<SVGAdd class="w-3 mr-2" />
|
||||
Open Trove
|
||||
</button-dashed>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
|
||||
import { defineComponent, computed, useRouter } from "@nuxtjs/composition-api";
|
||||
import BackIcon from "~/assets/icons/back.svg?inline";
|
||||
import LiquityIcon from '~/assets/icons/liquity.svg?inline'
|
||||
import SVGIncoming from "@/assets/img/icons/incoming.svg?inline";
|
||||
import SVGBalance from "@/assets/img/icons/balance.svg?inline";
|
||||
import SVGEarnings from "@/assets/img/icons/earnings.svg?inline";
|
||||
import SVGArrowRight from "@/assets/img/icons/arrow-right.svg?inline";
|
||||
import SVGPercent from "@/assets/img/icons/percent.svg?inline";
|
||||
import SVGAdd from "~/assets/img/icons/add.svg?inline";
|
||||
import LiquityIcon from "~/assets/icons/liquity.svg?inline";
|
||||
import ButtonDashed from "~/components/common/input/ButtonDashed.vue";
|
||||
import { useLiquityPosition } from "~/composables/protocols/useLiquityPosition";
|
||||
import { useFormatting } from "~/composables/useFormatting";
|
||||
import { useStatus } from "~/composables/useStatus";
|
||||
import { useBigNumber } from "~/composables/useBigNumber";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
BackIcon,
|
||||
LiquityIcon,
|
||||
ButtonDashed,
|
||||
SVGAdd,
|
||||
SVGBalance,
|
||||
SVGPercent,
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
|
||||
const { div, isZero, gt, lt } = useBigNumber();
|
||||
|
||||
const {
|
||||
formatUsd,
|
||||
formatUsdMax,
|
||||
formatPercent,
|
||||
formatDecimal
|
||||
} = useFormatting();
|
||||
|
||||
const {
|
||||
troveOpened,
|
||||
netValue,
|
||||
borrowFee,
|
||||
status,
|
||||
liquidation,
|
||||
liquidationPrice,
|
||||
liquidationMaxPrice
|
||||
} = useLiquityPosition();
|
||||
|
||||
const statusLiquidationRatio = computed(() =>
|
||||
div(status.value, liquidation.value).toFixed()
|
||||
);
|
||||
|
||||
const { color, text } = useStatus(statusLiquidationRatio);
|
||||
|
||||
function openNewTrove() {
|
||||
router.push({ hash: "trove-new" });
|
||||
}
|
||||
|
||||
return {
|
||||
color,
|
||||
text,
|
||||
|
||||
formatUsd,
|
||||
formatUsdMax,
|
||||
formatPercent,
|
||||
formatDecimal,
|
||||
|
||||
troveOpened,
|
||||
netValue,
|
||||
borrowFee,
|
||||
status,
|
||||
liquidation,
|
||||
liquidationPrice,
|
||||
liquidationMaxPrice,
|
||||
|
||||
openNewTrove
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -4675,10 +4675,10 @@ drbg.js@^1.0.1:
|
|||
create-hash "^1.1.2"
|
||||
create-hmac "^1.1.4"
|
||||
|
||||
dsa-connect@^0.4.2:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/dsa-connect/-/dsa-connect-0.4.2.tgz#1fb200eb3a93d260acada2cdbe8d5ebb71e3ba95"
|
||||
integrity sha512-NW9kblkfEgoYx7uyz0TiWauIya09XMnyDQ/kRCleSRo9wAmK3gGLLuPXMAtkk8P2jvgMxIce9zeJvFvYcGdX8Q==
|
||||
dsa-connect@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/dsa-connect/-/dsa-connect-0.4.3.tgz#382ad7c1b1fa54f963c84adc353dea4bde2f6e80"
|
||||
integrity sha512-kbG46cvAR2muy2P5jOTVsSZmyDewTAA0lKAeddsrVtpbmys1ujSuDjY/su4At6fAg/iF9/k+ltJAOEvoqt6g9A==
|
||||
|
||||
duplexer3@^0.1.4:
|
||||
version "0.1.4"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user