From 4951b6733b6a937e7e20c777bfa383791faab3bc Mon Sep 17 00:00:00 2001 From: Georges KABBOUCHI Date: Sun, 22 Aug 2021 14:27:02 +0300 Subject: [PATCH 01/29] wip --- core/strategies/helpers/strategy.ts | 49 +++++++++++++++++++ core/strategies/index.ts | 5 ++ .../protocols/aave-v2/deposit-and-borrow.ts | 49 +++++++++++++++++++ core/strategies/protocols/aave-v2/index.ts | 6 +++ 4 files changed, 109 insertions(+) create mode 100644 core/strategies/helpers/strategy.ts create mode 100644 core/strategies/index.ts create mode 100644 core/strategies/protocols/aave-v2/deposit-and-borrow.ts create mode 100644 core/strategies/protocols/aave-v2/index.ts diff --git a/core/strategies/helpers/strategy.ts b/core/strategies/helpers/strategy.ts new file mode 100644 index 0000000..79cf986 --- /dev/null +++ b/core/strategies/helpers/strategy.ts @@ -0,0 +1,49 @@ +import DSA from "dsa-connect"; +import Web3 from "web3"; + +export interface IStrategyContext { + dsa: typeof DSA; + web3: Web3; + inputs: IStrategyInput[]; +} + +export interface IStrategyToken { + address: string + key: string + symbol: string + balance: string + + supply: string + borrow: string +} + +export enum StrategyInputType { + INPUT = "input", + INPUT_WITH_TOKEN = "input-with-token" +} + +export interface IStrategyInput { + type: StrategyInputType; + name: string; + placeholder: + | string + | ((context: IStrategyContext & { input: IStrategyInput }) => string); + validate?: ((context: IStrategyContext & { input: IStrategyInput }) => boolean|string); + // If type is "input-with-token", this is the token + token?: IStrategyToken; + value?: any; +} + +export interface IStrategy { + name: string; + description: string; + author?: string; + + inputs: IStrategyInput[]; + + spells: (context: IStrategyContext) => any; +} + +export function defineStrategy(strategy: IStrategy): IStrategy { + return strategy; +} diff --git a/core/strategies/index.ts b/core/strategies/index.ts new file mode 100644 index 0000000..47e70d5 --- /dev/null +++ b/core/strategies/index.ts @@ -0,0 +1,5 @@ +import AaveV2 from "./protocols/aave-v2" + +export const strategies = { + aaveV2 : AaveV2, +} \ No newline at end of file diff --git a/core/strategies/protocols/aave-v2/deposit-and-borrow.ts b/core/strategies/protocols/aave-v2/deposit-and-borrow.ts new file mode 100644 index 0000000..76582c6 --- /dev/null +++ b/core/strategies/protocols/aave-v2/deposit-and-borrow.ts @@ -0,0 +1,49 @@ +import { defineStrategy, StrategyInputType } from "../../helpers/strategy"; + +export default defineStrategy({ + name: "Deposit & Borrow", + description: "Deposit collateral & borrow asset in a single txn.", + author: "Instadapp Team", + + inputs: [ + { + type: StrategyInputType.INPUT_WITH_TOKEN, + name: "Debt", + placeholder: ({ input }) => `${input.token.symbol} to Payback`, + validate: ({ input }) => { + if (!input.token) { + return "Token is required"; + } + + if (input.token.balance < input.value) { + return "Your amount exceeds your maximum limit."; + } + + return true; + } + }, + { + type: StrategyInputType.INPUT_WITH_TOKEN, + name: "Collateral", + placeholder: ({ input }) => `${input.token.symbol} to Withdraw` + } + ], + + spells: async ({ dsa, inputs }) => { + const spells = dsa.Spells(); + + spells.add({ + connector: "aave_v2", + method: "deposit", + args: [inputs[0].token.address, inputs[0].value, 0, 0] + }); + + spells.add({ + connector: "aave_v2", + method: "borrow", + args: [inputs[1].token.address, inputs[1].value, 0, 0, 0] + }); + + return spells; + } +}); diff --git a/core/strategies/protocols/aave-v2/index.ts b/core/strategies/protocols/aave-v2/index.ts new file mode 100644 index 0000000..f6bac2d --- /dev/null +++ b/core/strategies/protocols/aave-v2/index.ts @@ -0,0 +1,6 @@ +import depositAndBorrow from "./deposit-and-borrow" + +export default [ + depositAndBorrow +] + \ No newline at end of file From 48602b9d6cd593fedced63188ce0ab8cadc4bde2 Mon Sep 17 00:00:00 2001 From: Georges KABBOUCHI Date: Sun, 22 Aug 2021 15:54:23 +0300 Subject: [PATCH 02/29] update --- .../context/strategy/SidebarStrategy.vue | 51 +++++++++++++++++ .../strategy/SidebarStrategySelection.vue | 56 +++++++++++++++++++ composables/useSidebar.ts | 5 ++ core/strategies/helpers/strategy.ts | 19 ++++++- core/strategies/index.ts | 2 +- .../protocols/aave-v2/deposit-and-borrow.ts | 4 +- package.json | 1 + yarn.lock | 5 ++ 8 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 components/sidebar/context/strategy/SidebarStrategy.vue create mode 100644 components/sidebar/context/strategy/SidebarStrategySelection.vue diff --git a/components/sidebar/context/strategy/SidebarStrategy.vue b/components/sidebar/context/strategy/SidebarStrategy.vue new file mode 100644 index 0000000..cc9ac52 --- /dev/null +++ b/components/sidebar/context/strategy/SidebarStrategy.vue @@ -0,0 +1,51 @@ + + + \ No newline at end of file diff --git a/components/sidebar/context/strategy/SidebarStrategySelection.vue b/components/sidebar/context/strategy/SidebarStrategySelection.vue new file mode 100644 index 0000000..e1218e7 --- /dev/null +++ b/components/sidebar/context/strategy/SidebarStrategySelection.vue @@ -0,0 +1,56 @@ + + + \ No newline at end of file diff --git a/composables/useSidebar.ts b/composables/useSidebar.ts index 98fd763..517e473 100644 --- a/composables/useSidebar.ts +++ b/composables/useSidebar.ts @@ -35,11 +35,16 @@ import SidebarLiquityTroveWithdraw from '~/components/sidebar/context/liquity/Si import SidebarLiquityTroveBorrow from '~/components/sidebar/context/liquity/SidebarLiquityTroveBorrow.vue' import SidebarLiquityTrovePayback from '~/components/sidebar/context/liquity/SidebarLiquityTrovePayback.vue' +import SidebarStrategySelection from '~/components/sidebar/context/strategy/SidebarStrategySelection.vue' +import SidebarStrategy from '~/components/sidebar/context/strategy/SidebarStrategy.vue' const sidebars = { "#overview" : {component: SidebarOverview, back : false, close : true }, "#deposit-overview": {component: SidebarDepositOverview, back: { hash: 'overview' } }, '#withdraw-token': { component: SidebarWithdraw, back: { hash: 'overview' } }, + '#strategies': { component: SidebarStrategySelection }, + '#strategy': { component: SidebarStrategy }, + "/aave-v2": { component: null }, "/aave-v2#supply": { component: SidebarAaveV2Supply }, "/aave-v2#borrow": { component: SidebarAaveV2Borrow }, diff --git a/core/strategies/helpers/strategy.ts b/core/strategies/helpers/strategy.ts index 79cf986..b1f94e1 100644 --- a/core/strategies/helpers/strategy.ts +++ b/core/strategies/helpers/strategy.ts @@ -1,6 +1,6 @@ import DSA from "dsa-connect"; import Web3 from "web3"; - +import slugify from "slugify" export interface IStrategyContext { dsa: typeof DSA; web3: Web3; @@ -22,6 +22,13 @@ export enum StrategyInputType { INPUT_WITH_TOKEN = "input-with-token" } +// type InputTypes = { +// [StrategyInputType.INPUT] : { +// token?: IStrategyToken; +// value?: any; +// }; +// } + export interface IStrategyInput { type: StrategyInputType; name: string; @@ -32,9 +39,12 @@ export interface IStrategyInput { // If type is "input-with-token", this is the token token?: IStrategyToken; value?: any; + + [key: string]: any; } export interface IStrategy { + id?: string; name: string; description: string; author?: string; @@ -42,8 +52,15 @@ export interface IStrategy { inputs: IStrategyInput[]; spells: (context: IStrategyContext) => any; + + submitText?: string; } export function defineStrategy(strategy: IStrategy): IStrategy { + + if(! strategy.id){ + strategy.id = slugify(strategy.name).toLowerCase(); + } + return strategy; } diff --git a/core/strategies/index.ts b/core/strategies/index.ts index 47e70d5..03f0341 100644 --- a/core/strategies/index.ts +++ b/core/strategies/index.ts @@ -1,5 +1,5 @@ import AaveV2 from "./protocols/aave-v2" -export const strategies = { +export const protocolStrategies = { aaveV2 : AaveV2, } \ No newline at end of file diff --git a/core/strategies/protocols/aave-v2/deposit-and-borrow.ts b/core/strategies/protocols/aave-v2/deposit-and-borrow.ts index 76582c6..919a6ef 100644 --- a/core/strategies/protocols/aave-v2/deposit-and-borrow.ts +++ b/core/strategies/protocols/aave-v2/deposit-and-borrow.ts @@ -16,8 +16,8 @@ export default defineStrategy({ } if (input.token.balance < input.value) { - return "Your amount exceeds your maximum limit."; - } + return "Your amount exceeds your maximum limit."; + } return true; } diff --git a/package.json b/package.json index ebbc75f..e65ae6f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "dsa-connect": "^0.4.3", "nuxt": "^2.15.7", "qrcode": "^1.4.4", + "slugify": "^1.6.0", "v-click-outside": "^3.1.2", "v-tooltip": "^2.1.3", "vue-clipboard2": "^0.3.1", diff --git a/yarn.lock b/yarn.lock index 35f518d..01c4cd5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10410,6 +10410,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slugify@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.0.tgz#6bdf8ed01dabfdc46425b67e3320b698832ff893" + integrity sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang== + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" From 59bc5369db3cd8e48dc85ba17503af9722646a10 Mon Sep 17 00:00:00 2001 From: Georges KABBOUCHI Date: Sun, 22 Aug 2021 16:49:31 +0300 Subject: [PATCH 03/29] wip --- .../context/strategy/SidebarStrategy.vue | 54 +++-- core/strategies/helpers/strategy.ts | 67 ++++-- core/strategies/index.ts | 4 +- pages/aave-v2.vue | 45 +++- pages/mainnet/aave-v2.vue | 216 ------------------ 5 files changed, 128 insertions(+), 258 deletions(-) delete mode 100644 pages/mainnet/aave-v2.vue diff --git a/components/sidebar/context/strategy/SidebarStrategy.vue b/components/sidebar/context/strategy/SidebarStrategy.vue index cc9ac52..f21bc7d 100644 --- a/components/sidebar/context/strategy/SidebarStrategy.vue +++ b/components/sidebar/context/strategy/SidebarStrategy.vue @@ -6,46 +6,62 @@
{{ $props }}
+
{{ selectedStrategy }}
+ +
- \ No newline at end of file diff --git a/core/strategies/helpers/strategy.ts b/core/strategies/helpers/strategy.ts index b1f94e1..b9043ba 100644 --- a/core/strategies/helpers/strategy.ts +++ b/core/strategies/helpers/strategy.ts @@ -1,6 +1,6 @@ import DSA from "dsa-connect"; import Web3 from "web3"; -import slugify from "slugify" +import slugify from "slugify"; export interface IStrategyContext { dsa: typeof DSA; web3: Web3; @@ -8,13 +8,13 @@ export interface IStrategyContext { } export interface IStrategyToken { - address: string - key: string - symbol: string - balance: string + address: string; + key: string; + symbol: string; + balance: string; - supply: string - borrow: string + supply: string; + borrow: string; } export enum StrategyInputType { @@ -35,7 +35,9 @@ export interface IStrategyInput { placeholder: | string | ((context: IStrategyContext & { input: IStrategyInput }) => string); - validate?: ((context: IStrategyContext & { input: IStrategyInput }) => boolean|string); + validate?: ( + context: IStrategyContext & { input: IStrategyInput } + ) => boolean | string; // If type is "input-with-token", this is the token token?: IStrategyToken; value?: any; @@ -56,11 +58,50 @@ export interface IStrategy { submitText?: string; } -export function defineStrategy(strategy: IStrategy): IStrategy { +export function defineStrategy(strategy: IStrategy) { + return { + ...strategy, + id: strategy.id ? strategy.id : slugify(strategy.name).toLowerCase(), + inputs: strategy.inputs.map(input => ({ + ...input, + value: null, + onInput: (val: any) => { + input.value = val; + } + })), + submit: async (context: Pick) => { + await this.validate({ + ...context, + inputs: strategy.inputs + }); - if(! strategy.id){ - strategy.id = slugify(strategy.name).toLowerCase(); - } + const spells = strategy.spells({ + ...context, + inputs: strategy.inputs + }); - return strategy; + return await context.dsa.cast({ + spells, + onReceipt: this.onReceipt + }); + }, + validate: async (context: IStrategyContext) => { + for (const input of this.inputs) { + const result = await input.validate({ + ...context, + inputs: strategy.inputs, + input + }); + + if (result !== true) { + throw new Error(result || "Error has occurred"); + } + } + }, + onReceipt: (txHash: string, txReceipt: any) => { + // do something + } + }; } + +export type DefineStrategy = ReturnType; diff --git a/core/strategies/index.ts b/core/strategies/index.ts index 03f0341..6fc2f7e 100644 --- a/core/strategies/index.ts +++ b/core/strategies/index.ts @@ -2,4 +2,6 @@ import AaveV2 from "./protocols/aave-v2" export const protocolStrategies = { aaveV2 : AaveV2, -} \ No newline at end of file +} + +export * from "./helpers/strategy" \ No newline at end of file diff --git a/pages/aave-v2.vue b/pages/aave-v2.vue index e1c64c0..3b00a21 100644 --- a/pages/aave-v2.vue +++ b/pages/aave-v2.vue @@ -10,18 +10,43 @@ -
-
+
+
- +
+ +
+

Aave v2

-

Aave v2

+ + + Strategies + + + + +
@@ -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 { diff --git a/pages/mainnet/aave-v2.vue b/pages/mainnet/aave-v2.vue deleted file mode 100644 index 46501dc..0000000 --- a/pages/mainnet/aave-v2.vue +++ /dev/null @@ -1,216 +0,0 @@ - - - From b5b7141661f12878842363d1df15b4341842b549 Mon Sep 17 00:00:00 2001 From: Georges KABBOUCHI Date: Sun, 22 Aug 2021 18:45:37 +0300 Subject: [PATCH 04/29] wip --- .../context/strategy/SidebarStrategy.vue | 3 +- core/strategies/helpers/strategy.ts | 81 +++++++++++++------ .../protocols/aave-v2/deposit-and-borrow.ts | 45 +++++------ 3 files changed, 81 insertions(+), 48 deletions(-) diff --git a/components/sidebar/context/strategy/SidebarStrategy.vue b/components/sidebar/context/strategy/SidebarStrategy.vue index f21bc7d..d8d474c 100644 --- a/components/sidebar/context/strategy/SidebarStrategy.vue +++ b/components/sidebar/context/strategy/SidebarStrategy.vue @@ -14,6 +14,7 @@ :key="index" :value="input.value" @input="$event => input.onInput($event.target.value)" + :placeholder="input.placeholder()" />
@@ -53,7 +54,7 @@ export default defineComponent({ strategies.find(strategy => strategy.id === props.strategy) ); - watch(() => { + watch([], () => { selectedStrategy.value = strategies.find( strategy => strategy.id === props.strategy ); diff --git a/core/strategies/helpers/strategy.ts b/core/strategies/helpers/strategy.ts index b9043ba..ec31728 100644 --- a/core/strategies/helpers/strategy.ts +++ b/core/strategies/helpers/strategy.ts @@ -1,10 +1,10 @@ -import DSA from "dsa-connect"; +import DSA, { Spell } from "dsa-connect"; import Web3 from "web3"; import slugify from "slugify"; export interface IStrategyContext { - dsa: typeof DSA; + dsa: DSA; web3: Web3; - inputs: IStrategyInput[]; + inputs: IStrategyInput[]; } export interface IStrategyToken { @@ -22,24 +22,27 @@ export enum StrategyInputType { INPUT_WITH_TOKEN = "input-with-token" } -// type InputTypes = { -// [StrategyInputType.INPUT] : { -// token?: IStrategyToken; -// value?: any; -// }; -// } +export type StrategyInputParameterMap = { + [StrategyInputType.INPUT]: {}; -export interface IStrategyInput { - type: StrategyInputType; + [StrategyInputType.INPUT_WITH_TOKEN]: { + token?: IStrategyToken; + }; +}; + +export interface IStrategyInput { + type: InputType; name: string; - placeholder: - | string - | ((context: IStrategyContext & { input: IStrategyInput }) => string); + placeholder: ( + context: IStrategyContext & { + input: IStrategyInput & StrategyInputParameterMap[InputType]; + } + ) => string; validate?: ( - context: IStrategyContext & { input: IStrategyInput } - ) => boolean | string; - // If type is "input-with-token", this is the token - token?: IStrategyToken; + context: IStrategyContext & { + input: IStrategyInput & StrategyInputParameterMap[InputType]; + } + ) => string | void; value?: any; [key: string]: any; @@ -51,41 +54,71 @@ export interface IStrategy { description: string; author?: string; - inputs: IStrategyInput[]; + inputs: IStrategyInput[]; - spells: (context: IStrategyContext) => any; + spells: (context: IStrategyContext) => Promise | Spell[]; submitText?: string; } +export function defineInput( + input: IStrategyInput +) { + return input as IStrategyInput; +} + export function defineStrategy(strategy: IStrategy) { + const context = { + web3: null, + dsa: null + }; + return { ...strategy, - id: strategy.id ? strategy.id : slugify(strategy.name).toLowerCase(), + id: strategy.id ? strategy.id : slugify(strategy.name).toLowerCase(), inputs: strategy.inputs.map(input => ({ ...input, value: null, + placeholder: () => + input.placeholder + ? input.placeholder({ + ...context, + inputs: strategy.inputs, + input: { + ...input, + token: { + // todo + } + } + }) + : null, onInput: (val: any) => { input.value = val; } })), - submit: async (context: Pick) => { + submit: async () => { await this.validate({ ...context, inputs: strategy.inputs }); - const spells = strategy.spells({ + const allSpells = await strategy.spells({ ...context, inputs: strategy.inputs }); + const spells = context.dsa.Spell(); + + for (const spell of allSpells) { + spells.add(spell); + } + return await context.dsa.cast({ spells, onReceipt: this.onReceipt }); }, - validate: async (context: IStrategyContext) => { + validate: async () => { for (const input of this.inputs) { const result = await input.validate({ ...context, diff --git a/core/strategies/protocols/aave-v2/deposit-and-borrow.ts b/core/strategies/protocols/aave-v2/deposit-and-borrow.ts index 919a6ef..a2c04e8 100644 --- a/core/strategies/protocols/aave-v2/deposit-and-borrow.ts +++ b/core/strategies/protocols/aave-v2/deposit-and-borrow.ts @@ -1,4 +1,8 @@ -import { defineStrategy, StrategyInputType } from "../../helpers/strategy"; +import { + defineStrategy, + defineInput, + StrategyInputType +} from "../../helpers/strategy"; export default defineStrategy({ name: "Deposit & Borrow", @@ -6,7 +10,7 @@ export default defineStrategy({ author: "Instadapp Team", inputs: [ - { + defineInput({ type: StrategyInputType.INPUT_WITH_TOKEN, name: "Debt", placeholder: ({ input }) => `${input.token.symbol} to Payback`, @@ -18,32 +22,27 @@ export default defineStrategy({ if (input.token.balance < input.value) { return "Your amount exceeds your maximum limit."; } - - return true; } - }, - { + }), + defineInput({ type: StrategyInputType.INPUT_WITH_TOKEN, name: "Collateral", placeholder: ({ input }) => `${input.token.symbol} to Withdraw` - } + }) ], - spells: async ({ dsa, inputs }) => { - const spells = dsa.Spells(); - - spells.add({ - connector: "aave_v2", - method: "deposit", - args: [inputs[0].token.address, inputs[0].value, 0, 0] - }); - - spells.add({ - connector: "aave_v2", - method: "borrow", - args: [inputs[1].token.address, inputs[1].value, 0, 0, 0] - }); - - return spells; + spells: async ({ inputs }) => { + return [ + { + connector: "aave_v2", + method: "deposit", + args: [inputs[0].token.address, inputs[0].value, 0, 0] + }, + { + connector: "aave_v2", + method: "borrow", + args: [inputs[1].token.address, inputs[1].value, 0, 0, 0] + } + ]; } }); From 63dfc0c4c883e4e6e52b87a3b07fe12e2ce3f0b7 Mon Sep 17 00:00:00 2001 From: Georges KABBOUCHI Date: Sun, 22 Aug 2021 20:35:46 +0300 Subject: [PATCH 05/29] wip --- .../context/strategy/SidebarStrategy.vue | 43 ++-- composables/useBalances.ts | 10 +- composables/useStrategy.ts | 60 +++++ core/strategies/helpers/index.ts | 84 +++++++ core/strategies/helpers/strategy.ts | 226 +++++++++--------- core/strategies/index.ts | 2 +- .../protocols/aave-v2/deposit-and-borrow.ts | 2 +- 7 files changed, 280 insertions(+), 147 deletions(-) create mode 100644 composables/useStrategy.ts create mode 100644 core/strategies/helpers/index.ts diff --git a/components/sidebar/context/strategy/SidebarStrategy.vue b/components/sidebar/context/strategy/SidebarStrategy.vue index d8d474c..39a4b88 100644 --- a/components/sidebar/context/strategy/SidebarStrategy.vue +++ b/components/sidebar/context/strategy/SidebarStrategy.vue @@ -5,17 +5,19 @@
-
{{ $props }}
-
{{ selectedStrategy }}
+
+ + {{ input.error }} +
- + + + {{ error }}
@@ -23,15 +25,10 @@ \ No newline at end of file diff --git a/components/TokenSelectOption.vue b/components/TokenSelectOption.vue new file mode 100644 index 0000000..98b8ae1 --- /dev/null +++ b/components/TokenSelectOption.vue @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/components/common/input/InputAmount.vue b/components/common/input/InputAmount.vue index 7e2474d..75168ad 100644 --- a/components/common/input/InputAmount.vue +++ b/components/common/input/InputAmount.vue @@ -92,7 +92,7 @@