Skip to content

Commit

Permalink
fix: added token balance fix bug with duplicate baseUrl (#183)
Browse files Browse the repository at this point in the history
* fix: added balance fix bug wuth url path

* fix: removed logs
  • Loading branch information
addegbenga authored Oct 16, 2024
1 parent c668050 commit 40d44ac
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 12 deletions.
3 changes: 2 additions & 1 deletion apps/mobile/src/modules/Swap/TokenSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ const RenderTokenItem = ({
onSelect: (item: IGetTokenReturnTypeObj) => void;
}) => {
const styles = useStyles(styleSheet);

return (
<TouchableOpacity style={styles.tokenItem} onPress={() => onSelect(item)}>
<Image source={{uri: item.logo_url}} style={styles.tokenLogo} />
<View style={styles.tokenInfo}>
<Text style={styles.tokenName}>{item.name}</Text>
<Text style={styles.tokenSymbol}>{item.symbol}</Text>
</View>
<Text style={styles.tokenDecimals}>{item.decimals}</Text>
{/* <Text style={styles.tokenDecimals}>{item.decimals}</Text> */}
</TouchableOpacity>
);
};
Expand Down
21 changes: 13 additions & 8 deletions apps/mobile/src/modules/Swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import {WalletModalContext} from '../../context/WalletModal';
import {useStyles, useTheme} from '../../hooks';
import {useToast} from '../../hooks/modals';
import {
useAvnuExecuteSwap,
useAvnuSwapBuildDataType,
useAvnuSwapCalldata,
useGetAvnuSwapQuoteDetails,
useGetEvmTokens,
} from '../../starknet/evm/hooks';
import {useBalanceUtil} from '../../starknet/evm/utilHook';
import styleSheet from './styles';
import TokenSelectModal from './TokenSelection';
import {formatToUSD, parseAmountToHex, parseUSD} from './util';
Expand All @@ -25,12 +24,11 @@ interface Token {

export default function TokenSwapView({showHeader = false}: {showHeader?: boolean}) {
const {showToast} = useToast();
const {address, isConnected, account} = useAccount();

const [isLoading, setIsLoading] = useState(false);
const walletModalContext = useContext(WalletModalContext);

const {address, isConnected, account} = useAccount();

const {data: tokens} = useGetEvmTokens();
const [toToken, setToToken] = useState<Token | null>(null);
const [toAmount, setToAmount] = useState<string>('0');
Expand All @@ -41,6 +39,15 @@ export default function TokenSwapView({showHeader = false}: {showHeader?: boolea
const [activeInput, setActiveInput] = useState<'from' | 'to'>('from');
const [shouldRefetchQuote, setShouldRefetchQuote] = useState(false);

const {data: fromBalance} = useBalanceUtil({
address,
token: fromToken?.l2_token_address,
});
const {data: toBalance} = useBalanceUtil({
address,
token: toToken?.l2_token_address,
});

// Determine which amount and token to use based on activeInput and reversed state
const amount = activeInput === 'to' ? toAmount : fromAmount;

Expand All @@ -62,9 +69,6 @@ export default function TokenSwapView({showHeader = false}: {showHeader?: boolea
});
const {mutate: mutateSwapCallData} = useAvnuSwapCalldata();

const {mutate: mutateExecuteSwap} = useAvnuExecuteSwap();
const {mutate: mutateSwapBuildDataType} = useAvnuSwapBuildDataType();

const theme = useTheme();
const styles = useStyles(styleSheet);

Expand Down Expand Up @@ -264,6 +268,7 @@ export default function TokenSwapView({showHeader = false}: {showHeader?: boolea
</View>
<View style={styles.balanceEstimate}>
<Text style={styles.estimate}>{estUsdValue}</Text>
<Text style={styles.balance}>{fromBalance ? `$${fromBalance?.formatted}` : ''}</Text>
</View>
</View>

Expand Down Expand Up @@ -308,6 +313,7 @@ export default function TokenSwapView({showHeader = false}: {showHeader?: boolea
</View>
<View style={styles.balanceEstimate}>
<Text style={styles.estimate}>{estUsdValue}</Text>
<Text style={styles.balance}>{toBalance ? `$${toBalance?.formatted}` : ''}</Text>
</View>
</View>

Expand All @@ -323,7 +329,6 @@ export default function TokenSwapView({showHeader = false}: {showHeader?: boolea
</TouchableOpacity>
)}
</View>

<TokenSelectModal
visible={modalVisible}
onClose={() => setModalVisible(false)}
Expand Down
6 changes: 3 additions & 3 deletions apps/mobile/src/starknet/evm/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const buildAvnuSwapCallDataFn = async (
payload: IAvnuSwapCalldata,
): Promise<IAvnuSwapCalldataReturnTypeObj> => {
try {
const response = await ApiEvmInstance2.post(avnuApi + `/swap/v2/build`, payload);
const response = await ApiEvmInstance2.post(`/swap/v2/build`, payload);
return response.data;
} catch (error) {
return Promise.reject(error);
Expand All @@ -82,7 +82,7 @@ export const buildAvnuSwapBuildTypeFn = async (
payload: IAvnuSwapBuildTypedata,
): Promise<{data: IAvnuSwapBuildDataTypeReturnTypeObj; signature: any}> => {
try {
const response = await ApiEvmInstance2.post(avnuApi + `/swap/v2/build-typed-data`, payload);
const response = await ApiEvmInstance2.post(`/swap/v2/build-typed-data`, payload);
// Extract the signature from the response headers
const signature = response.headers['signature'] || null;
return {
Expand All @@ -103,7 +103,7 @@ export const executeAvnuSwapFn = async (
payload: IAvnuExecuteSwap,
): Promise<IGetAvnuQuoteReturnTypeObj[]> => {
try {
const response = await ApiEvmInstance2.post(avnuApi + `/swap/v2/execute`, payload);
const response = await ApiEvmInstance2.post(`/swap/v2/execute`, payload);
return response.data;
} catch (error) {
return Promise.reject(error);
Expand Down
203 changes: 203 additions & 0 deletions apps/mobile/src/starknet/evm/utilHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import type {Address, Chain} from '@starknet-react/chains';
import {useContract, useNetwork} from '@starknet-react/core';
import {QueryKey, useQuery, UseQueryOptions, UseQueryResult} from '@tanstack/react-query';
import {useMemo} from 'react';
import {type BlockNumber, type CallOptions, BlockTag, num, shortString} from 'starknet';
import {formatUnits} from 'viem';

export type Balance = {
decimals: number;
symbol: string;
formatted: string;
value: bigint;
};
export type UseQueryProps<
TQueryFnData = unknown,
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> = Pick<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
'enabled' | 'refetchInterval' | 'retry' | 'retryDelay'
>;

export type UseBalanceProps = UseQueryProps<
Balance,
Error,
Balance,
ReturnType<typeof queryKey>
> & {
/** The contract's address. Defaults to the native currency. */
token?: Address | string;
/** The address to fetch balance for. */
address?: Address | string;
/** Whether to watch for changes. */
watch?: boolean;
/** Block identifier used when performing call. */
blockIdentifier?: BlockNumber;
};

export type UseBalanceResult = UseQueryResult<Balance, Error>;

/**
* Fetch the balance for the provided address and token.
*
* If no token is provided, the native currency is used.
*/
export function useBalanceUtil({
token: token_,
address,
enabled: enabled_ = true,
blockIdentifier = BlockTag.latest,
...props
}: UseBalanceProps) {
const {chain} = useNetwork();
const token = token_ ?? chain.nativeCurrency.address;

const {contract} = useContract({
abi: balanceABIFragment,
address: token,
});

const queryKey_ = useMemo(
() => queryKey({chain, token, address, blockIdentifier}),
[chain, token, address, blockIdentifier],
);

const enabled = useMemo(
() => Boolean(enabled_ && contract && address),
[enabled_, contract, address],
);

return useQuery({
enabled,
queryKey: queryKey_,
queryFn: queryFn({chain, contract, token, address, blockIdentifier}),
...props,
});
}

function queryKey({
chain,
token,
address,
blockIdentifier,
}: {
chain: Chain;
token?: string;
address?: string;
blockIdentifier?: BlockNumber;
}) {
return [
{
entity: 'balance',
chainId: chain?.name,
token,
address,
blockIdentifier,
},
] as const;
}

function queryFn({
chain,
token,
address,
contract,
blockIdentifier,
}: {
chain: Chain;
token?: string;
address?: string;
contract?: any;
blockIdentifier?: BlockNumber;
}) {
return async () => {
if (!address) throw new Error('address is required');
if (!contract) throw new Error('contract is required');

const options: CallOptions = {
blockIdentifier,
};

const isNativeCurrency = token === chain.nativeCurrency.address;

let symbol = chain.nativeCurrency.symbol;
if (!isNativeCurrency) {
const symbol_ = await contract.symbol(options);
symbol = shortString.decodeShortString(num.toHex(symbol_));
}

let decimals = chain.nativeCurrency.decimals;
if (!isNativeCurrency) {
const decimals_ = await contract.decimals(options);
decimals = Number(decimals_);
}

const balanceOf = (await contract.balanceOf(address, options)) as bigint;

const formatted = formatUnits(balanceOf, decimals);

return {
value: balanceOf,
decimals,
symbol,
formatted,
};
};
}

const balanceABIFragment = [
{
name: 'core::integer::u256',
type: 'struct',
members: [
{
name: 'low',
type: 'core::integer::u128',
},
{
name: 'high',
type: 'core::integer::u128',
},
],
},
{
name: 'balanceOf',
type: 'function',
inputs: [
{
name: 'account',
type: 'core::starknet::contract_address::ContractAddress',
},
],
outputs: [
{
type: 'core::integer::u256',
},
],
state_mutability: 'view',
},
{
name: 'symbol',
type: 'function',
inputs: [],
outputs: [
{
type: 'core::felt252',
},
],
state_mutability: 'view',
},
{
name: 'decimals',
type: 'function',
inputs: [],
outputs: [
{
type: 'core::integer::u8',
},
],
state_mutability: 'view',
},
] as const;

0 comments on commit 40d44ac

Please sign in to comment.