Skip to content

Commit

Permalink
Release v0.3.19
Browse files Browse the repository at this point in the history
  • Loading branch information
dash-yuga committed Nov 26, 2024
1 parent 00a2c25 commit 07a2dfb
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 72 deletions.
106 changes: 89 additions & 17 deletions lib/components/ui/ApeStableDisclosure.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,113 @@
import { Address, isAddressEqual } from 'viem';
import { Address, isAddressEqual, zeroAddress } from 'viem';
import {
ApeEthOmnichainContract,
ApeUsdOmnichainContract,
UsdcArbMainnetContract,
UsdcEthMainnetContract,
UsdtArbMainnetContract,
UsdtEthMainnetContract,
} from '../../utils/utils';
import AdaptiveTooltip from './tooltip/AdaptiveTooltip';
import { useMemo } from 'react';
import { InfoIcon } from '../icons/InfoIcon';
import { ChainId } from '@decent.xyz/box-common';

type ApeStableDisclosureProps = {
isSourceToken: boolean;
tokenAddress: Address;
tokenUsdValue: string;
sourceTokenChainId: number;
sourceTokenAddress: Address;
destinationTokenChainId: number;
destinationTokenAddress: Address;
};

/**
* Display a warning icon with tooltip if:
* - the token is the destination token, AND
* - the token is ApeUSD or ApeETH
* Display a warning icon with tooltip in specific source/dest chain and token scenarios.
* Generally, this guides users to use the "base" stable token for a ~1:1 conversion of APE-ETH and APE-USD.
*/
export const ApeStableDisclosure = ({
isSourceToken,
tokenAddress,
sourceTokenChainId,
sourceTokenAddress,
destinationTokenChainId,
destinationTokenAddress,
}: ApeStableDisclosureProps) => {
const isApeUsd = isAddressEqual(tokenAddress, ApeUsdOmnichainContract);
const isApeEth = isAddressEqual(tokenAddress, ApeEthOmnichainContract);
const isApeStable = isApeUsd || isApeEth;

const tooltipContent = useMemo(() => {
if (isApeEth) {
return 'The underlying price of APE-ETH is correlated with Liquid Staked ETH (stETH).';
// ETH-ETH or ARB-ETH -> APE-ETH
if (
(sourceTokenChainId === ChainId.ETHEREUM ||
sourceTokenChainId === ChainId.ARBITRUM) &&
isAddressEqual(sourceTokenAddress, zeroAddress) &&
destinationTokenChainId === ChainId.APE &&
isAddressEqual(destinationTokenAddress, ApeEthOmnichainContract)
) {
return 'For a 1:1 conversion, use stETH to apeETH';
}
// APE-ETH -> ETH-ETH
if (
sourceTokenChainId === ChainId.APE &&
isAddressEqual(sourceTokenAddress, ApeEthOmnichainContract) &&
destinationTokenChainId === ChainId.ETHEREUM &&
isAddressEqual(destinationTokenAddress, zeroAddress)
) {
return 'For a 1:1 conversion, use apeETH to stETH';
}
// APE-ETH -> ARB-ETH
if (
sourceTokenChainId === ChainId.APE &&
isAddressEqual(sourceTokenAddress, ApeEthOmnichainContract) &&
destinationTokenChainId === ChainId.ARBITRUM &&
isAddressEqual(destinationTokenAddress, zeroAddress)
) {
return 'For a 1:1 conversion, use apeETH to stETH';
}
// ETH-USDC/USDT -> APE-USD
if (
sourceTokenChainId === ChainId.ETHEREUM &&
(isAddressEqual(sourceTokenAddress, UsdcEthMainnetContract) ||
isAddressEqual(sourceTokenAddress, UsdtEthMainnetContract)) &&
destinationTokenChainId === ChainId.APE &&
isAddressEqual(destinationTokenAddress, ApeUsdOmnichainContract)
) {
return 'For a 1:1 conversion, use DAI to apeUSD';
}
// ARB-USDC/USDT -> APE-USD
if (
sourceTokenChainId === ChainId.ARBITRUM &&
(isAddressEqual(sourceTokenAddress, UsdtArbMainnetContract) ||
isAddressEqual(sourceTokenAddress, UsdcArbMainnetContract)) &&
destinationTokenChainId === ChainId.APE &&
isAddressEqual(destinationTokenAddress, ApeUsdOmnichainContract)
) {
return 'For a 1:1 conversion, use DAI to apeUSD';
}
// APE-USD -> ETH-USDC/USDT
if (
sourceTokenChainId === ChainId.APE &&
isAddressEqual(sourceTokenAddress, ApeUsdOmnichainContract) &&
destinationTokenChainId === ChainId.ETHEREUM &&
(isAddressEqual(destinationTokenAddress, UsdcEthMainnetContract) ||
isAddressEqual(destinationTokenAddress, UsdtEthMainnetContract))
) {
return 'For a 1:1 conversion, use apeUSD to DAI';
}
if (isApeUsd) {
return 'The underlying price of APE-USD is correlated with DAI.';
// APE-USD -> ARB-USDC/USDT
if (
sourceTokenChainId === ChainId.APE &&
isAddressEqual(sourceTokenAddress, ApeUsdOmnichainContract) &&
destinationTokenChainId === ChainId.ARBITRUM &&
(isAddressEqual(destinationTokenAddress, UsdcArbMainnetContract) ||
isAddressEqual(destinationTokenAddress, UsdtArbMainnetContract))
) {
return 'For a 1:1 conversion, use apeUSD to DAI';
}
}, [isApeEth, isApeUsd]);
}, [
sourceTokenAddress,
sourceTokenChainId,
destinationTokenChainId,
destinationTokenAddress,
]);

if (isSourceToken || !isApeStable || !tooltipContent) {
if (isSourceToken || !tooltipContent) {
return;
}

Expand Down
8 changes: 6 additions & 2 deletions lib/components/ui/TokenInputModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,13 @@ export const TokenInputModule = ({
<div className="aw-flex aw-size-full aw-items-center aw-text-left aw-text-[15px] aw-font-normal aw-leading-[18px] aw-tracking-tight aw-text-white aw-opacity-70">
{inputUsdValue}
<ApeStableDisclosure
tokenAddress={currentToken.token.address as Address}
tokenUsdValue={inputUsdValue}
isSourceToken={isSourceToken}
sourceTokenAddress={sourceToken.token.address as Address}
sourceTokenChainId={sourceToken.token.chainId}
destinationTokenAddress={
destinationToken.token.address as Address
}
destinationTokenChainId={destinationToken.token.chainId}
/>
</div>
<div>
Expand Down
33 changes: 1 addition & 32 deletions lib/hooks/useTransactionConfig.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
import { useEffect, useRef, useState } from 'react';
import { usePortalStore } from '../store/usePortalStore.ts';
import {
Address,
isAddress,
isAddressEqual,
parseUnits,
zeroAddress,
} from 'viem';
import { isAddress, parseUnits, zeroAddress } from 'viem';
import {
ActionType,
bigintSerializer,
BridgeId,
ChainId,
SwapActionConfig,
SwapDirection,
TokenInfo,
} from '@decent.xyz/box-common';
import { useAccount } from 'wagmi';
import { UseBoxActionArgs } from '@decent.xyz/box-hooks';
import { InputType } from '../utils/constants.ts';
import { useApeContext } from '../providers/ape/apeProvider.context.ts';
import {
ApeCoinMainnetArbitrumContract,
ApeCoinMainnetEthereumContract,
} from '../utils/utils.ts';

const DisabledTransactionConfig: UseBoxActionArgs = {
sender: zeroAddress,
Expand All @@ -41,16 +29,6 @@ const DisabledTransactionConfig: UseBoxActionArgs = {
enable: false,
};

function isApechainOrApecoin(token: TokenInfo): boolean {
if (token.chainId === ChainId.APE || token.chainId === ChainId.APE_CURTIS) {
return true;
}
return (
isAddressEqual(token.address as Address, ApeCoinMainnetArbitrumContract) ||
isAddressEqual(token.address as Address, ApeCoinMainnetEthereumContract)
);
}

export const useTransactionConfig = (): UseBoxActionArgs => {
const { destinationAddress } = useApeContext();
const { address } = useAccount();
Expand Down Expand Up @@ -133,15 +111,6 @@ export const useTransactionConfig = (): UseBoxActionArgs => {
enable: true,
};

// For decent's initial ApeChain OFT support, bridge transactions must set bridgeId property if ApeChain or ApeCoin ARB is involved in the TX
if (
sourceToken.token.chainId !== destinationToken.token.chainId &&
(isApechainOrApecoin(sourceToken.token) ||
isApechainOrApecoin(destinationToken.token))
) {
newUseBoxActionArgs.bridgeId = BridgeId.OFT;
}

if (
JSON.stringify(useBoxActionArgs, bigintSerializer) !==
JSON.stringify(newUseBoxActionArgs, bigintSerializer)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@yuga-labs/ape-portal-public",
"description": "ApeChain Portal. Offers a set of components to interact with the ApeChain blockchain.",
"version": "0.3.17",
"version": "0.3.19",
"license": "APEPORTAL-1.0",
"type": "module",
"main": "./dist/cjs/index.js",
Expand Down
75 changes: 57 additions & 18 deletions test/components/ui/ApeStableDisclosure.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,34 @@ import { ApeStableDisclosure } from '../../../lib/components/ui/ApeStableDisclos
import {
ApeEthOmnichainContract,
ApeUsdOmnichainContract,
UsdcArbMainnetContract,
UsdcEthMainnetContract,
} from '../../../lib/utils/utils';
import { zeroAddress } from 'viem';
import { Address, zeroAddress } from 'viem';
import userEvent from '@testing-library/user-event';
import { ChainId } from '@decent.xyz/box-common';

describe('ApeStableDisclosure', () => {
const renderComponent = (
props: Partial<React.ComponentProps<typeof ApeStableDisclosure>> = {},
) => {
const defaultProps = {
isSourceToken: false,
tokenAddress: zeroAddress,
tokenUsdValue: '0',
sourceTokenAddress: zeroAddress as Address,
sourceTokenChainId: ChainId.ETHEREUM,
destinationTokenAddress: zeroAddress as Address,
destinationTokenChainId: ChainId.APE,
};
return render(<ApeStableDisclosure {...defaultProps} {...props} />);
};

test('should not render anything if isSourceToken is true', () => {
renderComponent({ isSourceToken: true });
renderComponent({
isSourceToken: true,
sourceTokenAddress: UsdcEthMainnetContract,
sourceTokenChainId: ChainId.ETHEREUM,
destinationTokenAddress: ApeUsdOmnichainContract,
});
expect(screen.queryByRole('img')).toBeNull();
});

Expand All @@ -32,45 +42,74 @@ describe('ApeStableDisclosure', () => {

test('should still render disclosure even if token amount USD is 0', () => {
renderComponent({
tokenAddress: ApeUsdOmnichainContract,
tokenUsdValue: '0',
destinationTokenAddress: ApeEthOmnichainContract,
});
const warningIcon = screen.getByRole('img');
expect(warningIcon).toBeDefined();
});

test('should render warning icon with correct tooltip for ETH-USDC -> APE-APEUSD', async () => {
userEvent.setup();
renderComponent({
sourceTokenAddress: UsdcEthMainnetContract,
sourceTokenChainId: ChainId.ETHEREUM,
destinationTokenAddress: ApeUsdOmnichainContract,
});
const warningIcon = screen.getByRole('img');
expect(warningIcon).toBeDefined();
userEvent.hover(warningIcon);
await waitFor(() =>
expect(
screen.queryByText('For a 1:1 conversion, use DAI to apeUSD'),
).toBeDefined(),
);
});

test('should render warning icon with correct tooltip for ARB-USDC -> APE-APEUSD', async () => {
userEvent.setup();
renderComponent({
sourceTokenAddress: UsdcArbMainnetContract,
sourceTokenChainId: ChainId.ARBITRUM,
destinationTokenAddress: ApeUsdOmnichainContract,
});
const warningIcon = screen.getByRole('img');
expect(warningIcon).toBeDefined();
userEvent.hover(warningIcon);
await waitFor(() =>
expect(
screen.queryByText('For a 1:1 conversion, use DAI to apeUSD'),
).toBeDefined(),
);
});

test('should render warning icon with correct tooltip for ApeUSD', async () => {
test('should render warning icon with correct tooltip for ETH-ETH -> APE-APEETH', async () => {
userEvent.setup();
renderComponent({
tokenAddress: ApeUsdOmnichainContract,
tokenUsdValue: '10',
destinationTokenAddress: ApeEthOmnichainContract,
});
const warningIcon = screen.getByRole('img');
expect(warningIcon).toBeDefined();
userEvent.hover(warningIcon);
await waitFor(() =>
expect(
screen.queryByText(
'The underlying price of APE-USD is correlated with DAI.',
),
screen.queryByText('For a 1:1 conversion, use stETH to apeETH'),
).toBeDefined(),
);
});

test('should render warning icon with correct tooltip for ApeETH', async () => {
test('should render warning icon with correct tooltip for ARB-ETH -> APE-APEETH', async () => {
userEvent.setup();
renderComponent({
tokenAddress: ApeEthOmnichainContract,
tokenUsdValue: '10',
sourceTokenAddress: zeroAddress as Address,
sourceTokenChainId: ChainId.ARBITRUM,
destinationTokenAddress: ApeEthOmnichainContract,
});
const warningIcon = screen.getByRole('img');
expect(warningIcon).toBeDefined();
userEvent.hover(warningIcon);
await waitFor(() =>
expect(
screen.queryByText(
'The underlying price of APE-ETH is correlated with Liquid Staked ETH (stETH).',
),
screen.queryByText('For a 1:1 conversion, use stETH to apeETH'),
).toBeDefined(),
);
});
Expand Down

0 comments on commit 07a2dfb

Please sign in to comment.