Skip to content

Commit

Permalink
Peter/strategy feat proxy account (#1539)
Browse files Browse the repository at this point in the history
* chore: update monetary to latest 0.7.3

* feat(strategies): use proxy accounts

* wip: write into identity pallet to keep track of strategy-proxy mapping

* feat(strategies): use proxy accounts saved into identity pallet

* chore: cleanup

* refactor: code review

* feat: add proxy account deposit field to transaction details, repay on withdraw-all

* refactor: code review
  • Loading branch information
peterslany authored Sep 8, 2023
1 parent e9414ef commit 96c625d
Show file tree
Hide file tree
Showing 21 changed files with 584 additions and 105 deletions.
8 changes: 6 additions & 2 deletions src/assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,8 @@
},
"strategy": {
"withdraw_rewards_in_wrapped": "Withdraw rewards in {{wrappedCurrencySymbol}}:",
"update_position": "Update position"
"update_position": "Update position",
"initialize": "Initialize strategy"
},
"transaction": {
"recent_transactions": "Recent transactions",
Expand Down Expand Up @@ -779,6 +780,9 @@
"low_risk_approach_generate_passive_income": "Discover a straightforward and low-risk approach to generate passive income. This strategy lends out deposited {{ticker}} to borrowers, allowing you to earn interest effortlessly",
"how_does_it_work": "How does it work?",
"what_are_the_risk": "What are the risks?",
"discover_fundamental_origins": "Discover the fundamental origins of the position, potential risks involved, the allocation of your capital, and other pertinent details in the docs section."
"discover_fundamental_origins": "Discover the fundamental origins of the position, potential risks involved, the allocation of your capital, and other pertinent details in the docs section.",
"proxy_deposit": "{{currency}} Proxy Deposit",
"proxy_deposit_tooltip": "This amount will be locked while the strategy is active. When you fully exit strategy deposit will be returned.",
"proxy_deposit_insufficient_funds": "Insufficient funds: 26 {{currency}} is required for proxy deposit locking."
}
}
5 changes: 3 additions & 2 deletions src/hooks/api/loans/use-get-account-lending-statistics.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TickerToData } from '@interlay/interbtc-api';
import { AccountId } from '@polkadot/types/interfaces';
import Big from 'big.js';
import { useMemo } from 'react';

Expand Down Expand Up @@ -92,11 +93,11 @@ const getAccountPositionsStats = (
};
};

const useGetAccountLendingStatistics = (): UseGetAccountLendingStatistics => {
const useGetAccountLendingStatistics = (proxyAccount?: AccountId): UseGetAccountLendingStatistics => {
const {
data: { lendPositions, borrowPositions },
refetch: positionsRefetch
} = useGetAccountPositions();
} = useGetAccountPositions(proxyAccount);
const { data: loanAssets, refetch: loanAssetsRefetch } = useGetLoanAssets();

const prices = useGetPrices();
Expand Down
10 changes: 7 additions & 3 deletions src/hooks/api/loans/use-get-account-positions-earnings.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CurrencyExt, newMonetaryAmount, TickerToData } from '@interlay/interbtc-api';
import { MonetaryAmount } from '@interlay/monetary-js';
import { AccountId } from '@polkadot/types/interfaces';
import Big from 'big.js';
import { gql, GraphQLClient } from 'graphql-request';
import { useCallback } from 'react';
Expand Down Expand Up @@ -68,12 +69,15 @@ type UseGetAccountPositionsEarningsResult = {
};

const useGetAccountPositionsEarnings = (
lendPositions: CollateralPosition[] | undefined
lendPositions: CollateralPosition[] | undefined,
proxyAccount?: AccountId
): UseGetAccountPositionsEarningsResult => {
const { account } = useWallet();
const { account: primaryAccount } = useWallet();

const account = proxyAccount || primaryAccount;

const { refetch, isLoading, data, error } = useQuery({
queryKey: ['loan-earnings', account],
queryKey: ['loan-earnings', account, proxyAccount],
queryFn: () => lendPositions && account && getEarnedAmountByTicker(account.toString(), lendPositions),
enabled: !!lendPositions && !!account,
refetchOnWindowFocus: false,
Expand Down
41 changes: 20 additions & 21 deletions src/hooks/api/loans/use-get-account-positions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { useCallback } from 'react';
import { useErrorHandler } from 'react-error-boundary';
import { useQuery } from 'react-query';

import { useWallet } from '@/hooks/use-wallet';
import { BorrowPosition, CollateralPosition } from '@/types/loans';
import { BLOCKTIME_REFETCH_INTERVAL } from '@/utils/constants/api';

import useAccountId from '../../use-account-id';
import { useGetAccountPositionsEarnings } from './use-get-account-positions-earnings';

const getLendPositionsOfAccount = async (accountId: AccountId): Promise<Array<CollateralPosition>> =>
Expand All @@ -19,13 +19,15 @@ interface UseGetLendPositionsOfAccountResult {
refetch: () => void;
}

const useGetLendPositionsOfAccount = (): UseGetLendPositionsOfAccountResult => {
const accountId = useAccountId();
const useGetLendPositionsOfAccount = (proxyAccount?: AccountId): UseGetLendPositionsOfAccountResult => {
const { account: primaryAccount } = useWallet();

const account = proxyAccount || primaryAccount;

const { data, error, refetch, isLoading } = useQuery({
queryKey: ['getLendPositionsOfAccount', accountId],
queryFn: () => accountId && getLendPositionsOfAccount(accountId),
enabled: !!accountId,
queryKey: ['getLendPositionsOfAccount', account?.toString(), proxyAccount],
queryFn: () => account && getLendPositionsOfAccount(account),
enabled: !!account,
refetchInterval: BLOCKTIME_REFETCH_INTERVAL
});

Expand All @@ -40,19 +42,15 @@ interface UseGetBorrowPositionsOfAccountResult {
refetch: () => void;
}

const useGetBorrowPositionsOfAccount = (): UseGetBorrowPositionsOfAccountResult => {
const accountId = useAccountId();
const useGetBorrowPositionsOfAccount = (proxyAccount?: AccountId): UseGetBorrowPositionsOfAccountResult => {
const { account: primaryAccount } = useWallet();

const { data, error, refetch, isLoading } = useQuery({
queryKey: ['getBorrowPositionsOfAccount', accountId],
queryFn: async () => {
if (!accountId) {
throw new Error('Something went wrong!');
}
const account = proxyAccount || primaryAccount;

return await window.bridge.loans.getBorrowPositionsOfAccount(accountId);
},
enabled: !!accountId,
const { data, error, refetch, isLoading } = useQuery({
queryKey: ['getBorrowPositionsOfAccount', account?.toString()],
queryFn: () => account && window.bridge.loans.getBorrowPositionsOfAccount(account),
enabled: !!account,
refetchInterval: BLOCKTIME_REFETCH_INTERVAL
});

Expand All @@ -76,21 +74,22 @@ type UseGetAccountPositionsResult = {
refetch: () => void;
};

const useGetAccountPositions = (): UseGetAccountPositionsResult => {
const useGetAccountPositions = (proxyAccount?: AccountId): UseGetAccountPositionsResult => {
const {
data: lendPositionsWithoutEarnings,
isLoading: isLendPositionsLoading,
refetch: lendPositionsRefetch
} = useGetLendPositionsOfAccount();
} = useGetLendPositionsOfAccount(proxyAccount);

const {
data: borrowPositions,
isLoading: isBorrowPositionsLoading,
refetch: borrowPositionsRefetch
} = useGetBorrowPositionsOfAccount();
} = useGetBorrowPositionsOfAccount(proxyAccount);

const { getPositionEarnings, isLoading: isAccountEarningsLoading } = useGetAccountPositionsEarnings(
lendPositionsWithoutEarnings
lendPositionsWithoutEarnings,
proxyAccount
);

const lendPositions: CollateralPosition[] | undefined = lendPositionsWithoutEarnings?.map((position) => ({
Expand Down
6 changes: 4 additions & 2 deletions src/hooks/api/loans/use-get-loan-available-amounts.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CurrencyExt, newMonetaryAmount } from '@interlay/interbtc-api';
import { MonetaryAmount } from '@interlay/monetary-js';
import { AccountId } from '@polkadot/types/interfaces';
import { useCallback } from 'react';

import { useGetAccountLendingStatistics } from '@/hooks/api/loans/use-get-account-lending-statistics';
Expand Down Expand Up @@ -138,10 +139,11 @@ type UseGetLoanAvailableAmountsResult = {
const useGetLoanAvailableAmounts = (
action: BorrowAction | LendAction,
asset: LoanAsset,
position?: CollateralPosition | BorrowPosition
position?: CollateralPosition | BorrowPosition,
proxyAccount?: AccountId | undefined
): UseGetLoanAvailableAmountsResult => {
const { getAvailableBalance } = useGetBalances();
const { data: statistics } = useGetAccountLendingStatistics();
const { data: statistics } = useGetAccountLendingStatistics(proxyAccount);

const maxCalculatedAmount = getMaxCalculatedAmount(action, asset, position, statistics);

Expand Down
111 changes: 105 additions & 6 deletions src/hooks/transaction/extrinsics/lib.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { ExtrinsicData } from '@interlay/interbtc-api';

import { DEFAULT_PROXY_ACCOUNT_AMOUNT, PROXY_ACCOUNT_RESERVE_AMOUNT } from '@/utils/constants/account';
import { proxifyExtrinsic } from '@/utils/helpers/extrinsic';

import { LibActions, Transaction } from '../types';

const getLibExtrinsic = async (params: LibActions): Promise<ExtrinsicData> => {
Expand Down Expand Up @@ -67,13 +70,109 @@ const getLibExtrinsic = async (params: LibActions): Promise<ExtrinsicData> => {
/* END - LOANS */

/* START - STRATEGIES */
case Transaction.STRATEGIES_DEPOSIT:
return window.bridge.loans.lend(...params.args);
case Transaction.STRATEGIES_WITHDRAW:
return window.bridge.loans.withdraw(...params.args);
case Transaction.STRATEGIES_INITIALIZE_PROXY: {
// Initialize 10 proxy accounts and then if we deposit for the first time, the proxy
// account will be assigned and stored in identity pallet.
const createProxiesExtrinsics = [...Array(DEFAULT_PROXY_ACCOUNT_AMOUNT).keys()].map((index) =>
window.bridge.api.tx.proxy.createPure('Any', 0, index)
);
const batchedExtrinsics = window.bridge.transaction.buildBatchExtrinsic(createProxiesExtrinsics);

return { extrinsic: batchedExtrinsics };
}
// Since we use proxy accounts for strategies, first argument is always proxy account for which
// the action should be performed - this account must be passed.
// Second argument is always boolean denoting if the proxy account identity was set or not.
case Transaction.STRATEGIES_DEPOSIT: {
const [strategyType, proxyAccount, isIdentitySet, ...args] = params.args;
const [, depositAmount] = args;

const transferExtrinsic = window.bridge.tokens.transfer(proxyAccount.toString(), depositAmount);

const strategyDepositExtrinsic = (await window.bridge.loans.lend(...args)).extrinsic;
const proxiedStrategyDepositExtrinsic = proxifyExtrinsic(proxyAccount, strategyDepositExtrinsic);

if (isIdentitySet) {
const batchedExtrinsics = window.bridge.transaction.buildBatchExtrinsic([
transferExtrinsic.extrinsic,
proxiedStrategyDepositExtrinsic
]);

return { extrinsic: batchedExtrinsics };
} else {
const identityLockAmountTransferExtrinsic = window.bridge.tokens.transfer(
proxyAccount.toString(),
PROXY_ACCOUNT_RESERVE_AMOUNT
);
const strategyAccountIdentity = {
additional: [[{ Raw: 'strategyType' }, { Raw: strategyType }]]
};
const setIdentityExtrinsic = window.bridge.api.tx.identity.setIdentity(strategyAccountIdentity);
const proxiedSetIdentityExtrinsic = proxifyExtrinsic(proxyAccount, setIdentityExtrinsic);

const batchedExtrinsicsWithIdentity = window.bridge.transaction.buildBatchExtrinsic([
identityLockAmountTransferExtrinsic.extrinsic,
proxiedSetIdentityExtrinsic,
transferExtrinsic.extrinsic,
proxiedStrategyDepositExtrinsic
]);

return { extrinsic: batchedExtrinsicsWithIdentity };
}
}

case Transaction.STRATEGIES_WITHDRAW: {
const primaryAccount = window.bridge.account;
if (!primaryAccount) {
throw new Error('Strategy primary account not found.');
}

const [, proxyAccount, ...args] = params.args;
const [, withdrawalAmount] = args;

const strategyWithdrawalExtrinsic = (await window.bridge.loans.withdraw(...args)).extrinsic;
const proxiedStrategyWithdrawExtrinsic = proxifyExtrinsic(proxyAccount, strategyWithdrawalExtrinsic);

const transferExtrinsic = window.bridge.tokens.transfer(primaryAccount.toString(), withdrawalAmount).extrinsic;
const proxiedTransferExtrinsic = proxifyExtrinsic(proxyAccount, transferExtrinsic);

const batchExtrinsic = window.bridge.transaction.buildBatchExtrinsic([
proxiedStrategyWithdrawExtrinsic,
proxiedTransferExtrinsic
]);

return { extrinsic: batchExtrinsic };
}

case Transaction.STRATEGIES_ALL_WITHDRAW: {
const [underlyingCurrency] = params.args;
return window.bridge.loans.withdrawAll(underlyingCurrency);
const primaryAccount = window.bridge.account;
if (!primaryAccount) {
throw new Error('Primary account not found.');
}

const [, proxyAccount, underlyingCurrency, withdrawalAmount] = params.args;

const clearIdentityExtrinsic = window.bridge.api.tx.identity.clearIdentity();

const transferIdentityReserveAmount = window.bridge.tokens.transfer(
primaryAccount.toString(),
PROXY_ACCOUNT_RESERVE_AMOUNT
).extrinsic;

const strategyWithdrawalExtrinsic = (await window.bridge.loans.withdrawAll(underlyingCurrency)).extrinsic;

const transferExtrinsic = window.bridge.tokens.transfer(primaryAccount.toString(), withdrawalAmount).extrinsic;

const batchExtrinsic = window.bridge.transaction.buildBatchExtrinsic([
clearIdentityExtrinsic,
transferIdentityReserveAmount,
strategyWithdrawalExtrinsic,
transferExtrinsic
]);

const proxiedBatchExtrinsic = proxifyExtrinsic(proxyAccount, batchExtrinsic);

return { extrinsic: proxiedBatchExtrinsic };
}
/* END - STRATEGIES */

Expand Down
1 change: 1 addition & 0 deletions src/hooks/transaction/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ enum Transaction {
LOANS_REPAY = 'LOANS_REPAY',
LOANS_REPAY_ALL = 'LOANS_REPAY_ALL',
// Stategies
STRATEGIES_INITIALIZE_PROXY = 'STRATEGIES_INITIALIZE_PROXY',
STRATEGIES_DEPOSIT = 'STRATEGIES_DEPOSIT',
STRATEGIES_WITHDRAW = 'STRATEGIES_WITHDRAW',
STRATEGIES_ALL_WITHDRAW = 'STRATEGIES_ALL_WITHDRAW',
Expand Down
20 changes: 16 additions & 4 deletions src/hooks/transaction/types/strategies.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
import { InterBtcApi } from '@interlay/interbtc-api';
import { AccountId } from '@polkadot/types/interfaces';

import { StrategyType } from '@/pages/Strategies/types';

import { Transaction } from '.';

interface StrategiesInitializeProxyAction {
type: Transaction.STRATEGIES_INITIALIZE_PROXY;
args: [StrategyType];
}

interface StrategiesDepositAction {
type: Transaction.STRATEGIES_DEPOSIT;
args: Parameters<InterBtcApi['loans']['lend']>;
args: [StrategyType, AccountId, boolean, ...Parameters<InterBtcApi['loans']['lend']>];
}

interface StrategiesWithdrawAction {
type: Transaction.STRATEGIES_WITHDRAW;
args: Parameters<InterBtcApi['loans']['withdraw']>;
args: [StrategyType, AccountId, ...Parameters<InterBtcApi['loans']['withdraw']>];
}

interface StrategiesWithdrawAllAction {
type: Transaction.STRATEGIES_ALL_WITHDRAW;
args: Parameters<InterBtcApi['loans']['withdrawAll']>;
args: [StrategyType, AccountId, ...Parameters<InterBtcApi['loans']['withdraw']>];
}

type StrategiesActions = StrategiesDepositAction | StrategiesWithdrawAction | StrategiesWithdrawAllAction;
type StrategiesActions =
| StrategiesInitializeProxyAction
| StrategiesDepositAction
| StrategiesWithdrawAction
| StrategiesWithdrawAllAction;

export type { StrategiesActions };
6 changes: 3 additions & 3 deletions src/hooks/transaction/utils/description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ const getTranslationArgs = (

/* START - STRATEGIES */
case Transaction.STRATEGIES_DEPOSIT: {
const [currency, amount] = params.args;
const [, , , currency, amount] = params.args;

return {
key: isPast ? 'transaction.deposited_amount' : 'transaction.depositing_amount',
Expand All @@ -262,7 +262,7 @@ const getTranslationArgs = (
};
}
case Transaction.STRATEGIES_WITHDRAW: {
const [currency, amount] = params.args;
const [, , currency, amount] = params.args;

return {
key: isPast ? 'transaction.withdrew_amount' : 'transaction.withdrawing_amount',
Expand All @@ -273,7 +273,7 @@ const getTranslationArgs = (
};
}
case Transaction.STRATEGIES_ALL_WITHDRAW: {
const [currency] = params.args;
const [, , currency] = params.args;

return {
key: isPast ? 'transaction.withdrew' : 'transaction.withdrawing',
Expand Down
Loading

2 comments on commit 96c625d

@vercel
Copy link

@vercel vercel bot commented on 96c625d Sep 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 96c625d Sep 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.