Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/tokensOverlap/TokensOverlap.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Token } from 'services/observables/tokens';
import { Token, TokenMinimal } from 'services/observables/tokens';
import { Reserve } from 'services/observables/pools';
import { Image } from 'components/image/Image';

export const TokensOverlap = ({
tokens,
maxLogos = 4,
}: {
tokens: Token[] | Reserve[];
tokens: Token[] | Reserve[] | TokenMinimal[];
maxLogos?: number;
}) => {
const tokenCount = tokens.length;
Expand Down
8 changes: 8 additions & 0 deletions src/elements/earn/portfolio/v3/V3Portfolio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ReactComponent as HoldingsLight } from 'assets/holdingsLight.svg';
import { ReactComponent as HoldingsDark } from 'assets/holdingsDark.svg';
import { V3Holdings } from 'elements/earn/portfolio/v3/holdings/V3Holdings';
import { useWalletConnect } from 'elements/walletConnect/useWalletConnect';
import V3ExternalHoldings from 'elements/earn/portfolio/v3/externalHoldings/V3ExternalHoldings';

const V3Portfolio = () => {
const account = useAppSelector((state) => state.user.account);
Expand Down Expand Up @@ -55,6 +56,13 @@ const V3Portfolio = () => {
</h2>
<V3Withdraw />
</div>

<div>
<h2 className="md:hidden max-w-[300px] rounded-20 h-[35px] mb-10">
External Holdings
</h2>
<V3ExternalHoldings />
</div>
</div>
</div>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const V3ExternalHoldings = () => {
return positions.length ? (
<section className="content-block p-20">
<h2>External Holdings at risk</h2>
<p className="mb-10 text-graphite">
<p className="mt-10 mb-20 text-secondary">
Your holdings on other platforms are vulnerable to impermanent loss
</p>
<Swiper
Expand All @@ -39,17 +39,19 @@ const V3ExternalHoldings = () => {
))}
</Swiper>

<div className="space-x-10 flex items-center mt-10">
<button className="external-holding-swiper-prev-btn hover:text-primary">
{'<--'}
</button>
<button className="external-holding-swiper-next-btn hover:text-primary">
{'-->'}
</button>
<div>
{activeIndex} of {positions.length}
{positions.length > 1 && (
<div className="space-x-10 flex items-center mt-10">
<button className="external-holding-swiper-prev-btn hover:text-primary">
{'<-'}
</button>
<button className="external-holding-swiper-next-btn hover:text-primary">
{'->'}
</button>
<div>
{activeIndex} of {positions.length}
</div>
</div>
</div>
)}
</section>
) : (
<div className="hidden" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,38 @@ import { Button, ButtonSize, ButtonVariant } from 'components/button/Button';
import { ExternalHolding } from 'elements/earn/portfolio/v3/externalHoldings/externalHoldings.types';
import { V3ExternalHoldingsModal } from 'elements/earn/portfolio/v3/externalHoldings/V3ExternalHoldingsModal';
import { TokensOverlap } from 'components/tokensOverlap/TokensOverlap';
import { useAppSelector } from 'store/index';
import { utils } from 'ethers';

interface Props {
position: ExternalHolding;
}

const V3ExternalHoldingsItem = ({ position }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const allTokenListTokens = useAppSelector(
(state) => state.bancor.allTokenListTokens
);

const nonBancorToken =
position.nonBancorToken !== undefined
? allTokenListTokens.find(
(t) =>
utils.getAddress(t.address) ===
utils.getAddress(position.nonBancorToken?.tokenAddress ?? '')
)
: undefined;

return (
<div className="rounded-20 border border-fog p-20">
<div className="rounded-20 border border-fog dark:border-grey p-20">
<div className="h-30">
<TokensOverlap tokens={position.tokens} />
<TokensOverlap
tokens={
nonBancorToken
? [...position.tokens, nonBancorToken]
: position.tokens
}
/>
</div>
<div className="mt-20 flex justify-between">
<div>{position.ammName}: </div>
Expand All @@ -38,6 +58,7 @@ const V3ExternalHoldingsItem = ({ position }: Props) => {
position={position}
isOpen={isOpen}
setIsOpen={setIsOpen}
nonBancorToken={nonBancorToken}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,46 @@ import { useApproveModal } from 'hooks/useApproveModal';
import { mockToken } from 'utils/mocked';
import { getMigrateFnByAmmProvider } from 'elements/earn/portfolio/v3/externalHoldings/externalHoldings';
import { shrinkToken } from 'utils/formulas';
import { ProtectedSettingsV3 } from 'components/protectedSettingsV3/ProtectedSettingsV3';
import { prettifyNumber, toBigNumber } from 'utils/helperFunctions';
import { TokenMinimal } from 'services/observables/tokens';
import { Image } from 'components/image/Image';
import {
confirmMigrateExtHoldingNotification,
failedNotification,
rejectNotification,
} from 'services/notifications/notifications';
import { ErrorCode } from 'services/web3/types';

interface Props {
position: ExternalHolding;
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
nonBancorToken?: TokenMinimal;
}

export const V3ExternalHoldingsModal = ({
position,
isOpen,
setIsOpen,
nonBancorToken,
}: Props) => {
const [txBusy, setTxBusy] = useState(false);
const account = useAppSelector((state) => state.user.account);
const dispatch = useDispatch();

const { withdrawalFee, lockDuration } = useAppSelector(
(state) => state.v3Portfolio.withdrawalSettings
);

const lockDurationInDays = useMemo(
() => lockDuration / 60 / 60 / 24,
[lockDuration]
);

const withdrawalFeeInPercent = useMemo(
() => (withdrawalFee * 100).toFixed(2),
[withdrawalFee]
);

const tokensToApprove = useMemo(
() => [
{
token: {
...mockToken,
address: position.poolTokenAddress,
symbol: 'lpTKN',
symbol: position.name,
},
amount: shrinkToken(position.poolTokenBalanceWei, 18),
},
],
[position.poolTokenAddress, position.poolTokenBalanceWei]
[position.name, position.poolTokenAddress, position.poolTokenBalanceWei]
);

const migrate = async () => {
Expand All @@ -68,16 +66,23 @@ export const V3ExternalHoldingsModal = ({
}

try {
const res = await migrateFn(
const tx = await migrateFn(
position.tokens[0].address,
position.tokens[1].address,
position.tokens[1]?.address ?? position.nonBancorToken?.tokenAddress,
position.poolTokenBalanceWei
);
await res.wait();
setIsOpen(false);
confirmMigrateExtHoldingNotification(dispatch, tx.hash, position.name);
await tx.wait();
await updatePortfolioData(dispatch);
} catch (e) {
console.error(e);
} catch (e: any) {
console.error('failed to migrate position', e);
if (e.code === ErrorCode.DeniedTx) {
rejectNotification(dispatch);
} else {
failedNotification(dispatch, 'Migration Failed');
}
setIsOpen(false);
} finally {
setTxBusy(false);
}
Expand All @@ -96,19 +101,21 @@ export const V3ExternalHoldingsModal = ({

return (
<Modal title={'Migrate'} setIsOpen={setIsOpen} isOpen={isOpen} large>
<div className="p-30 pt-0">
<h2 className="text-[24px] leading-9">
Protect this {position.ammName} holding from impermanent loss
</h2>

<p className="mt-16 mb-20 text-secondary">
{position.rektStatus === 'At risk'
? 'Your position is at risk of impermanent loss'
: `You’ve lost ${position.rektStatus} in impermanent loss so far`}
, get 100% protected on Bancor.
</p>

<h3 className="mb-10">Moving to Bancor</h3>
<div className="px-20 pb-10">
<div className="px-20">
<h2 className="text-[24px] leading-9">
Protect this {position.ammName} holding from impermanent loss
</h2>

<p className="mt-16 mb-20 text-secondary">
{position.rektStatus === 'At risk'
? 'Your position is at risk of impermanent loss'
: `You’ve lost ${position.rektStatus} in impermanent loss so far`}
, get 100% protected on Bancor.
</p>

<h3 className="mb-10">Moving to Bancor</h3>
</div>

<div className="bg-fog dark:bg-grey p-20 rounded space-y-20">
{position.tokens.map((t) => (
Expand All @@ -122,20 +129,42 @@ export const V3ExternalHoldingsModal = ({
</div>
))}
</div>
{!!position.nonBancorToken && (
<div className="px-20">
<h3 className="mb-10 mt-20 text-secondary">Exit risky position</h3>
<div className="flex items-center justify-between">
<div className="flex items-center">
<Image
alt={'Token Logo'}
src={nonBancorToken?.logoURI}
className="w-20 h-20 !rounded-full mr-10"
/>
{prettifyNumber(position.nonBancorToken.tokenCurrentBalance)}{' '}
{position.nonBancorToken.tokenName}
</div>
<div className="text-secondary">HODL in your wallet</div>
</div>
<div className="text-secondary ml-30">
{prettifyNumber(
toBigNumber(position.nonBancorToken.tokenCurrentPrice).times(
position.nonBancorToken.tokenCurrentBalance
),
true
)}
</div>
</div>
)}

<Button
onClick={handleButtonClick}
size={ButtonSize.Full}
className="mt-20"
className="mt-20 mb-10"
disabled={txBusy}
>
{txBusy ? '... waiting for confirmation' : 'Migrate and Protect'}
</Button>

<p className="text-secondary text-center mt-10">
100% Protected • {lockDurationInDays} day cooldown •{' '}
{withdrawalFeeInPercent}% withdrawal fee
</p>
<ProtectedSettingsV3 />

{ApproveModal}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { prettifyNumber } from 'utils/helperFunctions';
import {
ApyVisionData,
ApyVisionNonUniPosition,
ApyVisionNonUniPositionToken,
ApyVisionNonUniResponse,
ApyVisionUniPosition,
ExternalHolding,
Expand Down Expand Up @@ -33,7 +34,7 @@ const fetchApyVisionUniswap = async (
const fetchApyVisionNonUniswap = async (
user: string
): Promise<ApyVisionNonUniPosition[]> => {
const url = `https://api.apy.vision/portfolio/1/core/${user}?accessToken=${process.env.REACT_APP_APY_VISION_TOKEN}`;
const url = `https://api.apy.vision/portfolio/1/core/${user}?accessToken=${process.env.REACT_APP_APY_VISION_TOKEN}&isInWallet=true`;
try {
const { data } = await axios.get<ApyVisionNonUniResponse>(url);
return data.userPools;
Expand Down Expand Up @@ -115,6 +116,7 @@ export const getExternalHoldingsUni = (
// TODO add poolTokenAddress
poolTokenAddress: '',
poolTokenBalanceWei: '',
name: '',
};
return externalHolding;
})
Expand All @@ -130,12 +132,15 @@ export const getExternalHoldingsNonUni = (
// TODO Remove this filter once we support more than 2 reseves
.filter((pos) => pos.tokens.length === 2)
.map((pos) => {
let nonBancorToken: ApyVisionNonUniPositionToken | undefined =
undefined;
const tokens = pos.tokens
.map((token) => {
const address = utils.getAddress(token.tokenAddress);
const isETH = address === utils.getAddress(wethToken);
const tkn = tokensMap.get(isETH ? ethToken : address);
if (!tkn) {
nonBancorToken = token;
return undefined;
}
if (isETH) {
Expand All @@ -152,8 +157,7 @@ export const getExternalHoldingsNonUni = (
})
.filter((t) => !!t) as Token[];

// TODO once we support pools with more than 2 reserve tokens we need to update this
if (tokens.length !== 2) {
if (tokens.length === 0) {
return undefined;
}

Expand All @@ -172,10 +176,12 @@ export const getExternalHoldingsNonUni = (
ammKey: pos.poolProviderKey,
ammName,
tokens,
nonBancorToken,
rektStatus,
usdValue,
poolTokenAddress: pos.address,
poolTokenBalanceWei,
name: pos.name,
};
return newPos;
})
Expand Down
Loading