{/* Privacy Metrics Placeholder for future implementation */}
{/*
@@ -69,7 +61,7 @@ export const AssetCard: React.FC = () => {
{/* // TODO: add suport for displaying native token balance if no pinned address */}
{
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['id']) => {
openPortal();
diff --git a/packages/react/src/config.ts b/packages/react/src/config.ts
index 2a9d4954..6854f6be 100644
--- a/packages/react/src/config.ts
+++ b/packages/react/src/config.ts
@@ -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>),
+ .transform((lists) => lists as Partial>)
+ .optional(),
position: z.enum(['bottom-right', 'bottom-left', 'top-right', 'top-left']).optional().default('bottom-right'),
initialTheme: z.enum(['dark', 'light']).optional().default('light'),
});
diff --git a/packages/react/src/hooks/index.ts b/packages/react/src/hooks/index.ts
index 092ac203..cbd379ac 100644
--- a/packages/react/src/hooks/index.ts
+++ b/packages/react/src/hooks/index.ts
@@ -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,
diff --git a/packages/react/src/hooks/useCofheAutoConnect.ts b/packages/react/src/hooks/useCofheAutoConnect.ts
index 10a7f81b..89ab8382 100644
--- a/packages/react/src/hooks/useCofheAutoConnect.ts
+++ b/packages/react/src/hooks/useCofheAutoConnect.ts
@@ -1,6 +1,6 @@
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 = {
@@ -8,23 +8,36 @@ type Input = {
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]);
};
diff --git a/packages/react/src/hooks/useCofhePinnedTokenAddress.ts b/packages/react/src/hooks/useCofhePinnedTokenAddress.ts
index db675fd2..d6f4ccef 100644
--- a/packages/react/src/hooks/useCofhePinnedTokenAddress.ts
+++ b/packages/react/src/hooks/useCofhePinnedTokenAddress.ts
@@ -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;
}
diff --git a/packages/react/src/hooks/useCofheTokenLists.ts b/packages/react/src/hooks/useCofheTokenLists.ts
index 22260729..31cdb015 100644
--- a/packages/react/src/hooks/useCofheTokenLists.ts
+++ b/packages/react/src/hooks/useCofheTokenLists.ts
@@ -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;
@@ -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[] =
tokensListsUrls?.map((url) => ({
diff --git a/packages/react/src/hooks/useCofheTokenPublicBalance.ts b/packages/react/src/hooks/useCofheTokenPublicBalance.ts
index e7c401ea..3299da50 100644
--- a/packages/react/src/hooks/useCofheTokenPublicBalance.ts
+++ b/packages/react/src/hooks/useCofheTokenPublicBalance.ts
@@ -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';
@@ -92,7 +92,7 @@ export function createPublicTokenBalanceQueryOptions(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({
diff --git a/packages/react/src/hooks/useCofheTokenShield.ts b/packages/react/src/hooks/useCofheTokenShield.ts
index 84d33f90..a02bd1b3 100644
--- a/packages/react/src/hooks/useCofheTokenShield.ts
+++ b/packages/react/src/hooks/useCofheTokenShield.ts
@@ -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,
@@ -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
diff --git a/packages/react/src/hooks/useTrackPendingTransactions.ts b/packages/react/src/hooks/useTrackPendingTransactions.ts
index b35c45fc..9a753b45 100644
--- a/packages/react/src/hooks/useTrackPendingTransactions.ts
+++ b/packages/react/src/hooks/useTrackPendingTransactions.ts
@@ -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';
@@ -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) {
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 43894c4f..0292aced 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -28,7 +28,7 @@ export {
useCofheTokenDecryptedBalance,
useCofheTokenTransfer,
useTransactionReceiptsByHash,
- ETH_ADDRESS,
+ ETH_ADDRESS_LOWERCASE,
type Erc20Pair,
type UnshieldClaim,
type UnshieldClaimsSummary,
diff --git a/packages/react/src/types/token.ts b/packages/react/src/types/token.ts
index adcd2043..5e4c691c 100644
--- a/packages/react/src/types/token.ts
+++ b/packages/react/src/types/token.ts
@@ -5,12 +5,25 @@
* to avoid circular dependencies.
*/
+import { baseSepolia, sepolia } from '@cofhe/sdk/chains';
import type { Address } from 'viem';
/**
* Special address representing native ETH (used in erc20Pair for ConfidentialETH tokens)
*/
-export const ETH_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' as const;
+export const ETH_ADDRESS_LOWERCASE = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' as const;
+
+type TokenWithoutExtensions = Omit;
+export function constructNativeToken(chainId: number): TokenWithoutExtensions {
+ return {
+ chainId,
+ address: ETH_ADDRESS_LOWERCASE,
+ name: 'Ether',
+ symbol: 'ETH',
+ decimals: 18,
+ logoURI: 'https://storage.googleapis.com/cofhesdk/token-icons/eth.webp',
+ };
+}
/**
* ERC20 pair information for wrapped confidential tokens
@@ -40,7 +53,7 @@ export type Token = {
};
};
-// just a sample token for examples and quick tests
+// source: https://storage.googleapis.com/cofhesdk/sepolia.json
export const WETH_SEPOLIA_TOKEN: Token = {
chainId: 11155111,
address: '0x87A3effB84CBE1E4caB6Ab430139eC41d156D55A',
@@ -61,3 +74,30 @@ export const WETH_SEPOLIA_TOKEN: Token = {
},
},
};
+
+// source: https://storage.googleapis.com/cofhesdk/base-sepolia.json
+const WETH_BASE_SEPOLIA_TOKEN: Token = {
+ chainId: 84532,
+ address: '0xbED96aa98a49FeA71fcC55d755b915cF022a9159',
+ name: 'Redact eETH',
+ symbol: 'eETH',
+ decimals: 18,
+ logoURI: 'https://storage.googleapis.com/cofhesdk/token-icons/eth.webp',
+ extensions: {
+ coingeckoId: 'eeth',
+ fhenix: {
+ confidentialityType: 'wrapped',
+ confidentialValueType: 'uint128',
+ erc20Pair: {
+ address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
+ symbol: 'ETH',
+ decimals: 18,
+ logoURI: 'https://storage.googleapis.com/cofhesdk/token-icons/eth.webp',
+ },
+ },
+ },
+};
+export const DEFAULT_TOKEN_BY_CHAIN_ID: Record = {
+ [sepolia.id]: WETH_SEPOLIA_TOKEN,
+ [baseSepolia.id]: WETH_BASE_SEPOLIA_TOKEN,
+};
diff --git a/packages/react/src/utils/coingecko.ts b/packages/react/src/utils/coingecko.ts
index 4f183728..4dee8dda 100644
--- a/packages/react/src/utils/coingecko.ts
+++ b/packages/react/src/utils/coingecko.ts
@@ -1,5 +1,5 @@
import type { Address } from 'viem';
-import { ETH_ADDRESS } from '@/types/token';
+import { ETH_ADDRESS_LOWERCASE } from '@/types/token';
export const DEFAULT_COINGECKO_API_BASE_URL = 'https://api.coingecko.com/api/v3' as const;
@@ -102,7 +102,7 @@ export function parseCoingeckoSimplePriceUsd({
export function isNativeTokenAddress(address: Address | undefined): boolean {
if (!address) return false;
- return address.toLowerCase() === ETH_ADDRESS.toLowerCase();
+ return address.toLowerCase() === ETH_ADDRESS_LOWERCASE;
}
export const TMP_WBTC_ON_MAINNET = {
diff --git a/packages/react/src/utils/utils.ts b/packages/react/src/utils/utils.ts
index 3b333a9c..857b12b3 100644
--- a/packages/react/src/utils/utils.ts
+++ b/packages/react/src/utils/utils.ts
@@ -1,5 +1,5 @@
import * as viemChains from 'viem/chains';
-import { ETH_ADDRESS, type Token, type Erc20Pair } from '../types/token.js';
+import { ETH_ADDRESS_LOWERCASE, type Token, type Erc20Pair } from '../types/token.js';
import type { FheTypeValue } from '@cofhe/sdk';
import { isValidElement } from 'react';
@@ -68,7 +68,7 @@ export const getBlockExplorerTokenUrl = (chainId: number, tokenAddress: string):
*/
export const isEthPair = (erc20Pair: Erc20Pair | undefined): boolean => {
if (!erc20Pair) return false;
- return erc20Pair.address.toLowerCase() === ETH_ADDRESS.toLowerCase();
+ return erc20Pair.address.toLowerCase() === ETH_ADDRESS_LOWERCASE;
};
/**