Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,27 @@
font-size: 12px;
margin-left: 4px;
}

.currency-dropdown-trigger {
display: flex;
align-items: center;
justify-content: center;
background: none;
border: none;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: background-color 0.2s ease;

&:hover {
background-color: colors.$neutral-100;
}

&:focus {
outline: none;
background-color: colors.$neutral-100;
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Community } from '@hicommonwealth/schemas';
import { ZERO_ADDRESS } from '@hicommonwealth/shared';
import { notifyError, notifySuccess } from 'controllers/app/notifications';
import React, { useEffect, useMemo } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { useCreateThreadTokenTradeMutation } from 'state/api/threads';
import { useGetERC20BalanceQuery } from 'state/api/tokens';
import FractionalValue from 'views/components/FractionalValue';
import { z } from 'zod';
import { PopoverMenu } from '../../component_kit/CWPopoverMenu';
import { CWIcon } from '../../component_kit/cw_icons/cw_icon';
import { CWText } from '../../component_kit/cw_text';
import { CWButton } from '../../component_kit/new_designs/CWButton';
Expand All @@ -26,6 +29,14 @@ interface ThreadTokenWidgetProps {
) => void | Promise<void>;
}

type BuyCurrency = 'ETH' | 'COMMON' | 'PRIMARY';

interface CurrencyOption {
type: BuyCurrency;
symbol: string;
address: string;
}

const ThreadTokenWidget = ({
tokenizedThreadsEnabled = false,
threadId,
Expand All @@ -37,6 +48,8 @@ const ThreadTokenWidget = ({
onThreadCreated,
}: ThreadTokenWidgetProps) => {
const isThreadCreationMode = !threadId;
const [selectedBuyCurrency, setSelectedBuyCurrency] =
useState<BuyCurrency>('PRIMARY');

const threadTokenWidgetHook = useThreadTokenWidget({
tokenizedThreadsEnabled,
Expand Down Expand Up @@ -126,6 +139,124 @@ const ThreadTokenWidget = ({

const safeSetCurrentAmount = setCurrentAmount || (() => {});

// TODO 13191: test with base network
const COMMON_TOKEN_ADDRESS = '0x4c87da04887a1F9F21F777E3A8dD55C3C9f84701';

// Get WETH address based on chain ID
// WETH addresses for different chains
const getWETHAddress = (chainId: number): string => {
const wethAddresses: Record<number, string> = {
1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // Ethereum Mainnet
8453: '0x4200000000000000000000000000000000000006', // Base
84532: '0x4200000000000000000000000000000000000006', // Base Sepolia (same as Base)
10: '0x4200000000000000000000000000000000000006', // Optimism
42161: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', // Arbitrum
56: '0x2170Ed0880ac9A755fd29B2688956BD959F933F8', // BSC
};
return wethAddresses[chainId] || ZERO_ADDRESS;
};

const wethAddress = useMemo(() => getWETHAddress(ethChainId), [ethChainId]);

// Define available buy currencies
const currencyOptions: CurrencyOption[] = useMemo(() => {
const options: CurrencyOption[] = [
{
type: 'ETH',
symbol: 'WETH',
address: wethAddress,
},
];

// Add COMMON token if address is available
if (COMMON_TOKEN_ADDRESS) {
options.push({
type: 'COMMON',
symbol: 'COMMON',
address: COMMON_TOKEN_ADDRESS,
});
}

// Add primary token (the pre-selected one)
if (primaryTokenAddress && primaryTokenSymbol) {
options.push({
type: 'PRIMARY',
symbol: primaryTokenSymbol,
address: primaryTokenAddress,
});
}

return options;
}, [
primaryTokenAddress,
primaryTokenSymbol,
COMMON_TOKEN_ADDRESS,
wethAddress,
]);

// Get selected currency details
const selectedCurrency = useMemo(() => {
return (
currencyOptions.find((opt) => opt.type === selectedBuyCurrency) ||
currencyOptions[0]
);
}, [currencyOptions, selectedBuyCurrency]);

// Get payment token address based on selected currency
const paymentTokenAddress = useMemo(() => {
return selectedCurrency?.address || primaryTokenAddress;
}, [selectedCurrency, primaryTokenAddress]);

// Get currency symbol for display
const displayCurrencySymbol = useMemo(() => {
if (isThreadCreationMode || !isSellMode) {
return selectedCurrency?.symbol || primaryTokenSymbol;
}
return threadToken?.symbol || 'TOKEN';
}, [
isThreadCreationMode,
isSellMode,
selectedCurrency,
primaryTokenSymbol,
threadToken,
]);

// Fetch balance for selected currency
// For WETH, we fetch the ERC20 balance
const {
data: selectedCurrencyTokenBalance = '0.0',
isLoading: isLoadingSelectedCurrencyTokenBalance,
} = useGetERC20BalanceQuery({
nodeRpc: chainRpc,
tokenAddress: paymentTokenAddress,
userAddress: selectedAddress,
enabled:
tokenizedThreadsEnabled &&
!!selectedAddress &&
!!paymentTokenAddress &&
!!chainRpc &&
!isSellMode,
});

// Get balance for selected currency
const selectedCurrencyBalance = useMemo(() => {
if (isSellMode) {
return userTokenBalance;
}
return selectedCurrencyTokenBalance;
}, [isSellMode, selectedCurrencyTokenBalance, userTokenBalance]);

const isLoadingSelectedCurrencyBalance = useMemo(() => {
if (isSellMode) {
return isLoadingTokenBalance;
}
return isLoadingSelectedCurrencyTokenBalance;
}, [
isSellMode,
isLoadingSelectedCurrencyTokenBalance,
isLoadingTokenBalance,
]);

useEffect(() => {
const fetchTokenGain = async () => {
if (
Expand Down Expand Up @@ -193,7 +324,9 @@ const ThreadTokenWidget = ({

const handlePresetClick = (presetAmount: string) => {
if (presetAmount === 'MAX') {
safeSetCurrentAmount(isSellMode ? userTokenBalance : userBalance);
safeSetCurrentAmount(
isSellMode ? userTokenBalance : selectedCurrencyBalance,
);
} else {
safeSetCurrentAmount(presetAmount);
}
Expand Down Expand Up @@ -227,7 +360,7 @@ const ThreadTokenWidget = ({
return;
}

if (parseFloat(safeCurrentAmount) > parseFloat(userBalance)) {
if (parseFloat(safeCurrentAmount) > parseFloat(selectedCurrencyBalance)) {
notifyError('Insufficient balance');
return;
}
Expand All @@ -253,7 +386,7 @@ const ThreadTokenWidget = ({
amountIn: amountInWei,
walletAddress: selectedAddress,
minAmountOut: minAmountOut,
paymentTokenAddress: primaryTokenAddress,
paymentTokenAddress: paymentTokenAddress,
};

const txReceipt = await buyThreadToken(payload);
Expand Down Expand Up @@ -331,7 +464,7 @@ const ThreadTokenWidget = ({
tokenAddress: String(threadToken.token_address),
amountToken: amountToken,
walletAddress: selectedAddress,
paymentTokenAddress: primaryTokenAddress,
paymentTokenAddress: paymentTokenAddress,
};

const txReceipt = await sellThreadToken(payload);
Expand Down Expand Up @@ -418,9 +551,9 @@ const ThreadTokenWidget = ({
<div className="balance-amount">
<CWText type="caption" fontWeight="regular">
{isThreadCreationMode || !isSellMode
? isLoadingBalance
? isLoadingSelectedCurrencyBalance
? 'Loading...'
: `${userBalance} ${primaryTokenSymbol}`
: `${selectedCurrencyBalance} ${displayCurrencySymbol}`
: isLoadingTokenBalance
? 'Loading...'
: `${userTokenBalance} ${threadToken?.symbol || 'TOKEN'}`}
Expand All @@ -440,18 +573,37 @@ const ThreadTokenWidget = ({
<div className="input-currency">
<CWText type="b2">
{parseFloat(safeCurrentAmount) > 0
? `${safeCurrentAmount} ${
isThreadCreationMode || !isSellMode
? primaryTokenSymbol
: threadToken?.symbol || 'TOKEN'
}`
: `0.000 ${
isThreadCreationMode || !isSellMode
? primaryTokenSymbol
: threadToken?.symbol || 'TOKEN'
}`}
? `${safeCurrentAmount} ${displayCurrencySymbol}`
: `0.000 ${displayCurrencySymbol}`}
</CWText>
<CWIcon iconName="chevronDown" className="chevron-icon" />
{!isSellMode && (
<PopoverMenu
menuItems={currencyOptions.map((option) => ({
type: 'default' as const,
label: option.symbol,
onClick: () => {
setSelectedBuyCurrency(option.type);
},
}))}
placement="bottom-end"
modifiers={[{ name: 'offset', options: { offset: [0, 8] } }]}
renderTrigger={(onClick, isOpen) => (
<button
className="currency-dropdown-trigger"
onClick={onClick}
type="button"
>
<CWIcon
iconName={isOpen ? 'caretUp' : 'chevronDown'}
className="chevron-icon"
/>
</button>
)}
/>
)}
{isSellMode && (
<CWIcon iconName="chevronDown" className="chevron-icon" />
)}
</div>
</div>

Expand Down Expand Up @@ -517,7 +669,7 @@ const ThreadTokenWidget = ({
parseFloat(safeCurrentAmount) >
parseFloat(
isThreadCreationMode || !isSellMode
? userBalance
? selectedCurrencyBalance
: userTokenBalance,
) ||
isWrongNetwork
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const ConnectTokenStep = ({
</CWText>
</div>
<CWRadioButton
value={`${baseNode.id}`}
value={`${baseNode?.id}`}
checked
name="chainNodeId"
hookToForm
Expand Down
Loading