This commit is contained in:
Georges KABBOUCHI 2021-08-25 14:13:23 +03:00
parent b1b221876c
commit cd003cc517
13 changed files with 269 additions and 73 deletions

View File

@ -1,6 +1,6 @@
<template>
<div>
<div v-if="active" class="relative w-[193px]" v-click-outside="hide">
<div v-if="active && activeAccount" class="relative 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"
@ -142,7 +142,7 @@
</div>
</div>
<button
v-else
v-if="!active"
@click="activate"
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"
>

View File

@ -1,32 +1,70 @@
<template>
<SidebarContextContainer class="h-full overflow-hidden">
<SidebarContextHeader class="xxl:hidden">Strategy</SidebarContextHeader>
<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">
<div class="mx-auto" style="max-width: 296px">
<div class="py-2 sm:py-4">
<div v-for="(input, index) in inputs" :key="index">
<input-amount
:key="index"
:value="input.value"
:token-key="input.token ? input.token.key : 'eth'"
:token-keys="input.tokenKeys ? input.tokenKeys : activeStrategy.getContext()['tokenKeys']"
:placeholder="input.placeholder()"
:error="input.error"
@input="$event => input.onInput($event)"
@tokenKeyChanged="
tokenKey => {
input.onCustomInput({
token: getTokenByKey(tokenKey)
});
}
"
/>
<div class="h-full overflow-y-scroll scrollbar-hover flex flex-col">
<div class="mx-auto mb-9" 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="(input, index) in inputs" :key="index">
<input-amount
:key="index"
:value="input.value"
:token-key="input.token ? input.token.key : 'eth'"
:token-keys="
input.tokenKeys
? input.tokenKeys
: activeStrategy.getContext()['tokenKeys']
"
:placeholder="input.placeholder()"
@input="$event => input.onInput($event)"
@tokenKeyChanged="
tokenKey => {
input.onCustomInput({
token: getTokenByKey(tokenKey)
});
}
"
/>
</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>
<p class="text-xs text-[#9FB0C9] mt-2.5 text-center">Instadapp does not charge a fee for this operation</p>
</div>
</div>
<button @click="submit">Submit</button>
{{ error }}
</div>
</div>
</div>
@ -40,9 +78,9 @@ 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";
export default defineComponent({
components: { InputAmount },
components: { InputAmount, ButtonCTA },
props: {
protocol: {
type: String,
@ -60,7 +98,13 @@ export default defineComponent({
const strategies: DefineStrategy[] =
protocolStrategies[props.protocol] || [];
const { inputs, submit, error, strategy : activeStrategy} = useStrategy(
const {
inputs,
submit,
error,
strategy: activeStrategy,
pending
} = useStrategy(
strategies.find(strategy => strategy.id === props.strategy)
);
@ -70,6 +114,7 @@ export default defineComponent({
submit,
activeStrategy,
getTokenByKey,
pending
};
}
});

View File

@ -12,6 +12,7 @@ import { useBigNumber } from "~/composables/useBigNumber";
import { usePosition } from "~/composables/usePosition";
import { useToken } from "~/composables/useToken";
import { useSorting } from "~/composables/useSorting";
import useEventBus from "../useEventBus";
const {
times,
@ -69,6 +70,7 @@ export function useAaveV2Position(
const { activeAccount } = useDSA();
const { getTokenByKey, allATokensV2 } = useToken();
const { byMaxSupplyOrBorrowDesc } = useSorting()
const { onEvent } = useEventBus()
const resolver = computed(() =>
chainId.value === 1
@ -107,6 +109,7 @@ export function useAaveV2Position(
position.value = await fetchPosition();
};
onEvent("protocol::aaveV2::refresh", refreshPosition);
watch(
web3,

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

@ -3,15 +3,18 @@ import tokens from "~/constant/tokens";
import { buildStrategy, DefineStrategy, IStrategy } from "~/core/strategies";
import { useBalances } from "./useBalances";
import { useDSA } from "./useDSA";
import useEventBus from "./useEventBus";
import { useNotification } from "./useNotification";
import { useSidebar } from "./useSidebar";
import { useToken } from "./useToken";
import { useWeb3 } from "./useWeb3";
export function useStrategy(defineStrategy: DefineStrategy) {
const { web3, networkName, account } = useWeb3();
const { dsa } = useDSA();
const { prices, balances, fetchBalances } = useBalances();
const { close } = useSidebar();
const { valInt, getTokenByKey } = useToken();
const { emitEvent } = useEventBus();
const {
showPendingTransaction,
showConfirmedTransaction
@ -20,6 +23,7 @@ export function useStrategy(defineStrategy: DefineStrategy) {
const strategy = buildStrategy(defineStrategy);
const inputs = ref(strategy.inputs);
const error = ref("");
const pending = ref(false);
strategy.onUpdated(async () => {
await nextTick();
@ -31,21 +35,32 @@ export function useStrategy(defineStrategy: DefineStrategy) {
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) {
console.error(e);
error.value = e.message;
}
pending.value = false;
};
strategy.setProps({
convertTokenAmountToBigNumber: valInt,
getTokenByKey,
})
watch(web3, () => strategy.setWeb3(web3.value), { immediate: true });
watch(dsa, () => strategy.setDSA(dsa.value), { immediate: true });
watch(
@ -83,6 +98,7 @@ export function useStrategy(defineStrategy: DefineStrategy) {
strategy,
inputs,
submit,
error
error,
pending,
};
}

View File

@ -8,6 +8,8 @@ export interface IStrategyContext {
inputs: IStrategyInput<StrategyInputType>[];
dsaTokens?: { [address: string]: IStrategyToken };
userTokens?: { [address: string]: IStrategyToken };
convertTokenAmountToBigNumber?: (value: any, decimals: any) => string;
getTokenByKey?: (key: string) => IStrategyToken;
}
export interface IStrategyToken {
@ -47,17 +49,22 @@ export interface IStrategyInput<InputType extends StrategyInputType> {
}
) => string | void;
defaults?: (context: Omit<IStrategyContext, 'inputs'>) => object;
defaults?: (context: Omit<IStrategyContext, "inputs">) => object;
value?: any;
[key: string]: any;
}
export enum StrategyProtocol {
AAVE_V2 = "aaveV2"
}
export interface IStrategy {
protocol: StrategyProtocol;
id?: string;
name: string;
description: string;
details?: string;
author?: string;
inputs: IStrategyInput<StrategyInputType>[];

View File

@ -6,8 +6,8 @@ export class Strategy {
schema: DefineStrategy;
inputs = [];
context = {
web3: null,
dsa: null
web3: null as Web3,
dsa: null as DSA
};
listeners = [];
@ -62,11 +62,6 @@ export class Strategy {
value: input.value || "",
error: input.error || "",
placeholder: () => {
console.log({
...this.getContext(),
input: this.inputs[idx]
});
return input.placeholder
? input.placeholder({
...this.getContext(),
@ -111,10 +106,14 @@ export class Strategy {
});
}
async spells() {
return await this.schema.spells(this.getContext());
}
async submit(options) {
await this.validate();
const allSpells = await this.schema.spells(this.getContext());
const allSpells = await this.spells();
const spells = this.context.dsa.Spell();

View File

@ -1,56 +1,94 @@
import tokens from "~/constant/tokens";
import { defineStrategy, defineInput, StrategyInputType } from "../../helpers";
import {
defineStrategy,
defineInput,
StrategyInputType,
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>`,
author: "Instadapp Team",
inputs: [
defineInput({
type: StrategyInputType.INPUT_WITH_TOKEN,
name: "Debt",
placeholder: ({ input }) => `${input.token?.symbol} to Payback`,
placeholder: ({ input }) =>
input.token ? `${input.token.symbol} to Payback` : "",
validate: ({ input }) => {
if (!input.token) {
return "Token is required";
return "Debt token is required";
}
if (input.token.balance < input.value) {
return "Your amount exceeds your maximum limit.";
if (!input.value) {
return "Deb amount is required";
}
// if (input.token.balance < input.value) {
// return "Your amount exceeds your maximum limit.";
// }
},
defaults: context => {
return {
token: context.dsaTokens
? Object.values(context.dsaTokens).find(t => t.key === "eth")
: null
};
}
defaults: ({ getTokenByKey }) => ({
token: getTokenByKey?.("eth")
})
}),
defineInput({
type: StrategyInputType.INPUT_WITH_TOKEN,
name: "Collateral",
placeholder: ({ input }) => `${input.token?.symbol} to Withdraw`,
defaults: ({ dsaTokens }) => ({
token: dsaTokens
? Object.values(dsaTokens).find(t => t.key === "dai")
: null
placeholder: ({ input }) =>
input.token ? `${input.token.symbol} to Withdraw` : "",
validate: ({ input }) => {
if (!input.token) {
return "Collateral token is required";
}
if (!input.value) {
return "Collateral amount is required";
}
},
defaults: ({ getTokenByKey }) => ({
token: getTokenByKey?.("dai")
})
})
],
spells: async ({ inputs }) => {
spells: async ({ inputs, convertTokenAmountToBigNumber }) => {
return [
{
connector: "aave_v2",
method: "deposit",
args: [inputs[0].token.address, inputs[0].value, 0, 0]
args: [
inputs[0].token.address,
convertTokenAmountToBigNumber(
inputs[0].value,
inputs[0].token.decimals
),
0,
0
]
},
{
connector: "aave_v2",
method: "borrow",
args: [inputs[1].token.address, inputs[1].value, 1, 0, 0]
args: [
inputs[1].token.address,
convertTokenAmountToBigNumber(
inputs[1].value,
inputs[1].token.decimals
),
2,
0,
0
]
}
];
}

View File

@ -1,16 +1,29 @@
import tokens from "~/constant/tokens";
import { defineStrategy, defineInput, StrategyInputType } from "../../helpers";
import {
defineStrategy,
defineInput,
StrategyInputType,
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",
details: `<p class="text-center">This strategy executes:</p>
<ul>
<li>Payback debt</li>
<li>Withdraw collateral</li>
</ul>`,
inputs: [
defineInput({
type: StrategyInputType.INPUT_WITH_TOKEN,
name: "Debt",
placeholder: ({ input }) => `${input.token.symbol} to Payback`,
placeholder: ({ input }) =>
input.token ? `${input.token.symbol} to Payback` : "",
validate: ({ input }) => {
if (!input.token) {
return "Token is required";
@ -20,27 +33,49 @@ export default defineStrategy({
return "Your amount exceeds your maximum limit.";
}
},
token: tokens.mainnet.getTokenByKey("eth")
defaults: ({ getTokenByKey }) => ({
token: getTokenByKey?.("dai")
})
}),
defineInput({
type: StrategyInputType.INPUT_WITH_TOKEN,
name: "Collateral",
placeholder: ({ input }) => `${input.token.symbol} to Withdraw`,
token: tokens.mainnet.getTokenByKey("dai")
placeholder: ({ input }) =>
input.token ? `${input.token.symbol} to Withdraw` : "",
defaults: ({ getTokenByKey }) => ({
token: getTokenByKey?.("eth")
})
})
],
spells: async ({ inputs }) => {
spells: async ({ inputs, convertTokenAmountToBigNumber }) => {
return [
{
connector: "aave_v2",
method: "payback",
args: [inputs[0].token.address, inputs[0].value, 1, 0, 0]
args: [
inputs[0].token.address,
convertTokenAmountToBigNumber(
inputs[0].value,
inputs[0].token.decimals
),
12,
0,
0
]
},
{
connector: "aave_v2",
method: "withdraw",
args: [inputs[1].token.address, inputs[1].value, 0, 0]
args: [
inputs[1].token.address,
convertTokenAmountToBigNumber(
inputs[1].value,
inputs[1].token.decimals
),
0,
0
]
}
];
}

View File

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

View File

@ -13,6 +13,7 @@
"@nuxtjs/composition-api": "^0.24.7",
"@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",
"bignumber.js": "^9.0.1",
@ -22,6 +23,7 @@
"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

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

View File

@ -1707,6 +1707,16 @@
dependencies:
mini-svg-data-uri "^1.2.3"
"@tailwindcss/typography@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.4.1.tgz#51ddbceea6a0ee9902c649dbe58871c81a831212"
integrity sha512-ovPPLUhs7zAIJfr0y1dbGlyCuPhpuv/jpBoFgqAc658DWGGrOBWBMpAWLw2KlzbNeVk4YBJMzue1ekvIbdw6XA==
dependencies:
lodash.castarray "^4.4.0"
lodash.isplainobject "^4.0.6"
lodash.merge "^4.6.2"
lodash.uniq "^4.5.0"
"@types/anymatch@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-3.0.0.tgz#c95ff14401dbb2869913afac3935af4ad0d37f1a"
@ -7277,11 +7287,21 @@ lodash._reinterpolate@^3.0.0:
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
lodash.castarray@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115"
integrity sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU=
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.kebabcase@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
@ -7292,6 +7312,11 @@ lodash.memoize@^4.1.2:
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash.template@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
@ -11030,7 +11055,7 @@ timsort@^0.3.0:
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
tiny-emitter@^2.0.0:
tiny-emitter@^2.0.0, tiny-emitter@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==