diff --git a/composables/useBalances.ts b/composables/useBalances.ts index 8b4d2b2..b53cd2a 100644 --- a/composables/useBalances.ts +++ b/composables/useBalances.ts @@ -212,6 +212,7 @@ async function getBalances( } const { name, symbol, decimals, type, isStableCoin, key } = tokenData; tokensBalObj[tokenAddress] = { + address: tokenAddress, name, symbol, decimals, diff --git a/composables/useStrategy.ts b/composables/useStrategy.ts index 692647d..5688746 100644 --- a/composables/useStrategy.ts +++ b/composables/useStrategy.ts @@ -18,13 +18,13 @@ export function useStrategy(defineStrategy: DefineStrategy) { } = useNotification(); const strategy = buildStrategy(defineStrategy); - const inputs = ref(strategy.getInputs()); + const inputs = ref(strategy.inputs); const error = ref(""); strategy.onUpdated(async () => { await nextTick(); - inputs.value = strategy.getInputs(); + inputs.value = strategy.inputs; console.log("onUpdated"); }); diff --git a/core/strategies/helpers/index.ts b/core/strategies/helpers/index.ts index 97e08fa..2d0ac83 100644 --- a/core/strategies/helpers/index.ts +++ b/core/strategies/helpers/index.ts @@ -6,8 +6,8 @@ export interface IStrategyContext { dsa: DSA; web3: Web3; inputs: IStrategyInput[]; - dsaTokens?: IStrategyToken, - userTokens?: IStrategyToken, + dsaTokens?: { [address: string]: IStrategyToken }; + userTokens?: { [address: string]: IStrategyToken }; } export interface IStrategyToken { @@ -46,6 +46,9 @@ export interface IStrategyInput { input: IStrategyInput & StrategyInputParameterMap[InputType]; } ) => string | void; + + defaults?: (context: Omit) => object; + value?: any; [key: string]: any; @@ -78,7 +81,7 @@ export function defineStrategy(strategy: IStrategy) { } export function buildStrategy(schema: DefineStrategy) { - return new Strategy(schema) + return new Strategy(schema); } export type DefineStrategy = ReturnType; diff --git a/core/strategies/helpers/strategy.ts b/core/strategies/helpers/strategy.ts index ed20e6f..94c05ea 100644 --- a/core/strategies/helpers/strategy.ts +++ b/core/strategies/helpers/strategy.ts @@ -21,64 +21,94 @@ export class Strategy { constructor(schema: DefineStrategy) { this.schema = schema; - this.inputs = this.schema.inputs; + this.inputs = this.generateInputs(this.schema.inputs); + } + + getBaseContext(): Omit { + return { + ...this.context, + ...this.props + }; } getContext(): IStrategyContext { return { ...this.context, ...this.props, - inputs: this.getInputs() + inputs: this.inputs }; } setProps(props: object) { Object.assign(this.props, props); - this.notifyListeners() + const inputs = this.inputs; + + for (const input of inputs) { + if (typeof input.defaults !== "function") { + continue; + } + + Object.assign(input, input.defaults(this.getBaseContext())); + } + + this.notifyListeners(); } - getInputs() { - return this.inputs.map(input => ({ - ...input, - value: input.value || "", - error: input.error || "", - placeholder: () => - input.placeholder - ? input.placeholder({ - ...this.context, - inputs: this.inputs, - input: { - token: { - // todo - }, - ...input - } - }) - : null, - onInput: (val: any) => { - input.error = ""; - input.value = val; - - if (val) { - input.error = input.validate({ + generateInputs(inputs) { + return inputs.map((input, idx) => { + const computedInput = { + ...input, + value: input.value || "", + error: input.error || "", + placeholder: () => { + console.log({ ...this.getContext(), - input + input: this.inputs[idx] }); + + return input.placeholder + ? input.placeholder({ + ...this.getContext(), + input: this.inputs[idx] + }) + : null; + }, + onInput: (val: any) => { + this.inputs[idx].error = ""; + this.inputs[idx].value = val; + + if (val) { + this.inputs[idx].error = this.inputs[idx].validate({ + ...this.getContext(), + input: this.inputs[idx] + }); + } + + this.notifyListeners(); + }, + onCustomInput: (values: object) => { + this.inputs[idx] = Object.assign(this.inputs[idx], values); + + this.inputs[idx].error = this.inputs[idx].validate({ + ...this.getContext(), + input: this.inputs[idx] + }); + this.notifyListeners(); } + }; - this.notifyListeners(); - }, - onCustomInput: (values: object) => { - input = Object.assign(input, values); + let defaults = {}; - input.error = input.validate({ - ...this.getContext(), - input - }); - this.notifyListeners(); + if (input.defaults) { + defaults = input.defaults(this.getBaseContext()); } - })); + + return { + ...computedInput, + ...defaults + }; + }); } async submit(options) { @@ -100,7 +130,7 @@ export class Strategy { } async validate() { - const inputs = this.getInputs(); + const inputs = this.inputs; for (const input of inputs) { if (typeof input.validate !== "function") { @@ -120,10 +150,14 @@ export class Strategy { setWeb3(web3: Web3) { this.context.web3 = web3; + + this.notifyListeners(); } setDSA(dsa: DSA) { this.context.dsa = dsa; + + this.notifyListeners(); } async notifyListeners() { diff --git a/core/strategies/protocols/aave-v2/deposit-and-borrow.ts b/core/strategies/protocols/aave-v2/deposit-and-borrow.ts index a29c3df..8f27ccf 100644 --- a/core/strategies/protocols/aave-v2/deposit-and-borrow.ts +++ b/core/strategies/protocols/aave-v2/deposit-and-borrow.ts @@ -10,7 +10,7 @@ export default defineStrategy({ defineInput({ type: StrategyInputType.INPUT_WITH_TOKEN, name: "Debt", - placeholder: ({ input }) => `${input.token.symbol} to Payback`, + placeholder: ({ input }) => `${input.token?.symbol} to Payback`, validate: ({ input }) => { if (!input.token) { return "Token is required"; @@ -20,13 +20,23 @@ export default defineStrategy({ return "Your amount exceeds your maximum limit."; } }, - token: tokens.mainnet.getTokenByKey("eth") + defaults: context => { + return { + token: context.dsaTokens + ? Object.values(context.dsaTokens).find(t => t.key === "eth") + : null + }; + } }), defineInput({ type: StrategyInputType.INPUT_WITH_TOKEN, name: "Collateral", - placeholder: ({ input }) => `${input.token.symbol} to Withdraw`, - token: tokens.mainnet.getTokenByKey("dai") + placeholder: ({ input }) => `${input.token?.symbol} to Withdraw`, + defaults: ({ dsaTokens }) => ({ + token: dsaTokens + ? Object.values(dsaTokens).find(t => t.key === "dai") + : null + }) }) ], diff --git a/core/strategies/protocols/aave-v2/index.ts b/core/strategies/protocols/aave-v2/index.ts index f6bac2d..7a63aaa 100644 --- a/core/strategies/protocols/aave-v2/index.ts +++ b/core/strategies/protocols/aave-v2/index.ts @@ -1,6 +1,8 @@ import depositAndBorrow from "./deposit-and-borrow" +import paybackAndWithdraw from "./payback-and-withdraw" export default [ - depositAndBorrow + depositAndBorrow, + paybackAndWithdraw, ] \ No newline at end of file diff --git a/core/strategies/protocols/aave-v2/payback-and-withdraw.ts b/core/strategies/protocols/aave-v2/payback-and-withdraw.ts new file mode 100644 index 0000000..5e0e1f7 --- /dev/null +++ b/core/strategies/protocols/aave-v2/payback-and-withdraw.ts @@ -0,0 +1,47 @@ +import tokens from "~/constant/tokens"; +import { defineStrategy, defineInput, StrategyInputType } from "../../helpers"; + +export default defineStrategy({ + name: "Payback & Withdraw", + description: "Payback debt & withdraw collateral in a single txn.", + author: "Instadapp Team", + + inputs: [ + defineInput({ + 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."; + } + }, + token: tokens.mainnet.getTokenByKey("eth") + }), + defineInput({ + type: StrategyInputType.INPUT_WITH_TOKEN, + name: "Collateral", + placeholder: ({ input }) => `${input.token.symbol} to Withdraw`, + token: tokens.mainnet.getTokenByKey("dai") + }) + ], + + spells: async ({ inputs }) => { + return [ + { + connector: "aave_v2", + method: "payback", + args: [inputs[0].token.address, inputs[0].value, 1, 0, 0] + }, + { + connector: "aave_v2", + method: "withdraw", + args: [inputs[1].token.address, inputs[1].value, 0, 0] + } + ]; + } +});