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
4 changes: 2 additions & 2 deletions src/hooks/useBalance/strategies/evm.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Balance, BalanceStrategy } from "./types";
import {
getBalance,
getEvmBalance,
getNativeBalance,
getConfig,
getProvider,
Expand Down Expand Up @@ -61,7 +61,7 @@ export class EVMBalanceStrategy implements BalanceStrategy {

const balance = tokenInfo?.isNative
? await getNativeBalance(chainId, account, "latest", provider)
: await getBalance(chainId, account, tokenAddress, "latest", provider);
: await getEvmBalance(chainId, account, tokenAddress, "latest", provider);
const balanceDecimals = tokenInfo?.decimals ?? 18;
return {
balance,
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useWalletBalanceTrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useConnection } from "hooks";
import {
ChainId,
fixedPointAdjustment,
getBalance,
getEvmBalance,
getConfig,
getNativeBalance,
getRoutes,
Expand Down Expand Up @@ -85,7 +85,7 @@ const calculateUsdBalances = async (account: string) => {
fromChainId: Number(chainId),
fromTokenSymbol,
fromTokenAddress,
balance: await getBalance(
balance: await getEvmBalance(
Number(chainId),
account,
fromTokenAddress
Expand Down
2 changes: 2 additions & 0 deletions src/utils/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { BigNumber, providers } from "ethers";
import { EVMBlockFinder } from "@across-protocol/sdk/dist/esm/arch/evm/BlockUtils";
import { toAddressType as _toAddressType } from "@across-protocol/sdk/dist/esm/utils/AddressUtils";
export { getAssociatedTokenAddress } from "@across-protocol/sdk/dist/esm/arch/svm/SpokeUtils";
export { toAddress } from "@across-protocol/sdk/dist/esm/arch/svm/utils";
export { getCCTPDepositAccounts } from "@across-protocol/sdk/dist/esm/arch/svm/SpokeUtils";

export { SVMBlockFinder } from "@across-protocol/sdk/dist/esm/arch/svm/BlockUtils";
Expand Down
57 changes: 56 additions & 1 deletion src/utils/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import {
getConfig,
getChainInfo,
parseUnits,
getSVMRpc,
toAddressType,
toAddress,
Address,
getAssociatedTokenAddress,
chainIsSvm,
} from "utils";
import { ERC20__factory } from "utils/typechain";
import { SwapToken } from "utils/serverless-api/types";
Expand Down Expand Up @@ -34,7 +40,7 @@ export async function getNativeBalance(
* @param blockNumber The block number to execute the query.
* @returns a Promise that resolves to the balance of the account
*/
export async function getBalance(
export async function getEvmBalance(
chainId: ChainId,
account: string,
tokenAddress: string,
Expand All @@ -49,6 +55,55 @@ export async function getBalance(
return balance;
}

export function toSolanaKitAddress(address: Address) {
return toAddress(address);
}

export async function getSvmBalance(
chainId: string | number,
account: string,
token: string
) {
const tokenMint = toAddressType(token, Number(chainId));
const owner = toAddressType(account, Number(chainId));
const svmProvider = getSVMRpc(Number(chainId));

if (tokenMint.isZeroAddress()) {
const address = toSolanaKitAddress(owner);
const balance = await svmProvider.getBalance(address).send();
return BigNumber.from(balance.value);
}

// Get the associated token account address
const tokenAccount = await getAssociatedTokenAddress(
owner.forceSvmAddress(),
tokenMint.forceSvmAddress()
);

let balance: BigNumber;
try {
// Get token account info
const tokenAccountInfo = await svmProvider
.getTokenAccountBalance(tokenAccount)
.send();
balance = BigNumber.from(tokenAccountInfo.value.amount);
} catch (error) {
// If token account doesn't exist or other error, return 0 balance
balance = BigNumber.from(0);
}
return balance;
}

export async function getTokenBalance(
chainId: number,
account: string,
tokenAddress: string
) {
return chainIsSvm(chainId)
? getSvmBalance(chainId, account, tokenAddress)
: getEvmBalance(chainId, account, tokenAddress);
}

/**
*
* @param chainId The chain Id of the chain to query
Expand Down
9 changes: 7 additions & 2 deletions src/views/LiquidityPool/hooks/useUserLiquidityPool.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { useConnection } from "hooks";
import { useQuery } from "@tanstack/react-query";

import { getConfig, getBalance, getNativeBalance, hubPoolChainId } from "utils";
import {
getConfig,
getEvmBalance,
getNativeBalance,
hubPoolChainId,
} from "utils";
import getApiEndpoint from "utils/serverless-api";

const config = getConfig();
Expand Down Expand Up @@ -38,7 +43,7 @@ async function fetchUserLiquidityPool(
const [l1Balance, poolStateOfUser] = await Promise.all([
tokenSymbol === "ETH"
? getNativeBalance(hubPoolChainId, userAddress)
: getBalance(hubPoolChainId, userAddress, l1TokenAddress),
: getEvmBalance(hubPoolChainId, userAddress, l1TokenAddress),
getApiEndpoint().poolsUser(l1TokenAddress, userAddress),
]);
return {
Expand Down
35 changes: 4 additions & 31 deletions src/views/SwapAndBridge/components/BalanceSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { AnimatePresence, motion } from "framer-motion";
import { useMemo, useState } from "react";
import { useState } from "react";
import { BigNumber } from "ethers";
import styled from "@emotion/styled";
import {
COLORS,
compareAddressesSimple,
formatUnitsWithMaxFractions,
} from "utils";
import { useUserTokenBalances } from "hooks/useUserTokenBalances";
import { COLORS, formatUnitsWithMaxFractions } from "utils";
import { useTrackBalanceSelectorClick } from "./useTrackBalanceSelectorClick";
import { useTokenBalance } from "../hooks/useTokenBalance";

type BalanceSelectorProps = {
token: {
Expand All @@ -31,33 +27,10 @@ export function BalanceSelector({
error = false,
}: BalanceSelectorProps) {
const [isHovered, setIsHovered] = useState(false);
const tokenBalances = useUserTokenBalances();
const balance = useTokenBalance(token);

const trackBalanceSelectorClick = useTrackBalanceSelectorClick();

// Derive the balance from the latest token balances
const balance = useMemo(() => {
if (!tokenBalances.data?.balances) {
return BigNumber.from(0);
}

const chainBalances = tokenBalances.data.balances.find(
(cb) => cb.chainId === String(token.chainId)
);

if (!chainBalances) {
return BigNumber.from(0);
}

const tokenBalance = chainBalances.balances.find((b) =>
compareAddressesSimple(b.address, token.address)
);

return tokenBalance?.balance
? BigNumber.from(tokenBalance.balance)
: BigNumber.from(0);
}, [tokenBalances.data, token.chainId, token.address]);

const handlePillClick = (percentage: BalanceSelectorPercentage) => {
trackBalanceSelectorClick(percentage);

Expand Down
70 changes: 58 additions & 12 deletions src/views/SwapAndBridge/hooks/useTokenBalance.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,70 @@
import { BigNumber } from "ethers";
import { useUserTokenBalances } from "hooks/useUserTokenBalances";
import { useMemo } from "react";
import { compareAddressesSimple } from "utils";
import {
chainIsSvm,
compareAddressesSimple,
getTokenBalance,
isDefined,
} from "utils";
import { EnrichedToken } from "../components/ChainTokenSelector/ChainTokenSelectorModal";
import { useQuery } from "@tanstack/react-query";
import { useConnectionEVM } from "hooks/useConnectionEVM";
import { useConnectionSVM } from "hooks/useConnectionSVM";

// todo: implement local rpc fallback
// returns the connected wallet's balance for particular token
export function useTokenBalance(
token?: Pick<EnrichedToken, "address" | "chainId"> | null | undefined
) {
const { data: userBalances } = useUserTokenBalances();
const { data: localBalance } = useTokenBalanceLocal(token);

return useMemo(() => {
return token
? BigNumber.from(
userBalances?.balances
?.find((c) => Number(c.chainId) === token.chainId)
?.balances.find((b) =>
compareAddressesSimple(b.address, token.address)
)?.balance ?? 0
)
: undefined;
}, [token, userBalances?.balances]);
if (!token) return BigNumber.from(0);

// Try local first (we update more often)
if (localBalance !== undefined && localBalance !== null) {
return BigNumber.from(localBalance);
}

// Fall back to API balance
const balanceFromUserBalances = userBalances?.balances
?.find((c) => Number(c.chainId) === token.chainId)
?.balances.find((b) =>
compareAddressesSimple(b.address, token.address)
)?.balance;

if (
balanceFromUserBalances !== undefined &&
balanceFromUserBalances !== null
) {
return BigNumber.from(balanceFromUserBalances);
}

return BigNumber.from(0);
}, [token, localBalance, userBalances?.balances]);
}

export function useTokenBalanceLocal(
token?: Pick<EnrichedToken, "address" | "chainId"> | null | undefined
) {
const { account: evmAccount } = useConnectionEVM();
const { account: svmAccount } = useConnectionSVM();

const svmAccountString = svmAccount?.toString();

return useQuery({
queryKey: ["tokenBalanceLocal", token, svmAccountString, evmAccount],
queryFn: async () => {
if (!token) return;
const wallet = chainIsSvm(token.chainId) ? svmAccountString : evmAccount;
if (!wallet) return;
return await getTokenBalance(token?.chainId, wallet, token.address);
},
enabled:
(isDefined(evmAccount) || isDefined(svmAccountString)) &&
isDefined(token),
refetchInterval: 10_000, // 10 seconds
staleTime: 5_000, // 5 seconds
});
}
Loading