Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: added token balance fix bug with duplicate baseUrl #183

Merged
merged 2 commits into from
Oct 16, 2024
Merged
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
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;
Loading