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> <template>
<div> <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 <button
type="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" 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>
</div> </div>
<button <button
v-else v-if="!active"
@click="activate" @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" 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> <template>
<SidebarContextContainer class="h-full overflow-hidden"> <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="h-full overflow-y-scroll scrollbar-hover flex flex-col">
<div class="mx-auto" style="max-width: 296px"> <div class="mx-auto mb-9" style="width: 296px">
<div class="py-2 sm:py-4"> <div
<div v-for="(input, index) in inputs" :key="index"> class="flex-shrink-0 font-medium prose prose-sm text-primary-gray"
<input-amount v-if="activeStrategy.schema.details"
:key="index" v-html="activeStrategy.schema.details"
:value="input.value" ></div>
:token-key="input.token ? input.token.key : 'eth'" </div>
:token-keys="input.tokenKeys ? input.tokenKeys : activeStrategy.getContext()['tokenKeys']" <div class="flex-1 bg-[#1874FF] bg-opacity-[0.04]">
:placeholder="input.placeholder()" <div class="mx-auto h-full" style="max-width: 296px">
:error="input.error" <div class="space-y-4 py-9 h-full flex flex-col">
@input="$event => input.onInput($event)" <div class="flex-1">
@tokenKeyChanged=" <div v-for="(input, index) in inputs" :key="index">
tokenKey => { <input-amount
input.onCustomInput({ :key="index"
token: getTokenByKey(tokenKey) :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> </div>
<button @click="submit">Submit</button>
{{ error }}
</div> </div>
</div> </div>
</div> </div>
@ -40,9 +78,9 @@ import { protocolStrategies, DefineStrategy } from "~/core/strategies";
import { useStrategy } from "~/composables/useStrategy"; import { useStrategy } from "~/composables/useStrategy";
import InputAmount from "~/components/common/input/InputAmount.vue"; import InputAmount from "~/components/common/input/InputAmount.vue";
import { useToken } from "~/composables/useToken"; import { useToken } from "~/composables/useToken";
import ButtonCTA from "~/components/common/input/ButtonCTA.vue";
export default defineComponent({ export default defineComponent({
components: { InputAmount }, components: { InputAmount, ButtonCTA },
props: { props: {
protocol: { protocol: {
type: String, type: String,
@ -60,7 +98,13 @@ export default defineComponent({
const strategies: DefineStrategy[] = const strategies: DefineStrategy[] =
protocolStrategies[props.protocol] || []; 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) strategies.find(strategy => strategy.id === props.strategy)
); );
@ -70,6 +114,7 @@ export default defineComponent({
submit, submit,
activeStrategy, activeStrategy,
getTokenByKey, getTokenByKey,
pending
}; };
} }
}); });

View File

@ -12,6 +12,7 @@ import { useBigNumber } from "~/composables/useBigNumber";
import { usePosition } from "~/composables/usePosition"; import { usePosition } from "~/composables/usePosition";
import { useToken } from "~/composables/useToken"; import { useToken } from "~/composables/useToken";
import { useSorting } from "~/composables/useSorting"; import { useSorting } from "~/composables/useSorting";
import useEventBus from "../useEventBus";
const { const {
times, times,
@ -69,6 +70,7 @@ export function useAaveV2Position(
const { activeAccount } = useDSA(); const { activeAccount } = useDSA();
const { getTokenByKey, allATokensV2 } = useToken(); const { getTokenByKey, allATokensV2 } = useToken();
const { byMaxSupplyOrBorrowDesc } = useSorting() const { byMaxSupplyOrBorrowDesc } = useSorting()
const { onEvent } = useEventBus()
const resolver = computed(() => const resolver = computed(() =>
chainId.value === 1 chainId.value === 1
@ -107,6 +109,7 @@ export function useAaveV2Position(
position.value = await fetchPosition(); position.value = await fetchPosition();
}; };
onEvent("protocol::aaveV2::refresh", refreshPosition);
watch( watch(
web3, 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 { buildStrategy, DefineStrategy, IStrategy } from "~/core/strategies";
import { useBalances } from "./useBalances"; import { useBalances } from "./useBalances";
import { useDSA } from "./useDSA"; import { useDSA } from "./useDSA";
import useEventBus from "./useEventBus";
import { useNotification } from "./useNotification"; import { useNotification } from "./useNotification";
import { useSidebar } from "./useSidebar"; import { useSidebar } from "./useSidebar";
import { useToken } from "./useToken";
import { useWeb3 } from "./useWeb3"; import { useWeb3 } from "./useWeb3";
export function useStrategy(defineStrategy: DefineStrategy) { export function useStrategy(defineStrategy: DefineStrategy) {
const { web3, networkName, account } = useWeb3(); const { web3, networkName, account } = useWeb3();
const { dsa } = useDSA(); const { dsa } = useDSA();
const { prices, balances, fetchBalances } = useBalances(); const { prices, balances, fetchBalances } = useBalances();
const { close } = useSidebar(); const { close } = useSidebar();
const { valInt, getTokenByKey } = useToken();
const { emitEvent } = useEventBus();
const { const {
showPendingTransaction, showPendingTransaction,
showConfirmedTransaction showConfirmedTransaction
@ -20,6 +23,7 @@ export function useStrategy(defineStrategy: DefineStrategy) {
const strategy = buildStrategy(defineStrategy); const strategy = buildStrategy(defineStrategy);
const inputs = ref(strategy.inputs); const inputs = ref(strategy.inputs);
const error = ref(""); const error = ref("");
const pending = ref(false);
strategy.onUpdated(async () => { strategy.onUpdated(async () => {
await nextTick(); await nextTick();
@ -31,21 +35,32 @@ export function useStrategy(defineStrategy: DefineStrategy) {
const submit = async () => { const submit = async () => {
error.value = ""; error.value = "";
pending.value = true;
try { try {
const tx = await strategy.submit({ const tx = await strategy.submit({
onReceipt: async () => { onReceipt: async () => {
showConfirmedTransaction(tx); showConfirmedTransaction(tx);
await fetchBalances(true); await fetchBalances(true);
emitEvent(`protocol::${strategy.schema.protocol}::refresh`,{});
}, },
from: account.value from: account.value
}); });
showPendingTransaction(tx); showPendingTransaction(tx);
close(); close();
} catch (e) { } catch (e) {
console.error(e);
error.value = e.message; error.value = e.message;
} }
pending.value = false;
}; };
strategy.setProps({
convertTokenAmountToBigNumber: valInt,
getTokenByKey,
})
watch(web3, () => strategy.setWeb3(web3.value), { immediate: true }); watch(web3, () => strategy.setWeb3(web3.value), { immediate: true });
watch(dsa, () => strategy.setDSA(dsa.value), { immediate: true }); watch(dsa, () => strategy.setDSA(dsa.value), { immediate: true });
watch( watch(
@ -83,6 +98,7 @@ export function useStrategy(defineStrategy: DefineStrategy) {
strategy, strategy,
inputs, inputs,
submit, submit,
error error,
pending,
}; };
} }

View File

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

View File

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

View File

@ -1,56 +1,94 @@
import tokens from "~/constant/tokens"; import {
import { defineStrategy, defineInput, StrategyInputType } from "../../helpers"; defineStrategy,
defineInput,
StrategyInputType,
StrategyProtocol
} from "../../helpers";
export default defineStrategy({ export default defineStrategy({
protocol: StrategyProtocol.AAVE_V2,
name: "Deposit & Borrow", name: "Deposit & Borrow",
description: "Deposit collateral & borrow asset in a single txn.", 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", author: "Instadapp Team",
inputs: [ inputs: [
defineInput({ defineInput({
type: StrategyInputType.INPUT_WITH_TOKEN, type: StrategyInputType.INPUT_WITH_TOKEN,
name: "Debt", name: "Debt",
placeholder: ({ input }) => `${input.token?.symbol} to Payback`, placeholder: ({ input }) =>
input.token ? `${input.token.symbol} to Payback` : "",
validate: ({ input }) => { validate: ({ input }) => {
if (!input.token) { if (!input.token) {
return "Token is required"; return "Debt token is required";
} }
if (input.token.balance < input.value) { if (!input.value) {
return "Your amount exceeds your maximum limit."; return "Deb amount is required";
} }
// if (input.token.balance < input.value) {
// return "Your amount exceeds your maximum limit.";
// }
}, },
defaults: context => { defaults: ({ getTokenByKey }) => ({
return { token: getTokenByKey?.("eth")
token: context.dsaTokens })
? Object.values(context.dsaTokens).find(t => t.key === "eth")
: null
};
}
}), }),
defineInput({ defineInput({
type: StrategyInputType.INPUT_WITH_TOKEN, type: StrategyInputType.INPUT_WITH_TOKEN,
name: "Collateral", name: "Collateral",
placeholder: ({ input }) => `${input.token?.symbol} to Withdraw`, placeholder: ({ input }) =>
defaults: ({ dsaTokens }) => ({ input.token ? `${input.token.symbol} to Withdraw` : "",
token: dsaTokens validate: ({ input }) => {
? Object.values(dsaTokens).find(t => t.key === "dai") if (!input.token) {
: null 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 [ return [
{ {
connector: "aave_v2", connector: "aave_v2",
method: "deposit", 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", connector: "aave_v2",
method: "borrow", 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 tokens from "~/constant/tokens";
import { defineStrategy, defineInput, StrategyInputType } from "../../helpers"; import {
defineStrategy,
defineInput,
StrategyInputType,
StrategyProtocol
} from "../../helpers";
export default defineStrategy({ export default defineStrategy({
protocol: StrategyProtocol.AAVE_V2,
name: "Payback & Withdraw", name: "Payback & Withdraw",
description: "Payback debt & withdraw collateral in a single txn.", description: "Payback debt & withdraw collateral in a single txn.",
author: "Instadapp Team", author: "Instadapp Team",
details: `<p class="text-center">This strategy executes:</p>
<ul>
<li>Payback debt</li>
<li>Withdraw collateral</li>
</ul>`,
inputs: [ inputs: [
defineInput({ defineInput({
type: StrategyInputType.INPUT_WITH_TOKEN, type: StrategyInputType.INPUT_WITH_TOKEN,
name: "Debt", name: "Debt",
placeholder: ({ input }) => `${input.token.symbol} to Payback`, placeholder: ({ input }) =>
input.token ? `${input.token.symbol} to Payback` : "",
validate: ({ input }) => { validate: ({ input }) => {
if (!input.token) { if (!input.token) {
return "Token is required"; return "Token is required";
@ -20,27 +33,49 @@ export default defineStrategy({
return "Your amount exceeds your maximum limit."; return "Your amount exceeds your maximum limit.";
} }
}, },
token: tokens.mainnet.getTokenByKey("eth") defaults: ({ getTokenByKey }) => ({
token: getTokenByKey?.("dai")
})
}), }),
defineInput({ defineInput({
type: StrategyInputType.INPUT_WITH_TOKEN, type: StrategyInputType.INPUT_WITH_TOKEN,
name: "Collateral", name: "Collateral",
placeholder: ({ input }) => `${input.token.symbol} to Withdraw`, placeholder: ({ input }) =>
token: tokens.mainnet.getTokenByKey("dai") input.token ? `${input.token.symbol} to Withdraw` : "",
defaults: ({ getTokenByKey }) => ({
token: getTokenByKey?.("eth")
})
}) })
], ],
spells: async ({ inputs }) => { spells: async ({ inputs, convertTokenAmountToBigNumber }) => {
return [ return [
{ {
connector: "aave_v2", connector: "aave_v2",
method: "payback", 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", connector: "aave_v2",
method: "withdraw", 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 }) }, { immediate: true })
onErrorCaptured(() => { onErrorCaptured((error) => {
console.error(error)
return false return false
}) })

View File

@ -13,6 +13,7 @@
"@nuxtjs/composition-api": "^0.24.7", "@nuxtjs/composition-api": "^0.24.7",
"@portis/web3": "^4.0.5", "@portis/web3": "^4.0.5",
"@tailwindcss/forms": "^0.3.3", "@tailwindcss/forms": "^0.3.3",
"@tailwindcss/typography": "^0.4.1",
"@vueuse/core": "^5.1.4", "@vueuse/core": "^5.1.4",
"@walletconnect/web3-provider": "^1.4.1", "@walletconnect/web3-provider": "^1.4.1",
"bignumber.js": "^9.0.1", "bignumber.js": "^9.0.1",
@ -22,6 +23,7 @@
"nuxt": "^2.15.7", "nuxt": "^2.15.7",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"slugify": "^1.6.0", "slugify": "^1.6.0",
"tiny-emitter": "^2.1.0",
"v-click-outside": "^3.1.2", "v-click-outside": "^3.1.2",
"v-tooltip": "^2.1.3", "v-tooltip": "^2.1.3",
"vue-clipboard2": "^0.3.1", "vue-clipboard2": "^0.3.1",

View File

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

View File

@ -1707,6 +1707,16 @@
dependencies: dependencies:
mini-svg-data-uri "^1.2.3" 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@*": "@types/anymatch@*":
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-3.0.0.tgz#c95ff14401dbb2869913afac3935af4ad0d37f1a" 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" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= 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: lodash.debounce@^4.0.8:
version "4.0.8" version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= 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: lodash.kebabcase@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" 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" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= 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: lodash.template@^4.5.0:
version "4.5.0" version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" 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" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
tiny-emitter@^2.0.0: tiny-emitter@^2.0.0, tiny-emitter@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==