Merge branch 'master' into reflexer

This commit is contained in:
Georges KABBOUCHI 2021-09-06 19:53:44 +03:00
commit 9ecfbd2ee5
75 changed files with 4761 additions and 626 deletions

16
assets/icons/coinbase.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 53 KiB

11
assets/icons/metamask.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 149 KiB

11
assets/icons/portis.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,6 +1,6 @@
<template>
<div>
<div v-if="active && activeAccount" class="relative w-[193px]" v-click-outside="hide">
<div class="mt-3 w-full md:w-auto md:mt-0">
<div v-if="active && activeAccount" class="relative w-full md:w-[193px]" v-click-outside="hide">
<button
type="button"
class=" relative w-full border border-primary-blue-border rounded pl-3 pr-10 py-2 text-left focus:outline-none focus:ring-1 focus:ring-[#0846E4] focus:border-[#0846E4] sm:text-sm"
@ -10,7 +10,7 @@
@click="show = !show"
>
<span
class="flex items-center capitalize text-center text-sm font-bold"
class="flex items-center capitalize text-center text-sm font-bold truncate"
>
<div class="mr-2">
<svg
@ -143,7 +143,7 @@
</div>
<button
v-else-if="!active"
@click="activate"
@click="open"
class="hidden md:flex bg-primary-blue-dark hover:bg-primary-blue-hover shadow text-white p-3 rounded h-9 items-center justify-center w-40"
>
Connect
@ -155,12 +155,14 @@
import { defineComponent, ref, watch } from '@nuxtjs/composition-api'
import { useDSA } from "~/composables/useDSA";
import { useFormatting } from '~/composables/useFormatting';
import { useWeb3 } from '~/composables/useWeb3';
import { useWeb3Modal } from '~/composables/useWeb3Modal';
import { useWeb3 } from '@instadapp/vue-web3';
export default defineComponent({
setup() {
const { activeAccount } = useDSA()
const { active, deactivate, activate } = useWeb3()
const { open } = useWeb3Modal()
const { shortenHash } = useFormatting()
const show = ref(false)
@ -172,6 +174,7 @@ export default defineComponent({
return {
hide,
show,
open,
activeAccount,
active,
activate,

View File

@ -1,5 +1,5 @@
<template>
<div class="relative w-[160px] md:w-[178px]" v-click-outside="hide" v-if="activeAccount">
<div class="relative w-1/2 md:w-[178px]" v-click-outside="hide" v-if="activeAccount">
<button
type="button"
class="bg-primary-blue-dark hover:bg-primary-blue-hover relative w-full border border-primary-blue-border rounded pl-3 pr-10 py-2 text-left focus:outline-none focus:ring-1 focus:ring-[#0846E4] focus:border-[#0846E4] sm:text-sm"

View File

@ -40,7 +40,7 @@
</div>
<div
class="mt-8 md:mt-0 ml-auto flex items-center justify-center space-x-2.5"
class="mt-8 md:mt-0 md:ml-auto flex-wrap flex items-center md:justify-center md:space-x-2.5"
>
<AccountSwitcher v-if="active" />
@ -53,7 +53,7 @@
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import { useTenderly } from "~/composables/useTenderly";
import { useWeb3 } from "~/composables/useWeb3";
import { useWeb3 } from "@instadapp/vue-web3";
import ToggleButton from "./common/input/ToggleButton.vue";
export default defineComponent({

View File

@ -1,5 +1,5 @@
<template>
<div class="relative w-[160px] md:w-[178px]" v-click-outside="hide">
<div v-if="!isGnosisSafe" class="relative w-1/2 md:w-[178px]" v-click-outside="hide">
<button
type="button"
class="bg-primary-blue-dark hover:bg-primary-blue-hover relative w-full border border-primary-blue-border rounded pl-2.5 pr-10 py-1.5 text-left focus:outline-none focus:ring-1 focus:ring-[#0846E4] focus:border-[#0846E4] sm:text-sm"
@ -99,16 +99,21 @@
</template>
<script>
import { defineComponent, nextTick, ref } from '@nuxtjs/composition-api'
import { defineComponent, nextTick, ref, computed } from '@nuxtjs/composition-api'
import { useNetwork } from '~/composables/useNetwork'
import { useTenderly } from '~/composables/useTenderly'
import { useWeb3 } from '@instadapp/vue-web3'
import { gnosisSafe } from '~/connectors'
export default defineComponent({
setup() {
const show = ref(false)
const { connector } = useWeb3()
const { networks, activeNetworkId, activeNetwork, checkForNetworkMismatch } = useNetwork()
const { stopSimulation } = useTenderly()
const isGnosisSafe = computed(() => connector.value === gnosisSafe)
const setActiveNetwork = async networkId => {
await stopSimulation()
@ -129,6 +134,7 @@ export default defineComponent({
activeNetwork,
setActiveNetwork,
activeNetworkId,
isGnosisSafe,
}
},

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,117 @@
<template>
<div
class="relative inline-block w-full max-w-md px-8 py-8 overflow-hidden text-left align-bottom transition-all transform bg-white sm:my-16 sm:align-middle sm:p-6 "
:class="{
'border border-opacity-50 rounded-lg shadow-xl border-green-light': !slim
}"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div
:class="{
'py-8': !slim
}"
>
<div class="text-center">
<h3 id="modal-headline" class="font-bold text-2xl text-[#374253]">
Connect your wallet
</h3>
</div>
<div class="mt-8 w-full space-y-4">
<button
class="w-full px-6 py-3 text-left flex items-center h-[80px] border border-[#DBE5F4] rounded-[4px] text-lg text-[#374253] font-semibold hover:bg-background-light"
v-for="(wallet, key) in wallets"
:key="key"
@click="connect(wallet.connector)"
>
<div
style="background: radial-gradient(42.15% 42.15% at 48.94% 48.94%, #D6DAE0 75.67%, #F0F3F9 100%), #C4C4C4;"
class="mr-5 w-14 h-14 flex-shrink-0 rounded-full flex items-center justify-center border border-[#CCDCF3]"
>
<div
class="w-10 h-10 rounded-full inline-flex items-center justify-center bg-white"
>
<component :is="wallet.iconURL" class="w-7 h-7 text-white" />
</div>
</div>
{{ wallet.name }}
</button>
</div>
<div class="mt-6 text-center text-sm hidden">
Need help connecting a wallet?
<nuxt-link to="/faqs" class="font-semibold text-ocean-blue-pure"
>Read our FAQ</nuxt-link
>
</div>
</div>
<button v-if="!slim" class="absolute top-0 right-0 p-4" @click="close">
<svg
width="10"
height="10"
viewBox="0 0 10 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.279403 0.279336C-0.0930443 0.651784 -0.0930442 1.25564 0.279403 1.62809L3.65128 4.99997L0.279336 8.37191C-0.0931119 8.74436 -0.0931119 9.34822 0.279336 9.72066C0.651783 10.0931 1.25564 10.0931 1.62809 9.72066L5.00003 6.34872L8.37191 9.7206C8.74436 10.0931 9.34822 10.0931 9.72066 9.7206C10.0931 9.34816 10.0931 8.7443 9.72066 8.37185L6.34878 4.99997L9.7206 1.62815C10.093 1.2557 10.093 0.651844 9.7206 0.279396C9.34815 -0.0930521 8.74429 -0.093052 8.37184 0.279396L5.00003 3.65121L1.62816 0.279336C1.25571 -0.093112 0.651851 -0.093112 0.279403 0.279336Z"
fill="#1874FF"
/>
</svg>
</button>
</div>
</template>
<script>
import { computed, defineComponent, ref } from '@nuxtjs/composition-api'
import Input from '~/components/common/input/Input.vue'
import { useModal } from '~/composables/useModal'
import { useWeb3 } from '@instadapp/vue-web3'
import { injected } from '~/connectors'
import { SUPPORTED_WALLETS } from '~/constant/wallet'
import ButtonCTA from '../../common/input/ButtonCTA.vue'
import ButtonCTAOutlined from '../../common/input/ButtonCTAOutlined.vue'
export default defineComponent({
props: {
slim: {
type: Boolean,
default: false
}
},
components: { ButtonCTA, ButtonCTAOutlined, Input },
setup() {
const { close } = useModal()
const { activate } = useWeb3()
const connect = async (connector) => {
await activate(connector, console.log)
close()
}
const isMetamask = computed(() => window.ethereum && window.ethereum.isMetaMask)
const wallets = computed(() => Object.keys(SUPPORTED_WALLETS).map((key) => {
const wallet = SUPPORTED_WALLETS[key]
if (wallet.connector === injected && !isMetamask.value) {
return null
}
return wallet
}).filter(Boolean))
return {
close,
connect,
wallets,
isMetamask,
injected,
}
},
})
</script>

View File

@ -79,7 +79,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import atokens from '~/constant/atokens'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
@ -87,6 +87,7 @@ import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
import { useNotification } from '~/composables/useNotification'
import Button from '~/components/Button.vue'
import { useSidebar } from '~/composables/useSidebar'
import { useNetwork } from '~/composables/useNetwork'
export default defineComponent({
components: { InputNumeric, Input, ToggleButton, ButtonCTA, Button },
@ -95,7 +96,8 @@ export default defineComponent({
},
setup(props) {
const { close } = useSidebar()
const { networkName, account, web3 } = useWeb3()
const { account, library } = useWeb3()
const { activeNetworkId } = useNetwork()
const { dsa } = useDSA()
const { getTokenByKey, valInt } = useToken()
const { formatNumber, formatUsdMax, formatUsd } = useFormatting()
@ -109,7 +111,7 @@ export default defineComponent({
const amount = ref('')
const amountParsed = computed(() => parseSafeFloat(amount.value))
const rootTokenKey = computed(() => atokens[networkName.value].rootTokens.includes(props.tokenKey) ? props.tokenKey : 'eth')
const rootTokenKey = computed(() => atokens[activeNetworkId.value].rootTokens.includes(props.tokenKey) ? props.tokenKey : 'eth')
const token = computed(() => getTokenByKey(rootTokenKey.value))
const symbol = computed(() => token.value?.symbol)
@ -126,7 +128,7 @@ export default defineComponent({
return {
amount: { message: validateAmount(amountParsed.value, balance.value), show: hasAmountValue },
accountAddress: { message: web3.value && !web3.value.utils.isAddress(accountAddress.value) ? 'Enter valid address!' : null, show: accountAddress.value.length > 0 },
accountAddress: { message: library.value && !library.value.utils.isAddress(accountAddress.value) ? 'Enter valid address!' : null, show: accountAddress.value.length > 0 },
auth: { message: validateIsLoggedIn(!!account.value), show: true },
}
})

View File

@ -76,7 +76,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import atokens from '~/constant/atokens'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
@ -84,6 +84,7 @@ import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
import { useNotification } from '~/composables/useNotification'
import Button from '~/components/Button.vue'
import { useSidebar } from '~/composables/useSidebar'
import { useNetwork } from '~/composables/useNetwork'
export default defineComponent({
components: { InputNumeric, ToggleButton, ButtonCTA, Button },
@ -92,7 +93,8 @@ export default defineComponent({
},
setup(props) {
const { close } = useSidebar()
const { networkName, account } = useWeb3()
const { account } = useWeb3()
const { activeNetworkId } = useNetwork()
const { dsa } = useDSA()
const { getTokenByKey, valInt } = useToken()
const { formatNumber, formatUsdMax, formatUsd } = useFormatting()
@ -117,7 +119,7 @@ export default defineComponent({
const amount = ref('')
const amountParsed = computed(() => parseSafeFloat(amount.value))
const rootTokenKey = computed(() => atokens[networkName.value].rootTokens.includes(props.tokenKey) ? props.tokenKey : 'eth')
const rootTokenKey = computed(() => atokens[activeNetworkId.value].rootTokens.includes(props.tokenKey) ? props.tokenKey : 'eth')
const currentPosition = computed(() =>
displayPositions.value.find((position) => position.key === rootTokenKey.value)

View File

@ -99,7 +99,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import atokens from '~/constant/atokens'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
@ -107,6 +107,7 @@ import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
import { useNotification } from '~/composables/useNotification'
import Button from '~/components/Button.vue'
import { useSidebar } from '~/composables/useSidebar'
import { useNetwork } from '~/composables/useNetwork'
export default defineComponent({
components: { InputNumeric, ToggleButton, ButtonCTA, Button },
@ -115,7 +116,8 @@ export default defineComponent({
},
setup(props) {
const { close } = useSidebar()
const { networkName, account } = useWeb3()
const { account } = useWeb3()
const { activeNetworkId } = useNetwork()
const { dsa } = useDSA()
const { getTokenByKey, valInt } = useToken()
const { getBalanceByKey, getBalanceRawByKey, fetchBalances } = useBalances()
@ -139,7 +141,7 @@ export default defineComponent({
const amount = ref('')
const amountParsed = computed(() => parseSafeFloat(amount.value))
const rootTokenKey = computed(() => atokens[networkName.value].rootTokens.includes(props.tokenKey) ? props.tokenKey : 'eth')
const rootTokenKey = computed(() => atokens[activeNetworkId.value].rootTokens.includes(props.tokenKey) ? props.tokenKey : 'eth')
const currentPosition = computed(() =>
displayPositions.value.find((position) => position.key === rootTokenKey.value)

View File

@ -80,7 +80,7 @@ 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 { useWeb3 } from "@instadapp/vue-web3";
import atokens from "~/constant/atokens";
import ToggleButton from "~/components/common/input/ToggleButton.vue";
import { useDSA } from "~/composables/useDSA";
@ -88,6 +88,7 @@ import ButtonCTA from "~/components/common/input/ButtonCTA.vue";
import Button from "~/components/Button.vue";
import { useSidebar } from "~/composables/useSidebar";
import DSA from "dsa-connect";
import { useNetwork } from "~/composables/useNetwork";
export default defineComponent({
components: { InputNumeric, ToggleButton, ButtonCTA, Button },
props: {
@ -95,7 +96,8 @@ export default defineComponent({
},
setup(props) {
const { close } = useSidebar();
const { networkName, account } = useWeb3();
const { account } = useWeb3();
const { activeNetworkId } = useNetwork()
const { dsa } = useDSA();
const { getTokenByKey, valInt } = useToken();
const { getBalanceByKey, fetchBalances } = useBalances();
@ -129,7 +131,7 @@ export default defineComponent({
const amountParsed = computed(() => parseSafeFloat(amount.value));
const rootTokenKey = computed(() =>
atokens[networkName.value].rootTokens.includes(props.tokenKey)
atokens[activeNetworkId.value].rootTokens.includes(props.tokenKey)
? props.tokenKey
: "eth"
);

View File

@ -81,7 +81,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import atokens from '~/constant/atokens'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
@ -89,6 +89,7 @@ import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
import { useNotification } from '~/composables/useNotification'
import Button from '~/components/Button.vue'
import { useSidebar } from '~/composables/useSidebar'
import { useNetwork } from '~/composables/useNetwork'
export default defineComponent({
components: { InputNumeric, ToggleButton, ButtonCTA, Button },
@ -97,7 +98,8 @@ export default defineComponent({
},
setup(props) {
const { close } = useSidebar()
const { networkName, account } = useWeb3()
const { account } = useWeb3()
const { activeNetworkId } = useNetwork()
const { dsa } = useDSA()
const { getTokenByKey, valInt } = useToken()
const { formatNumber, formatUsdMax, formatUsd } = useFormatting()
@ -132,7 +134,7 @@ export default defineComponent({
const amount = ref('')
const amountParsed = computed(() => parseSafeFloat(amount.value))
const rootTokenKey = computed(() => atokens[networkName.value].rootTokens.includes(props.tokenKey) ? props.tokenKey : 'eth')
const rootTokenKey = computed(() => atokens[activeNetworkId.value].rootTokens.includes(props.tokenKey) ? props.tokenKey : 'eth')
const token = computed(() => getTokenByKey(rootTokenKey.value))
const symbol = computed(() => token.value?.symbol)

View File

@ -65,7 +65,7 @@ import { useValidators } from '~/composables/useValidators'
import { useValidation } from '~/composables/useValidation'
import { useToken } from '~/composables/useToken'
import { useParsing } from '~/composables/useParsing'
import { useWeb3 } from '~/composables/useWeb3'
import { useWeb3 } from '@instadapp/vue-web3'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
@ -76,6 +76,7 @@ import { useCompoundPosition } from '~/composables/protocols/useCompoundPosition
import ctokens from '~/constant/ctokens'
import tokenIdMapping from '~/constant/tokenIdMapping'
import { useBalances } from '~/composables/useBalances'
import { useNetwork } from '~/composables/useNetwork'
export default defineComponent({
components: { InputNumeric, ToggleButton, ButtonCTA, Button },
@ -84,7 +85,8 @@ export default defineComponent({
},
setup(props) {
const { close } = useSidebar()
const { networkName, account } = useWeb3()
const { account } = useWeb3()
const { activeNetworkId } = useNetwork()
const { dsa } = useDSA()
const { getTokenByKey, valInt } = useToken()
const { fetchBalances } = useBalances()
@ -96,7 +98,7 @@ export default defineComponent({
const tokenId = computed(() => props.tokenId)
const tokenKey = computed(() => tokenIdMapping.idToToken[tokenId.value])
const rootTokenKey = computed(() => ctokens[networkName.value].rootTokens.includes(tokenKey.value) ? tokenKey.value : 'eth')
const rootTokenKey = computed(() => ctokens[activeNetworkId.value].rootTokens.includes(tokenKey.value) ? tokenKey.value : 'eth')
const { stats, status: initialStatus, position, displayPositions, liquidation, liquidationPrice, liquidationMaxPrice, refreshPosition } = useCompoundPosition({
overridePosition: (position) => {

View File

@ -90,7 +90,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
@ -100,6 +100,7 @@ import { useSidebar } from '~/composables/useSidebar'
import { useCompoundPosition } from '~/composables/protocols/useCompoundPosition'
import ctokens from '~/constant/ctokens'
import tokenIdMapping from '~/constant/tokenIdMapping'
import { useNetwork } from '~/composables/useNetwork'
export default defineComponent({
components: { InputNumeric, ToggleButton, ButtonCTA, Button },
@ -108,7 +109,8 @@ export default defineComponent({
},
setup(props) {
const { close } = useSidebar()
const { networkName, account } = useWeb3()
const { account } = useWeb3()
const { activeNetworkId } = useNetwork()
const { dsa } = useDSA()
const { getTokenByKey, valInt } = useToken()
const { getBalanceByKey, getBalanceRawByKey, fetchBalances } = useBalances()
@ -119,7 +121,7 @@ export default defineComponent({
const tokenId = computed(() => props.tokenId)
const tokenKey = computed(() => tokenIdMapping.idToToken[tokenId.value])
const rootTokenKey = computed(() => ctokens[networkName.value].rootTokens.includes(tokenKey.value) ? tokenKey.value : 'eth')
const rootTokenKey = computed(() => ctokens[activeNetworkId.value].rootTokens.includes(tokenKey.value) ? tokenKey.value : 'eth')
const { status, position, displayPositions, liquidation, liquidationPrice, liquidationMaxPrice, refreshPosition } = useCompoundPosition({

View File

@ -79,7 +79,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
@ -88,6 +88,7 @@ import { useSidebar } from '~/composables/useSidebar'
import tokenIdMapping from '~/constant/tokenIdMapping'
import ctokens from '~/constant/ctokens'
import { useCompoundPosition } from '~/composables/protocols/useCompoundPosition'
import { useNetwork } from '~/composables/useNetwork'
export default defineComponent({
components: { InputNumeric, ToggleButton, ButtonCTA, Button },
@ -96,7 +97,8 @@ export default defineComponent({
},
setup(props) {
const { close } = useSidebar()
const { networkName, account } = useWeb3()
const { account } = useWeb3()
const { activeNetworkId } = useNetwork()
const { dsa } = useDSA()
const { getTokenByKey, valInt } = useToken()
const { getBalanceByKey, fetchBalances } = useBalances()
@ -108,7 +110,7 @@ export default defineComponent({
const tokenId = computed(() => props.tokenId)
const tokenKey = computed(() => tokenIdMapping.idToToken[tokenId.value])
const rootTokenKey = computed(() => ctokens[networkName.value].rootTokens.includes(tokenKey.value) ? tokenKey.value : 'eth')
const rootTokenKey = computed(() => ctokens[activeNetworkId.value].rootTokens.includes(tokenKey.value) ? tokenKey.value : 'eth')
const { status, position, displayPositions, liquidation, liquidationPrice, liquidationMaxPrice, refreshPosition } = useCompoundPosition({
overridePosition: (position) => {

View File

@ -79,7 +79,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
@ -90,6 +90,7 @@ import tokenIdMapping from '~/constant/tokenIdMapping'
import ctokens from '~/constant/ctokens'
import { useCompoundPosition } from '~/composables/protocols/useCompoundPosition'
import { useBalances } from '~/composables/useBalances'
import { useNetwork } from '~/composables/useNetwork'
export default defineComponent({
components: { InputNumeric, ToggleButton, ButtonCTA, Button },
@ -98,7 +99,8 @@ export default defineComponent({
},
setup(props) {
const { close } = useSidebar()
const { networkName, account } = useWeb3()
const { account } = useWeb3()
const { activeNetworkId } = useNetwork()
const { dsa } = useDSA()
const { fetchBalances } = useBalances()
const { getTokenByKey, valInt } = useToken()
@ -110,7 +112,7 @@ export default defineComponent({
const tokenId = computed(() => props.tokenId)
const tokenKey = computed(() => tokenIdMapping.idToToken[tokenId.value])
const rootTokenKey = computed(() => ctokens[networkName.value].rootTokens.includes(tokenKey.value) ? tokenKey.value : 'eth')
const rootTokenKey = computed(() => ctokens[activeNetworkId.value].rootTokens.includes(tokenKey.value) ? tokenKey.value : 'eth')
const { stats, status, position, displayPositions, liquidation, liquidationPrice, liquidationMaxPrice, refreshPosition } = useCompoundPosition({

View File

@ -97,7 +97,7 @@ import { useValidators } from '~/composables/useValidators'
import { useValidation } from '~/composables/useValidation'
import { useToken } from '~/composables/useToken'
import { useParsing } from '~/composables/useParsing'
import { useWeb3 } from '~/composables/useWeb3'
import { useWeb3 } from '@instadapp/vue-web3'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'

View File

@ -107,7 +107,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import atokens from '~/constant/atokens'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'

View File

@ -95,7 +95,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
@ -108,7 +108,7 @@ export default defineComponent({
components: { InputNumeric, ToggleButton, ButtonCTA, Button },
setup() {
const { close } = useSidebar()
const { networkName, account } = useWeb3()
const { account } = useWeb3()
const { dsa } = useDSA()
const { valInt } = useToken()
const { getBalanceByKey, fetchBalances } = useBalances()

View File

@ -84,7 +84,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'

View File

@ -96,7 +96,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'

View File

@ -69,7 +69,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
@ -87,7 +87,7 @@ export default defineComponent({
},
setup(props) {
const { close } = useSidebar()
const { networkName, account } = useWeb3()
const { account } = useWeb3()
const { dsa } = useDSA()
const { getTokenByKey, valInt } = useToken()
const { fetchBalances } = useBalances()

View File

@ -91,7 +91,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
@ -107,7 +107,7 @@ export default defineComponent({
},
setup(props) {
const { close } = useSidebar()
const { networkName, account } = useWeb3()
const { account } = useWeb3()
const { dsa } = useDSA()
const { getTokenByKey, valInt } = useToken()
const { getBalanceByKey, getBalanceRawByKey, fetchBalances } = useBalances()

View File

@ -79,7 +79,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'

View File

@ -77,7 +77,7 @@ 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 { useWeb3 } from '@instadapp/vue-web3'
import ToggleButton from '~/components/common/input/ToggleButton.vue'
import { useDSA } from '~/composables/useDSA'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'

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

@ -3,15 +3,16 @@ import { AbiItem } from "web3-utils";
import aaveV2ABI from "~/abis/read/aaveV2.json";
import { computed, ref, watch } from "@nuxtjs/composition-api";
import { useDSA } from "~/composables/useDSA";
import { useWeb3 } from "~/composables/useWeb3";
import { useWeb3 } from "@instadapp/vue-web3";
import BigNumber from "bignumber.js";
import atokensV2 from "~/constant/atokensV2";
import tokens from "~/constant/tokens";
import { Network } from "~/composables/useNetwork";
import { Network, useNetwork } from "~/composables/useNetwork";
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),
@ -65,10 +66,12 @@ export function useAaveV2Position(
) {
overridePosition = overridePosition || (pos => pos);
const { web3, chainId, networkName } = useWeb3();
const { library, chainId } = useWeb3();
const { activeNetworkId } = useNetwork();
const { activeAccount } = useDSA();
const { getTokenByKey, allATokensV2 } = useToken();
const { byMaxSupplyOrBorrowDesc } = useSorting()
const { onEvent } = useEventBus()
const resolver = computed(() =>
chainId.value === 1
@ -77,7 +80,7 @@ export function useAaveV2Position(
);
const fetchPosition = async () => {
if (!web3.value) {
if (!library.value) {
return;
}
@ -85,20 +88,20 @@ export function useAaveV2Position(
return;
}
const aaveResolverInstance = new web3.value.eth.Contract(
const aaveResolverInstance = new library.value.eth.Contract(
aaveV2ABI as AbiItem[],
resolver.value
);
const aaveTokensArr = atokensV2[networkName.value].allTokens.map(
a => tokens[networkName.value].getTokenByKey(a.root).address
const aaveTokensArr = atokensV2[activeNetworkId.value].allTokens.map(
a => tokens[activeNetworkId.value].getTokenByKey(a.root).address
);
const aaveRawData = await aaveResolverInstance.methods
.getPosition(activeAccount.value.address, aaveTokensArr)
.call();
const newPos = calculateAavePosition(aaveRawData, networkName.value);
const newPos = calculateAavePosition(aaveRawData, activeNetworkId.value);
return newPos;
};
@ -107,9 +110,10 @@ export function useAaveV2Position(
position.value = await fetchPosition();
};
onEvent("protocol::aaveV2::refresh", refreshPosition);
watch(
web3,
library,
async val => {
if (val) {
refreshPosition();
@ -165,7 +169,7 @@ export function useAaveV2Position(
);
const rewardTokenPriceInUsd = computed(() => {
if (networkName.value === Network.Polygon) {
if (activeNetworkId.value === Network.Polygon) {
return ensureValue(
position.value.data.find(position => position.key === "matic")
?.priceInUsd

View File

@ -3,10 +3,10 @@ import { AbiItem } from "web3-utils";
import compoundABI from "~/abis/read/compound.json";
import { computed, ref, watch } from "@nuxtjs/composition-api";
import { useDSA } from "~/composables/useDSA";
import { useWeb3 } from "~/composables/useWeb3";
import { useWeb3 } from "@instadapp/vue-web3";
import BigNumber from "bignumber.js";
import tokens from "~/constant/tokens";
import { Network } from "~/composables/useNetwork";
import { Network, useNetwork } from "~/composables/useNetwork";
import { useBigNumber } from "~/composables/useBigNumber";
import { usePosition } from "~/composables/usePosition";
import { useToken } from "~/composables/useToken";
@ -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),
@ -62,14 +63,16 @@ export function useCompoundPosition(
) {
overridePosition = overridePosition || (pos => pos);
const { web3, networkName } = useWeb3();
const { library } = useWeb3();
const { activeNetworkId } = useNetwork()
const { onEvent } = useEventBus()
const { activeAccount } = useDSA();
const { getTokenByKey } = useToken();
const { byMaxSupplyOrBorrowDesc } = useSorting()
const resolver = computed(() => addresses.mainnet.resolver.compound);
const fetchPosition = async () => {
if (!web3.value) {
if (!library.value) {
return;
}
@ -77,12 +80,12 @@ export function useCompoundPosition(
return;
}
const resolverInstance = new web3.value.eth.Contract(
const resolverInstance = new library.value.eth.Contract(
compoundABI as AbiItem[],
resolver.value
);
const tokensArr = ctokens[networkName.value].allTokens.map(a => a.address);
const tokensArr = ctokens[activeNetworkId.value].allTokens.map(a => a.address);
const compoundRawData = await resolverInstance.methods
.getPosition(activeAccount.value.address, tokensArr)
@ -90,7 +93,7 @@ export function useCompoundPosition(
const newPos = calculateCompoundPosition(
compoundRawData,
networkName.value
activeNetworkId.value
);
return newPos;
@ -100,8 +103,10 @@ export function useCompoundPosition(
position.value = await fetchPosition();
};
onEvent("protocol::compound::refresh", refreshPosition);
watch(
web3,
library,
async val => {
if (val) {
refreshPosition();
@ -162,7 +167,7 @@ export function useCompoundPosition(
return [];
}
return ctokens[networkName.value].allTokens
return ctokens[activeNetworkId.value].allTokens
.flatMap(ctoken => {
const token = getTokenByKey(ctoken.root);
if (!token) {

View File

@ -2,15 +2,16 @@ 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 { useWeb3 } from "@instadapp/vue-web3";
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";
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)
);
@ -49,7 +50,8 @@ export function useLiquityPosition(
collateralAmountRef: Ref = null,
debtAmountRef: Ref = null
) {
const { web3 } = useWeb3();
const { library } = useWeb3();
const { onEvent } = useEventBus()
const { activeAccount } = useDSA();
const { isZero, times, div, max, minus, plus } = useBigNumber();
@ -137,22 +139,22 @@ export function useLiquityPosition(
);
const fetchPosition = async () => {
if (!web3.value) {
if (!library.value) {
return;
}
troveTypes.value = await getTroveTypes(web3.value);
troveTypes.value = await getTroveTypes(library.value);
if (!activeAccount.value) {
return;
}
trove.value = await getTrove(activeAccount.value.address, web3.value);
trove.value = await getTrove(activeAccount.value.address, library.value);
};
async function getTrovePositionHints(collateralInWei, debtInWei) {
try {
const liquityInstance = new web3.value.eth.Contract(
const liquityInstance = new library.value.eth.Contract(
abis.resolver.liquity as AbiItem[],
addresses.mainnet.resolver.liquity
);
@ -178,8 +180,11 @@ export function useLiquityPosition(
}
}
onEvent("protocol::liquity::refresh", fetchPosition);
watch(
web3,
library,
async val => {
if (val) {
fetchPosition();

View File

@ -7,8 +7,9 @@ import makerVaults from "~/constant/tokens/vaults";
import { useBigNumber } from "~/composables/useBigNumber";
import { useDSA } from "~/composables/useDSA";
import { useToken } from "~/composables/useToken";
import { useWeb3 } from "~/composables/useWeb3";
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;
@ -54,7 +55,8 @@ export function useMakerdaoPosition(
collateralAmountRef: Ref = null,
debtAmountRef: Ref = null
) {
const { web3, chainId, networkName } = useWeb3();
const { library } = useWeb3();
const { onEvent } = useEventBus()
const { activeAccount } = useDSA();
const { isZero, ensureValue, times, div, max, gt } = useBigNumber();
const { getTokenByKey } = useToken();
@ -117,23 +119,25 @@ export function useMakerdaoPosition(
);
const fetchPosition = async () => {
if (!web3.value) {
if (!library.value) {
return;
}
vaultTypes.value = await getVaultTypes(web3.value);
vaultTypes.value = await getVaultTypes(library.value);
if (!activeAccount.value) {
return;
}
vaults.value = await getVaults(activeAccount.value.address, web3.value);
vaults.value = await getVaults(activeAccount.value.address, library.value);
if (vaults.value.length > 0 && !vaultId.value) {
vaultId.value = vaults.value[0].id;
}
};
onEvent("protocol::makerdao::refresh", fetchPosition);
watch(
web3,
library,
async val => {
if (val) {
fetchPosition();

View File

@ -11,8 +11,8 @@ import addresses from "~/constant/addresses";
import tokens from "~/constant/tokens";
import uniPoolTokens from "~/constant/uniPoolTokens";
import { useDSA } from "./useDSA";
import { Network } from "./useNetwork";
import { useWeb3 } from "./useWeb3";
import { Network, useNetwork } from "./useNetwork";
import { useWeb3 } from "@instadapp/vue-web3";
import Web3 from "web3";
import { AbiItem } from "web3-utils";
import { useToken } from "./useToken";
@ -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({
@ -32,7 +38,8 @@ const prices = reactive({
export function useBalances() {
const { $axios } = useContext();
const { times, plus, ensureValue } = useBigNumber();
const { account, networkName, web3 } = useWeb3();
const { account, library } = useWeb3();
const { activeNetworkId } = useNetwork()
const { activeAccount } = useDSA();
const { getTokenByKey } = useToken();
const { by } = useSorting();
@ -48,12 +55,12 @@ export function useBalances() {
if (!account.value) return;
balances.user = {
mainnet:
networkName.value === Network.Mainnet
? await getBalances(account.value, Network.Mainnet, web3.value)
activeNetworkId.value === Network.Mainnet
? await getBalances(account.value, Network.Mainnet, library.value)
: {},
polygon:
networkName.value === Network.Polygon
? await getBalances(account.value, Network.Polygon, web3.value)
activeNetworkId.value === Network.Polygon
? await getBalances(account.value, Network.Polygon, library.value)
: {}
};
}
@ -63,19 +70,19 @@ export function useBalances() {
balances.dsa = {
mainnet:
networkName.value === Network.Mainnet
activeNetworkId.value === Network.Mainnet
? await getBalances(
activeAccount.value.address,
Network.Mainnet,
web3.value
library.value
)
: {},
polygon:
networkName.value === Network.Polygon
activeNetworkId.value === Network.Polygon
? await getBalances(
activeAccount.value.address,
Network.Polygon,
web3.value
library.value
)
: {}
};
@ -88,27 +95,27 @@ export function useBalances() {
const getBalanceByAddress = (address, network = null, type = "dsa") => {
return (
balances[type]?.[network || networkName.value][address]?.balance || "0"
balances[type]?.[network || activeNetworkId.value][address]?.balance || "0"
);
};
const getBalanceRawByKey = (tokenKey, network = null, type = "dsa") => {
return (
balances[type]?.[network || networkName.value][
balances[type]?.[network || activeNetworkId.value][
getTokenByKey(tokenKey)?.address
]?.raw || "0"
);
};
const netWorth = (address, type = "dsa") => {
const balance = getBalanceByAddress(address, networkName.value, type);
const price = ensureValue(prices[networkName.value][address]).toFixed();
const balance = getBalanceByAddress(address, activeNetworkId.value, type);
const price = ensureValue(prices[activeNetworkId.value][address]).toFixed();
return times(balance, price).toFixed();
};
const balanceTotal = computed(() =>
tokens[networkName.value].allTokens.reduce(
tokens[activeNetworkId.value].allTokens.reduce(
(totalNetWorth, token) =>
plus(totalNetWorth, netWorth(token.address)).toFixed(),
"0"
@ -116,16 +123,16 @@ export function useBalances() {
);
const getAssets = (type = "dsa") => {
return tokens[networkName.value].allTokens
return tokens[activeNetworkId.value].allTokens
.map(token => ({
...token,
balance: getBalanceByAddress(token.address, networkName.value, type),
balance: getBalanceByAddress(token.address, activeNetworkId.value, type),
netWorth: netWorth(token.address, type)
}))
.sort(by("-netWorth"));
};
watch(web3, () => {
watch(library, () => {
fetchBalances(true);
});
return {
@ -206,6 +213,7 @@ async function getBalances(
}
const { name, symbol, decimals, type, isStableCoin, key } = tokenData;
tokensBalObj[tokenAddress] = {
address: tokenAddress,
name,
symbol,
decimals,

View File

@ -1,10 +1,11 @@
import { computed, readonly, ref, watch } from "@nuxtjs/composition-api";
import { useWeb3 } from "./useWeb3";
import { useWeb3 } from "@instadapp/vue-web3";
import DSA from "dsa-connect";
import addresses from "~/constant/addresses";
import abis from "~/constant/abis";
import { AbiItem } from "web3-utils";
import { useNotification } from "./useNotification";
import { useNetwork } from "./useNetwork";
const dsa = ref<DSA>();
const accounts = ref<any[]>([]);
@ -12,18 +13,28 @@ const activeAccount = ref<any>();
const authorities = ref<string[]>();
export function useDSA() {
const { web3, chainId, networkName, account } = useWeb3();
const { active, library, chainId, account } = useWeb3();
const { activeNetworkId } = useNetwork()
const { showWarning } = useNotification();
watch(web3, () => {
if (web3.value) {
dsa.value = new DSA(web3.value, chainId.value);
watch(library, () => {
if (library.value) {
dsa.value = new DSA(library.value, chainId.value);
}
});
watch(active, () => {
console.log("here");
if (library.value) {
dsa.value = new DSA(library.value, chainId.value);
}
});
watch(chainId, () => {
if (web3.value) {
dsa.value = new DSA(web3.value, chainId.value);
if (library.value) {
dsa.value = new DSA(library.value, chainId.value);
}
});
@ -82,9 +93,9 @@ export function useDSA() {
async function fethAuthorities() {
try {
const accountsResolverInstance = new web3.value.eth.Contract(
const accountsResolverInstance = new library.value.eth.Contract(
abis.resolver.accounts as AbiItem[],
addresses[networkName.value].resolver.accounts
addresses[activeNetworkId.value].resolver.accounts
);
const rawData = await accountsResolverInstance.methods
.getAccountAuthorities(activeAccount.value.address)
@ -170,7 +181,7 @@ export function useDSA() {
createAccount,
creatingAccount,
setAccount,
web3,
library,
chainId,
authorities,
createAuthority,

View File

@ -0,0 +1,35 @@
import { useWeb3 } from "@instadapp/vue-web3";
import { injected, gnosisSafe } from "../connectors";
import { onMounted, ref, watch, watchEffect } from "@nuxtjs/composition-api";
import { useSafeAppConnection } from "./useSafeAppConnection";
export function useEagerConnect() {
const { activate, active } = useWeb3();
const { tried: triedToConnectToSafe } = useSafeAppConnection(gnosisSafe);
const tried = ref(false);
watchEffect(() => {
if (triedToConnectToSafe.value && !active.value) {
injected.isAuthorized().then((isAuthorized: boolean) => {
if (isAuthorized) {
activate(injected, undefined, true).catch(() => {
tried.value = true;
});
} else {
tried.value = true;
}
});
}
});
// if the connection worked, wait until we get confirmation of that to flip the flag
watch([tried, active], () => {
if (!tried.value && active.value) {
tried.value = true;
}
});
return {
tried
};
}

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

@ -1,22 +1,25 @@
import { computed } from '@nuxtjs/composition-api'
import { useWeb3 } from './useWeb3'
import { computed } from "@nuxtjs/composition-api";
import { useNetwork } from "./useNetwork";
export function useLink() {
const { networkName } = useWeb3()
const { activeNetworkId } = useNetwork();
const addressDetailsLink = computed(() => {
if (networkName.value === 'polygon') {
return 'https://polygonscan.com/address'
if (activeNetworkId.value === "polygon") {
return "https://polygonscan.com/address";
}
return 'https://etherscan.io/address'
})
return "https://etherscan.io/address";
});
return { addressDetailsLink }
return { addressDetailsLink };
}
export const getEtherscanLink = (transactionHash) => `https://etherscan.io/tx/${transactionHash}`
export const getMaticLink = (transactionHash) => `https://polygonscan.com/tx/${transactionHash}`
export const getPolygonLink = (transactionHash) => `https://polygonscan.com/tx/${transactionHash}`
export const getTenderlyLink = (simulationId) =>
`https://dashboard.tenderly.co/public/InstaDApp/dsa-simulations/fork-simulation/${simulationId}?hideSidebar=true`
export const getEtherscanLink = transactionHash =>
`https://etherscan.io/tx/${transactionHash}`;
export const getMaticLink = transactionHash =>
`https://polygonscan.com/tx/${transactionHash}`;
export const getPolygonLink = transactionHash =>
`https://polygonscan.com/tx/${transactionHash}`;
export const getTenderlyLink = simulationId =>
`https://dashboard.tenderly.co/public/InstaDApp/dsa-simulations/fork-simulation/${simulationId}?hideSidebar=true`;

View File

@ -5,7 +5,7 @@ import MainnetSVG from "~/assets/icons/mainnet.svg?inline";
import PolygonSVG from "~/assets/icons/polygon.svg?inline";
import { useModal } from "./useModal";
import { useNotification } from "./useNotification";
import { useWeb3 } from "./useWeb3";
import { useWeb3 } from "@instadapp/vue-web3";
export enum Network {
Mainnet = "mainnet",
@ -24,11 +24,11 @@ export const activeNetwork = computed(
export function useNetwork() {
const { showWarning } = useNotification();
const { account, networkName, refreshWeb3 } = useWeb3();
const { account, chainId } = useWeb3();
const { showNetworksMismatchDialog } = useModal();
const networkMismatch = computed(
() => networkName.value != activeNetworkId.value
() => chainId.value != activeNetwork.value?.chainId
);
const checkForNetworkMismatch = () => {
@ -117,7 +117,7 @@ export function useNetwork() {
//@ts-ignore
activeNetworkId.value = localStorage.getItem("network") || "mainnet";
refreshWeb3();
// refreshWeb3()
});
return {

View File

@ -0,0 +1,37 @@
import { useWeb3 } from "@instadapp/vue-web3";
import { injected } from "../connectors";
import { onMounted, ref, watch } from "@nuxtjs/composition-api";
import { SafeAppConnector } from "@gnosis.pm/safe-apps-web3-react";
import { Network, useNetwork } from "./useNetwork";
export function useSafeAppConnection(connector?: SafeAppConnector) {
const { activate, active } = useWeb3();
const { activeNetworkId} = useNetwork();
const tried = ref(false);
onMounted(() => {
connector?.isSafeApp().then(async (loadedInSafe: boolean) => {
if (loadedInSafe) {
await activate(connector, undefined, true).catch(() => {
tried.value = true;
});
activeNetworkId.value = (await connector.getChainId() === 1) ? Network.Mainnet : Network.Polygon;
} else {
tried.value = true;
}
});
});
// if the connection worked, wait until we get confirmation of that to flip the flag
watch([tried, active], () => {
if (!tried.value && active.value) {
tried.value = true;
}
});
return {
tried
};
}

View File

@ -8,7 +8,7 @@ import {
} from "@nuxtjs/composition-api";
import { useDSA } from "./useDSA";
import { useWeb3 } from "./useWeb3";
import { useWeb3 } from "@instadapp/vue-web3";
import SidebarAaveV2Supply from "~/components/sidebar/context/aaveV2/SidebarAaveV2Supply.vue";
import SidebarAaveV2Withdraw from '~/components/sidebar/context/aaveV2/SidebarAaveV2Withdraw.vue'
import SidebarAaveV2Borrow from '~/components/sidebar/context/aaveV2/SidebarAaveV2Borrow.vue'
@ -41,11 +41,16 @@ import SidebarReflexerWithdraw from '~/components/sidebar/context/reflexer/Sideb
import SidebarReflexerBorrow from '~/components/sidebar/context/reflexer/SidebarReflexerBorrow.vue'
import SidebarReflexerPayback from '~/components/sidebar/context/reflexer/SidebarReflexerPayback.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 },

159
composables/useStrategy.ts Normal file
View File

@ -0,0 +1,159 @@
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 "@instadapp/vue-web3";
import { useBigNumber } from "./useBigNumber";
import tokenIdMapping from "~/constant/tokenIdMapping";
import { useFormatting } from "./useFormatting";
import { useNetwork } from "./useNetwork";
export function useStrategy(defineStrategy: DefineStrategy) {
const { library, account } = useWeb3();
const { activeNetworkId } = useNetwork()
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(library, () => strategy.setWeb3(library.value), { immediate: true });
watch(dsa, () => strategy.setDSA(dsa.value), { immediate: true });
watch(
prices,
() => strategy.setProps({ prices: prices[activeNetworkId.value] }),
{ immediate: true }
);
watch(
balances,
() => {
strategy.setProps({
dsaBalances: balances.dsa[activeNetworkId.value],
userBalances: balances.user[activeNetworkId.value]
});
},
{ immediate: true }
);
watch(
activeNetworkId,
() =>
strategy.setProps({
tokens: tokens[activeNetworkId.value].allTokens,
tokenKeys: tokens[activeNetworkId.value].tokenKeys
}),
{ immediate: true }
);
// testing
onMounted(() => {
//@ts-ignore
window.strategy = strategy;
});
return {
strategy,
components,
submit,
error,
pending
};
}

View File

@ -1,14 +1,14 @@
import { useContext, ref, onMounted, computed } from "@nuxtjs/composition-api";
import axios from "axios";
import { activeNetwork, useNetwork } from "./useNetwork";
import { useWeb3 } from "./useWeb3";
import { useWeb3 } from "@instadapp/vue-web3";
import Web3 from "web3";
import { useDSA } from "./useDSA";
const forkId = ref(null);
export function useTenderly() {
const { $config } = useContext();
const { setWeb3, refreshWeb3 } = useWeb3();
const { activate, deactivate, connector, library } = useWeb3();
const { accounts, refreshAccounts } = useDSA();
const canSimulate = computed(
() => $config.TENDERLY_FORK_PATH && $config.TENDERLY_KEY
@ -21,7 +21,7 @@ export function useTenderly() {
}
setTimeout(() => {
setForkId(window.localStorage.getItem("forkId"));
setForkId(window.localStorage.getItem("forkId"), true);
}, 1000);
});
@ -51,39 +51,47 @@ export function useTenderly() {
loading.value = false;
};
const stopSimulation = async () => {
const stopSimulation = async (silent = false) => {
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;
window.localStorage.removeItem("forkId");
await refreshWeb3();
if (!silent && connector.value) {
deactivate();
activate(connector.value);
}
loading.value = false;
};
const setForkId = fork => {
const setForkId = async (fork, silent = false) => {
if (!fork) {
stopSimulation();
stopSimulation(silent);
return;
}
forkId.value = fork;
setWeb3(
new Web3(
new Web3.providers.HttpProvider(
`https://rpc.tenderly.co/fork/${forkId.value}`
)
library.value = new Web3(
new Web3.providers.HttpProvider(
`https://rpc.tenderly.co/fork/${forkId.value}`
)
);
window.localStorage.setItem("forkId", forkId.value);
};
@ -106,6 +114,6 @@ export function useTenderly() {
canSimulate,
startSimulation,
stopSimulation,
loading,
loading
};
}

View File

@ -2,18 +2,18 @@ import { computed } from "@nuxtjs/composition-api";
import atokensV2 from "~/constant/atokensV2";
import tokens from "~/constant/tokens";
import { useBigNumber } from "./useBigNumber";
import { useWeb3 } from "./useWeb3";
import { useNetwork } from "./useNetwork";
export function useToken() {
const { networkName } = useWeb3();
const { activeNetworkId } = useNetwork();
const { toBN, times, minus, div, pow } = useBigNumber();
const getTokenByKey = key =>
tokens[networkName.value].allTokens.find(
tokens[activeNetworkId.value].allTokens.find(
token => String(token.key).toLowerCase() === String(key).toLowerCase()
);
const allATokensV2 = computed(() => atokensV2[networkName.value].allTokens);
const allATokensV2 = computed(() => atokensV2[activeNetworkId.value].allTokens);
function valInt(val, decimals) {
const num = toBN(val);

View File

@ -1,152 +0,0 @@
import { computed, onMounted, ref, watch } from "@nuxtjs/composition-api";
import Web3 from "web3";
import { SafeAppWeb3Modal } from "@gnosis.pm/safe-apps-web3modal";
import { Network } from "./useNetwork";
let web3Modal: SafeAppWeb3Modal;
let web3Provider: any;
let providerOptions = {};
const chains = [
{
name: "mainnet" as Network,
chainId: 1,
displayName: "Mainnet"
},
{
name: "polygon" as Network,
chainId: 137,
node: "https://rpc-mainnet.matic.network",
displayName: "Polygon"
}
];
const active = ref(false);
const chainId = ref<1 | 137>();
const networkName = computed<Network>(
() => chains.find(c => c.chainId === chainId.value)?.name || Network.Mainnet
);
const account = ref<string>();
const web3 = ref<Web3>();
export function setProviders(providers: any) {
providerOptions = providers;
}
export function useWeb3() {
onMounted(async () => {
if (web3Modal) {
return;
}
web3Modal = new SafeAppWeb3Modal({
cacheProvider: true,
providerOptions
});
//@ts-ignore
window.web3Modal = web3Modal;
if (web3Modal.cachedProvider) {
await activate();
}
if (await web3Modal.isSafeApp()) {
await activate();
}
});
const activate = async () => {
web3Provider = await web3Modal.requestProvider();
active.value = true;
if (web3Provider.selectedAddress) {
account.value = web3Provider.selectedAddress;
} else if (web3Provider.accounts && web3Provider.accounts.length) {
account.value = web3Provider.accounts[0];
}
let newWeb3 = new Web3(web3Provider);
//@ts-ignore
chainId.value = await newWeb3.eth.getChainId();
web3.value = newWeb3;
setProvider(web3Provider);
};
const deactivate = async () => {
if (
web3.value &&
web3.value.currentProvider &&
typeof web3.value.currentProvider === "object"
) {
//@ts-ignore
if (typeof web3.value.currentProvider.disconnect === "function") {
//@ts-ignore
web3.value.currentProvider.disconnect();
}
}
web3Modal.clearCachedProvider();
web3Provider = undefined;
active.value = false;
web3.value = undefined;
account.value = undefined;
chainId.value = undefined;
};
const setProvider = provider => {
if (web3Modal.cachedProvider === "walletconnect") {
provider.on("accountsChanged", () => {
location.reload();
});
// Subscribe to networkId change
provider.on("networkChanged", () => {
location.reload();
});
// Subscribe to session connection/open
provider.on("open", () => {
location.reload();
});
// Subscribe to session disconnection/close
provider.on("close", () => {
location.reload();
});
}
// Subscribe to chainId change
provider.on("chainChanged", refreshWeb3);
provider.on("accountsChanged", refreshWeb3);
};
const refreshWeb3 = async () => {
if (!web3Provider) {
return;
}
let newWeb3 = new Web3(web3Provider);
//@ts-ignore
chainId.value = await newWeb3.eth.getChainId();
web3.value = newWeb3;
};
const setWeb3 = (newWeb3: Web3) => {
web3.value = newWeb3;
};
watch(web3, () => {
window.web3 = web3.value;
});
return {
account,
chainId,
web3,
active,
activate,
deactivate,
networkName,
refreshWeb3,
setWeb3
};
}

View File

@ -0,0 +1,10 @@
import { useModal } from "./useModal";
import Web3Modal from "~/components/modal/web3/Web3Modal.vue";
export const useWeb3Modal = () => {
const { showComponent } = useModal();
return {
open: () => showComponent(Web3Modal)
};
};

46
connectors/index.ts Normal file
View File

@ -0,0 +1,46 @@
import { setWeb3LibraryCallback } from "@instadapp/vue-web3";
import { InjectedConnector } from "@web3-react/injected-connector";
import { WalletConnectConnector } from "@web3-react/walletconnect-connector";
import { PortisConnector } from "@web3-react/portis-connector";
import { WalletLinkConnector } from "@web3-react/walletlink-connector";
import INSTADAPP_LOGO_URL from "~/assets/logo/instadapp-logo-icon.svg?inline";
import Web3 from "web3";
import { SafeAppConnector } from "@gnosis.pm/safe-apps-web3-react/dist/connector";
setWeb3LibraryCallback(provider => new Web3(provider));
export const injected = new InjectedConnector({
supportedChainIds: [1, 137]
});
export const walletconnect = new WalletConnectConnector({
rpc: {
1: `https://mainnet.infura.io/v3/${process.env.INFURA_ID}`,
137: "https://rpc-mainnet.maticvigil.com"
},
supportedChainIds: [1, 137]
});
// mainnet only
export const portis = new PortisConnector({
dAppId: process.env.PORTIS_ID as string,
networks: [1]
});
export const walletlink = new WalletLinkConnector({
url: `https://mainnet.infura.io/v3/${process.env.INFURA_ID}`,
appName: "Instadapp",
appLogoUrl: INSTADAPP_LOGO_URL
});
let gnosisSafe = null;
if (process.client) {
gnosisSafe = new SafeAppConnector({
supportedChainIds: [1, 137]
});
}
export { gnosisSafe };

38
constant/wallet.ts Normal file
View File

@ -0,0 +1,38 @@
import { AbstractConnector } from '@web3-react/abstract-connector'
import { injected, walletconnect, portis, walletlink } from '~/connectors'
import METAMASK_ICON_URL from '~/assets/icons/metamask.svg?inline'
import WALLETCONNECT_ICON_URL from '~/assets/icons/wallet-connect-icon.svg?inline'
import PORTIS_ICON_URL from '~/assets/icons/portis.svg?inline'
import COINBASE_ICON_URL from '~/assets/icons/coinbase.svg?inline'
interface WalletInfo {
connector?: AbstractConnector;
name: string;
iconURL: string;
}
export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
METAMASK: {
connector: injected,
name: 'MetaMask',
iconURL: METAMASK_ICON_URL,
},
WALLET_CONNECT: {
connector: walletconnect,
name: 'WalletConnect',
iconURL: WALLETCONNECT_ICON_URL,
},
Portis: {
connector: portis,
name: 'Portis',
iconURL: PORTIS_ICON_URL,
},
WALLET_LINK: {
connector: walletlink,
name: 'Coinbase Wallet',
iconURL: COINBASE_ICON_URL,
},
}

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

@ -5,8 +5,13 @@
<div class="min-h-screen flex flex-col">
<Navbar />
<div v-if="activeNetworkId" class="flex-1 overflow-x-hidden ">
<div class="px-8 md:px-4 max-w-6xl mx-auto py-12">
<Nuxt />
<div
class="px-8 md:px-4 max-w-6xl mx-auto"
:class="{ 'text-center': !active, 'py-12': active }"
>
<Nuxt v-if="active" />
<web-3-modal slim v-else />
</div>
</div>
<div class="flex-1 flex items-center justify-center" v-else>
@ -43,7 +48,10 @@
<NotificationBar />
<div class="fixed bottom-0 right-0 mr-10 mb-28">
<div
v-if="active"
class="fixed bottom-0 right-0 mr-5 md:mr-10 mb-5 md:mb-28"
>
<button
@click="showSidebarBalances"
class="px-9 h-[56px] bg-primary-blue-dark hover:bg-primary-blue-hover text-white rounded-[28px] text-lg font-semibold shadow flex items-center"
@ -80,18 +88,20 @@ import { defineComponent, nextTick, onErrorCaptured, onMounted, useContext, useR
import MakerDAOIcon from '~/assets/icons/makerdao.svg?inline'
import CompoundIcon from '~/assets/icons/compound.svg?inline'
import AaveIcon from '~/assets/icons/aave.svg?inline'
import { useWeb3 } from '~/composables/useWeb3'
import { useWeb3 } from '@instadapp/vue-web3'
import { init as initSidebars, useSidebar } from '~/composables/useSidebar'
import { useBackdrop } from '@/composables/useBackdrop'
import { useNetwork } from "~/composables/useNetwork";
import { useTenderly } from "~/composables/useTenderly";
import { useModal } from "~/composables/useModal";
import { useEagerConnect } from "~/composables/useEagerConnect";
import Web3Modal from "~/components/modal/web3/Web3Modal.vue";
export default defineComponent({
components: {
MakerDAOIcon,
CompoundIcon,
AaveIcon,
Web3Modal,
},
setup() {
const { active, activate, deactivate, chainId } = useWeb3();
@ -100,6 +110,7 @@ export default defineComponent({
const { redirect } = useContext()
const { showSidebarBalances } = useSidebar()
const { showNetworksMismatchDialog } = useModal()
useEagerConnect()
const route = useRoute()
watch(isBackdropShown, () => {
@ -137,7 +148,8 @@ export default defineComponent({
}
}, { immediate: true })
onErrorCaptured(() => {
onErrorCaptured((error) => {
console.error(error)
return false
})

View File

@ -39,6 +39,10 @@ export default {
// Global CSS: https://go.nuxtjs.dev/config-css
css: [
],
env: {
PORTIS_ID: process.env.PORTIS_ID,
INFURA_ID: process.env.INFURA_ID,
},
publicRuntimeConfig: {
INFURA_ID: process.env.INFURA_ID,
@ -50,7 +54,7 @@ export default {
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
"~/plugins/v-click-outside.js",
"~/plugins/web3modal.js",
// "~/plugins/web3modal.js",
{ src: '~/plugins/v-tooltip', mode: 'client' },
{ src: '~/plugins/v-clipboard2', mode: 'client' },
],

View File

@ -10,19 +10,29 @@
},
"dependencies": {
"@gnosis.pm/safe-apps-sdk": "^4.2.0",
"@gnosis.pm/safe-apps-web3-react": "^0.6.2",
"@gnosis.pm/safe-apps-web3modal": "^2.0.0",
"@instadapp/vue-web3": "^0.3.0",
"@nuxtjs/axios": "^5.13.6",
"@nuxtjs/composition-api": "^0.24.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/core": "^6.1.9",
"@web3-react/injected-connector": "^6.0.7",
"@web3-react/portis-connector": "^6.1.9",
"@web3-react/walletconnect-connector": "^6.2.4",
"@web3-react/walletlink-connector": "^6.2.3",
"bignumber.js": "^9.0.1",
"core-js": "^3.15.1",
"css-color-function": "^1.3.3",
"dsa-connect": "^0.4.4-beta.2",
"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

@ -56,7 +56,7 @@ import { useBigNumber } from "~/composables/useBigNumber";
import { useNetwork } from "~/composables/useNetwork";
import { useDSA } from "~/composables/useDSA";
import { use1InchSwap } from "~/composables/swap/use1InchSwap";
import { useWeb3 } from "~/composables/useWeb3";
import { useWeb3 } from "@instadapp/vue-web3";
import { useNotification } from "~/composables/useNotification";
import { useBalances } from "~/composables/useBalances";

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

@ -1,5 +1,5 @@
import WalletConnectProvider from "@walletconnect/web3-provider";
import { setProviders } from "~/composables/useWeb3"
import { setProviders } from "@instadapp/vue-web3"
import WalletLink from 'walletlink'
import Portis from '@portis/web3'
import SVGcoinbase from '~/assets/coinbase.svg'

View File

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

1378
yarn.lock

File diff suppressed because it is too large Load Diff