Skip to content
Open
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
33 changes: 30 additions & 3 deletions examples/react/src/utils/cofhe.config.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,44 @@
import { CofheProvider, useInternalQueryClient } from '@cofhe/react';
import { CofheProvider, createCofheConfig, useInternalQueryClient } from '@cofhe/react';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { usePublicClient, useWalletClient } from 'wagmi';
import { useAccount, usePublicClient, useWalletClient } from 'wagmi';
import { baseSepolia, sepolia } from '@cofhe/sdk/chains';

function QueryDebug() {
const cofheQueryClient = useInternalQueryClient();
return <ReactQueryDevtools client={cofheQueryClient} position="left" buttonPosition="bottom-left" />;
}
const cofheConfig = createCofheConfig({
supportedChains: [
sepolia,
// baseSepolia
],
react: {
// pinnedTokens: {
// 11155111: '0x87A3effB84CBE1E4caB6Ab430139eC41d156D55A', // sepolia weth
// 84532: '0xbED96aa98a49FeA71fcC55d755b915cF022a9159', // base sepolia weth
// },
// tokenLists: {
// 11155111: ['https://storage.googleapis.com/cofhesdk/sepolia.json'],
// 84532: ['https://storage.googleapis.com/cofhesdk/base-sepolia.json'],
// 421613: ['https://tokens.cofhe.io/arbitrum-sepolia.json'],
// },
},
});

export const CofheProviderLocal = ({ children }: { children: React.ReactNode }) => {
const wagmiPublicClient = usePublicClient();
const { data: wagmiWalletClient } = useWalletClient();

const { chain } = useAccount();

const isConnectedToWagmiSupportedChain = chain !== undefined;

return (
<CofheProvider walletClient={wagmiWalletClient} publicClient={wagmiPublicClient}>
<CofheProvider
walletClient={isConnectedToWagmiSupportedChain ? wagmiWalletClient : undefined}
publicClient={isConnectedToWagmiSupportedChain ? wagmiPublicClient : undefined}
config={cofheConfig}
>
{children}
<QueryDebug />
</CofheProvider>
Expand Down
2 changes: 1 addition & 1 deletion examples/react/src/utils/wagmi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

export const injectedProvider = injected({ shimDisconnect: true });
const config = createConfig({
chains: [baseSepolia, sepolia],
chains: [sepolia, baseSepolia],
transports: {
[sepolia.id]: http(),
[baseSepolia.id]: http(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('Base Sepolia Integration Tests', () => {

before(async function () {
// Skip if no private key is provided (for CI/CD)
if (!process.env.BASE_SEPOLIA_PRIVATE_KEY && process.env.CI) {
if (!process.env.TEST_PRIVATE_KEY && process.env.CI) {
this.skip();
}

Expand Down Expand Up @@ -90,7 +90,7 @@ describe('Base Sepolia Integration Tests', () => {

it('Should encrypt -> store -> decrypt a value', async function () {
// Skip if no private key is provided
if (!process.env.BASE_SEPOLIA_PRIVATE_KEY && process.env.CI) {
if (!process.env.TEST_PRIVATE_KEY && process.env.CI) {
this.skip();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,28 @@
import { cn } from '@/utils/cn';
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
import { useCofheToken } from '@/hooks/useCofheTokenLists';
import { TokenIcon } from '../../components/TokenIcon';
import { CofheTokenConfidentialBalance } from '../../components/CofheTokenConfidentialBalance';
import { useCofhePinnedTokenAddress } from '@/hooks/useCofhePinnedTokenAddress';
import { FloatingButtonPage } from '../../pagesConfig/types';
import { usePortalNavigation } from '@/stores';
import { useCofhePinnedToken } from '@/hooks/useCofhePinnedToken';
import { useCofheChainId } from '@/hooks/useCofheConnection';
import { DEFAULT_TOKEN_BY_CHAIN_ID } from '@/types/token';

export const AssetCard: React.FC = () => {
// TODO: show Native token if no pinned token address

const { navigateTo } = usePortalNavigation();
// const pinnedTokenAddress = "0x8ee52408ED5b0e396aA779Fd52F7fbc20A4b33Fb"; // Base sepolia
// const pinnedTokenAddress = "0xbED96aa98a49FeA71fcC55d755b915cF022a9159"; // Redact (Sepolia)
const pinnedTokenAddress = useCofhePinnedTokenAddress();

// Find token from token lists to get icon and confidentialityType
const tokenFromList = useCofheToken({
address: pinnedTokenAddress,
});
const pinnedToken = useCofhePinnedToken();
const chainId = useCofheChainId();
const fallbackToken = chainId ? DEFAULT_TOKEN_BY_CHAIN_ID[chainId] : undefined;
const token = pinnedToken ?? fallbackToken;

const handleClick = () => {
// TODO: figure out best handling for this error
if (!tokenFromList) throw new Error('Token not found in token list');

if (pinnedTokenAddress) {
if (token) {
navigateTo(FloatingButtonPage.TokenInfo, {
pageProps: { token: tokenFromList },
pageProps: { token },
});
} else {
// TODO: native token support
alert('Native token info navigation is not implemented yet.');
throw new Error('No token found for AssetCard. No pinned token and no fallback token for current chain.');
}
};

Expand All @@ -43,11 +35,11 @@ export const AssetCard: React.FC = () => {
{/* Left Side: Icon, Ticker, Privacy Metrics */}
<div className="flex items-center gap-3 flex-1">
{/* Asset Icon */}
<TokenIcon logoURI={tokenFromList?.logoURI} alt={tokenFromList?.symbol} size="md" />
<TokenIcon logoURI={token?.logoURI} alt={token?.symbol} size="md" />

{/* Ticker and Privacy */}
<div className="flex flex-col gap-1">
<h3 className="text-lg font-bold fnx-text-primary">{tokenFromList?.symbol}</h3>
<h3 className="text-lg font-bold fnx-text-primary">{token?.symbol}</h3>

{/* Privacy Metrics Placeholder for future implementation */}
{/* <div className="flex items-center gap-2 text-xs fnx-text-primary opacity-80">
Expand All @@ -69,7 +61,7 @@ export const AssetCard: React.FC = () => {
<div className="flex flex-col items-end">
{/* // TODO: add suport for displaying native token balance if no pinned address */}
<CofheTokenConfidentialBalance
token={tokenFromList}
token={token}
showSymbol={false}
size="xl"
decimalPrecision={5}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { assert, type ElementOf } from 'ts-essentials';
import { usePortalNavigation, usePortalUI } from '@/stores';
import { Button } from '../../components';
import { useCofheClaimableTokens } from '@/hooks/useCofheClaimableTokens';
import { useCofheChainId } from '@/hooks/useCofheConnection';
import { DEFAULT_TOKEN_BY_CHAIN_ID } from '@/types/token';

const iconClassName = 'w-4 h-4';

Expand Down Expand Up @@ -121,7 +123,10 @@ const navItems = [...baseNavItems, ...(!isProduction() ? [debugNavItem] : [])] a
export const BottomNavigation: React.FC = () => {
const { navigateTo } = usePortalNavigation();
const { openPortal } = usePortalUI();
const defaultToken = useCofhePinnedToken();
const pinnedToken = useCofhePinnedToken();
const chainId = useCofheChainId();
const fallbackToken = chainId ? DEFAULT_TOKEN_BY_CHAIN_ID[chainId] : undefined;
const defaultToken = pinnedToken ?? fallbackToken;

const handleNavClick = (page: ElementOf<typeof baseNavItems>['id']) => {
openPortal();
Expand Down
15 changes: 3 additions & 12 deletions packages/react/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,11 @@ export const CofheReactConfigSchema = z.object({
{ label: '1 Month', intervalSeconds: 2592000 },
]),
defaultPermitExpirationSeconds: z.number().optional().default(604800), // 1 week
pinnedTokens: z.record(z.string(), addressSchema).optional().default({
11155111: '0x87A3effB84CBE1E4caB6Ab430139eC41d156D55A', // sepolia weth
84532: '0xbED96aa98a49FeA71fcC55d755b915cF022a9159', // base sepolia weth
// 421613: '0x980b62da83eff3d4576c647993b0c1d7faf17c73', // arbitrum sepolia weth
}),
pinnedTokens: z.record(z.string(), addressSchema).optional(),
tokenLists: z
.record(z.string(), z.array(z.string()))
.optional()
.default({
11155111: ['https://storage.googleapis.com/cofhesdk/sepolia.json'],
84532: ['https://storage.googleapis.com/cofhesdk/base-sepolia.json'],
// 421613: ['https://tokens.cofhe.io/arbitrum-sepolia.json'],
})
.transform((lists) => lists as Partial<Record<number, string[]>>),
.transform((lists) => lists as Partial<Record<number, string[]>>)
.optional(),
position: z.enum(['bottom-right', 'bottom-left', 'top-right', 'top-left']).optional().default('bottom-right'),
initialTheme: z.enum(['dark', 'light']).optional().default('light'),
});
Expand Down
8 changes: 7 additions & 1 deletion packages/react/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ export {
type ClaimableAmountByTokenAddress,
} from './useCofheTokensClaimable';
export { useCofheWalletClient } from './useCofheConnection';
export { useCofheTokens, useCofheTokenLists, ETH_ADDRESS, type Token, type Erc20Pair } from './useCofheTokenLists';
export {
useCofheTokens,
useCofheTokenLists,
ETH_ADDRESS_LOWERCASE,
type Token,
type Erc20Pair,
} from './useCofheTokenLists';
export {
useCofheTokensWithExistingEncryptedBalances,
type UseCofheTokensWithExistingBalancesInput,
Expand Down
25 changes: 19 additions & 6 deletions packages/react/src/hooks/useCofheAutoConnect.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
import type { PublicClient, WalletClient } from 'viem';
import { useCofheClient } from './useCofheClient';
import { useEffect } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { useCofheConnect } from './useCofheConnect';

type Input = {
publicClient?: PublicClient;
walletClient?: WalletClient;
};
export const useCofheAutoConnect = ({ walletClient, publicClient }: Input) => {
// TODO: if the user switches in the wallet to a chain that's not supported by the dapp, should show error message or disconnect?
const client = useCofheClient();

const connectMutationFn = useCofheConnect().mutate;

const supportedChainIds = useMemo(() => {
return new Set(client.config.supportedChains.map((chain) => chain.id));
}, [client.config.supportedChains]);

const disconnectIfConnected = useCallback(() => {
if (client.connected || client.connecting) {
client.disconnect();
}
}, [client]);

useEffect(() => {
// Keep COFHE connection in sync with the upstream (wagmi) connection.
// - if wagmi disconnects, it stops providing clients -> disconnect COFHE
// - if wagmi provides clients -> ensure COFHE is connected
if (!publicClient || !walletClient) {
if (client.connected || client.connecting) {
client.disconnect();
}
disconnectIfConnected();
return;
}

const chainId = walletClient.chain?.id ?? publicClient.chain?.id;
if (chainId !== undefined && !supportedChainIds.has(chainId)) {
disconnectIfConnected();
return;
}

if (client.connecting) return;
connectMutationFn({ publicClient, walletClient });
}, [publicClient, walletClient, client, client.connected, client.connecting, connectMutationFn]);
}, [publicClient, walletClient, client, connectMutationFn, supportedChainIds, disconnectIfConnected]);
};
2 changes: 1 addition & 1 deletion packages/react/src/hooks/useCofhePinnedTokenAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ import { useCofheChainId } from './useCofheConnection';
export function useCofhePinnedTokenAddress() {
const widgetConfig = useCofheContext().client.config.react;
const chainId = useCofheChainId();
const pinnedTokenAddress = chainId ? widgetConfig.pinnedTokens[chainId?.toString()] : undefined;
const pinnedTokenAddress = chainId ? widgetConfig.pinnedTokens?.[chainId?.toString()] : undefined;
return pinnedTokenAddress;
}
6 changes: 3 additions & 3 deletions packages/react/src/hooks/useCofheTokenLists.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { type UseQueryOptions, type UseQueryResult } from '@tanstack/react-query';
import { useCofheContext } from '../providers/CofheProvider';
import { useMemo } from 'react';
import { ETH_ADDRESS, type Erc20Pair, type Token } from '../types/token.js';
import { ETH_ADDRESS_LOWERCASE, type Erc20Pair, type Token } from '../types/token.js';
import { useInternalQueries } from '../providers/index.js';
import type { Address } from 'viem';
import { useCofheChainId } from './useCofheConnection';

export { ETH_ADDRESS, type Token, type Erc20Pair };
export { ETH_ADDRESS_LOWERCASE, type Token, type Erc20Pair };

type TokenList = {
name: string;
Expand All @@ -30,7 +30,7 @@ export function useCofheTokenLists(
queryOptions?: UseTokenListsOptions
): UseTokenListsResult {
const widgetConfig = useCofheContext().client.config.react;
const tokensListsUrls = chainId ? widgetConfig.tokenLists[chainId] : [];
const tokensListsUrls = chainId ? widgetConfig.tokenLists?.[chainId] : [];

const queriesOptions: UseQueryOptions<TokenList, Error>[] =
tokensListsUrls?.map((url) => ({
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/hooks/useCofheTokenPublicBalance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type UseQueryOptions } from '@tanstack/react-query';
import { type Address } from 'viem';
import { useCofheAccount, useCofhePublicClient } from './useCofheConnection';
import { type Token, ETH_ADDRESS } from './useCofheTokenLists';
import { type Token, ETH_ADDRESS_LOWERCASE } from './useCofheTokenLists';
import { ERC20_BALANCE_OF_ABI } from '../constants/erc20ABIs';
import { assert } from 'ts-essentials';
import { useInternalQuery } from '../providers/index';
Expand Down Expand Up @@ -92,7 +92,7 @@ export function createPublicTokenBalanceQueryOptions<TSelectedData = bigint>(par
assert(publicClient, 'PublicClient is required to fetch token balance');
assert(accountAddress, 'Account address is required to fetch token balance');

const isNativeToken = tokenAddress.toLowerCase() === ETH_ADDRESS.toLowerCase();
const isNativeToken = tokenAddress.toLowerCase() === ETH_ADDRESS_LOWERCASE;

const balance = isNativeToken
? publicClient.getBalance({
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/hooks/useCofheTokenShield.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from '@tanstack/react-query';
import { type Address } from 'viem';
import { useCofheWalletClient, useCofheChainId, useCofheAccount, useCofhePublicClient } from './useCofheConnection.js';
import { type Token, ETH_ADDRESS } from './useCofheTokenLists.js';
import { type Token, ETH_ADDRESS_LOWERCASE } from './useCofheTokenLists.js';
import {
SHIELD_ABIS,
UNSHIELD_ABIS,
Expand Down Expand Up @@ -93,7 +93,7 @@ export function useCofheTokenShield(
if (confidentialityType === 'wrapped') {
// Check if this is a wrapped ETH token (erc20Pair is ETH_ADDRESS)
const erc20PairAddress = input.token.extensions.fhenix.erc20Pair?.address as Address | undefined;
const isEth = erc20PairAddress?.toLowerCase() === ETH_ADDRESS.toLowerCase();
const isEth = erc20PairAddress?.toLowerCase() === ETH_ADDRESS_LOWERCASE;

if (isEth) {
// For ETH: use encryptETH(address to) with value
Expand Down
12 changes: 10 additions & 2 deletions packages/react/src/hooks/useTrackPendingTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { constructCofheReadContractQueryForInvalidation } from './useCofheReadCo
import { QueryClient, type QueriesOptions } from '@tanstack/react-query';
import type { Address, TransactionReceipt } from 'viem';
import { getTokenContractConfig } from '@/constants/confidentialTokenABIs';
import type { Token } from './useCofheTokenLists';
import { ETH_ADDRESS_LOWERCASE, type Token } from './useCofheTokenLists';
import { constructPublicTokenBalanceQueryKeyForInvalidation } from './useCofheTokenPublicBalance';
import { constructUnshieldClaimsQueryKeyForInvalidation, invalidateClaimableQueries } from './useCofheTokenClaimable';
import { usePendingTransactions } from './usePendingTransactions';
Expand Down Expand Up @@ -131,7 +131,15 @@ function useHandleInvalidations() {
const { upsert: upsertDecryptionWatcher, byKey } = useDecryptionWatchersStore();
console.log('Scheduled invalidations store:', byKey);
const handleInvalidations = (tx: Transaction) => {
// TODO invalidate gas on all txs since any tx spends gas
// each transaction requires gas, so native token balance changes on every transaction
invalidatePublicTokenBalanceQueries(
{
tokenAddress: ETH_ADDRESS_LOWERCASE,
chainId: tx.chainId,
accountAddress: tx.account,
},
queryClient
);
if (tx.actionType === TransactionActionType.ShieldSend) {
invalidateConfidentialTokenBalanceQueries(tx.token, queryClient);
} else if (tx.actionType === TransactionActionType.Shield) {
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export {
useCofheTokenDecryptedBalance,
useCofheTokenTransfer,
useTransactionReceiptsByHash,
ETH_ADDRESS,
ETH_ADDRESS_LOWERCASE,
type Erc20Pair,
type UnshieldClaim,
type UnshieldClaimsSummary,
Expand Down
Loading