diff --git a/src/hooks/useBestV3Trade.ts b/src/hooks/useBestV3Trade.ts index 9d505fc2..d0378576 100644 --- a/src/hooks/useBestV3Trade.ts +++ b/src/hooks/useBestV3Trade.ts @@ -7,7 +7,18 @@ import { useAllV3Routes } from './useAllV3Routes' import { useBlockNumber, useContractRead } from '@starknet-react/core' import SWAP_QUOTER_ABI from 'contracts/swapquoter/abi.json' import { DEFAULT_CHAIN_ID, SWAP_ROUTER_ADDRESS } from 'constants/tokens' -import { BigNumberish, BlockNumber, CallData, TransactionType, cairo, encode, num } from 'starknet' +import { + BigNumberish, + BlockNumber, + CallData, + Invocation, + InvocationsDetails, + RpcProvider, + TransactionType, + cairo, + encode, + num, +} from 'starknet' import { TradeState } from 'state/routing/types' import { ec, hash, json, Contract, WeierstrassSignatureType } from 'starknet' import { useAccountDetails } from './starknet-react' @@ -18,6 +29,10 @@ import { useQuery } from 'react-query' // import { useV3Quoter } from './useContract' import ERC20_ABI from 'abis/erc20.json' +const provider = new RpcProvider({ + nodeUrl: 'https://starknet-testnet.public.blastapi.io/rpc/v0_6', +}) + export enum V3TradeState { LOADING, INVALID, @@ -56,7 +71,7 @@ export function useBestV3TradeExactIn( trade: null, } - const { account, address, chainId } = useAccountDetails() + const { account, address, chainId, connector } = useAccountDetails() const swapRouterAddress = SWAP_ROUTER_ADDRESS[chainId ?? DEFAULT_CHAIN_ID] const quoteExactInInputs = useMemo(() => { @@ -101,9 +116,8 @@ export function useBestV3TradeExactIn( } const call = { - contractAddress: swapRouterAddress, - entrypoint: 'exact_input', calldata: CallData.compile(exactInputSingleParams), + route, } return call @@ -125,9 +139,8 @@ export function useBestV3TradeExactIn( } const call = { - contractAddress: swapRouterAddress, - entrypoint: 'exact_input_single', - calldata: CallData.compile(exactInputSingleParams), + calldata: exactInputSingleParams, + route, } return call @@ -135,53 +148,107 @@ export function useBestV3TradeExactIn( }) }, [routes, amountIn, address]) - const approveCall = useMemo(() => { + const approveSelector = useMemo(() => { if (!amountIn) return - const approveParams = { - spender: swapRouterAddress, - approveAmount: cairo.uint256(2 ** 128), - } - return { - contractAddress: amountIn.currency.address, - entrypoint: 'approve', - calldata: CallData.compile(approveParams), + currency_address: amountIn?.currency?.address, + selector: hash.getSelectorFromName('approve'), } }, [amountIn]) - const compiledApprovedCall = useMemo(() => { - if (!approveCall) return - return CallData.compile(approveCall) - }, [approveCall]) + const totalTx = { + totalTx: '0x2', + } - const callsArr = useMemo(() => { - if (!quoteExactInInputs || !quoteExactInInputs.length || !account || !address) return - const results = quoteExactInInputs.map((input, index) => { - return [approveCall, input] - }) + const approve_call_data_length = { approve_call_data_length: '0x03' } + const approve_call_data = { + router_address: swapRouterAddress, + approveAmount: cairo.uint256(2 ** 128), + } + + //exact input + const inputSelector = { + contract_address: swapRouterAddress, + entry_point: hash.getSelectorFromName('exact_input_single'), + } + const input_call_data_length = { input_call_data_length: '0xb' } + + const nonce_results = useQuery({ + queryKey: [`nonce/${address}`], + queryFn: async () => { + if (!account) return + const results = await account?.getNonce() + return cairo.felt(results.toString()) + }, + onSuccess: (data) => { + // Handle the successful data fetching here if needed + }, + }) - return results - }, [quoteExactInInputs, approveCall]) + const privateKey = '0x1234567890987654321' + + const message: BigNumberish[] = [1, 128, 18, 14] + + const msgHash = hash.computeHashOnElements(message) + const signature: WeierstrassSignatureType = ec.starkCurve.sign(msgHash, privateKey) // const fetchResults = useFetchResults(account, blockNumber, callsArr) const amountOutResults = useQuery({ queryKey: ['get_simulation', address, amountIn], queryFn: async () => { - if (!address || !account || !callsArr || !callsArr.length) return - - const callPromises = callsArr.map(async (call: any) => { - const response = await account.simulateTransaction([{ type: TransactionType.INVOKE, payload: call }], { - skipValidate: true, - }) + if (!address || !account || !approveSelector || !quoteExactInInputs || !connector || !nonce_results) return + const nonce = Number(nonce_results.data) + const callPromises = quoteExactInInputs.map(async (call: any) => { + const isConnectorBraavos = connector.id === 'braavos' + + const payload = isConnectorBraavos + ? { + contractAddress: address, + calldata: CallData.compile({ + ...totalTx, + ...approveSelector, + ...{ approve_offset: '0x0' }, + ...approve_call_data_length, + ...inputSelector, + ...{ input_offset: approve_call_data_length }, + ...input_call_data_length, + ...{ total_call_data_length: '0xe' }, + ...approve_call_data, + ...call.calldata, + }), + } + : { + contractAddress: address, + calldata: CallData.compile({ + ...totalTx, + ...approveSelector, + ...approve_call_data_length, + ...approve_call_data, + ...inputSelector, + ...input_call_data_length, + ...call.calldata, + }), + } + // const compiledCall = CallData. + const response = provider.simulateTransaction( + [{ type: TransactionType.INVOKE, ...payload, signature, nonce }], + { + skipValidate: true, + } + ) return response }) const settledResults = await Promise.allSettled(callPromises as any) + const settledResultsWithRoute = settledResults.map((result, i) => ({ ...result, route: routes[i] })) - const resolvedResults = settledResults + const resolvedResults = settledResultsWithRoute .filter((result) => result.status === 'fulfilled') - .map((result: any) => result.value) + .map((result: any) => { + const response = { ...result.value, route: result.route } + return response + }) return resolvedResults }, @@ -201,27 +268,30 @@ export function useBestV3TradeExactIn( const data = amountOutResults?.data if (!data) return - const subRoutesArray = data.map((subArray) => subArray[0]) + const subRoutesArray = data.map((subArray) => ({ ...subArray[0], route: subArray.route })) const bestRouteResults = { bestRoute: null, amountOut: null } - const { bestRoute, amountOut } = subRoutesArray.reduce((currentBest: any, result: any, i: any) => { - const selected_tx_result = result?.transaction_trace?.execute_invocation?.result - const value = selected_tx_result[selected_tx_result.length - 2] - const amountOut = fromUint256ToNumber({ high: value }) - if (!result) return currentBest - if (currentBest.amountOut === null) { - return { - bestRoute: routes[i], - amountOut, - } - } else if (Number(cairo.felt(currentBest.amountOut)) < Number(cairo.felt(amountOut))) { - return { - bestRoute: routes[i], - amountOut, + + const { bestRoute, amountOut } = subRoutesArray + .filter((result: any) => result?.transaction_trace?.execute_invocation?.result) + .reduce((currentBest: any, result: any, i: any) => { + const selected_tx_result = result?.transaction_trace?.execute_invocation?.result + const value = selected_tx_result[selected_tx_result.length - 2] + const amountOut = fromUint256ToNumber({ high: value }) + if (!result) return currentBest + if (currentBest.amountOut === null) { + return { + bestRoute: result?.route, + amountOut, + } + } else if (Number(cairo.felt(currentBest.amountOut)) < Number(cairo.felt(amountOut))) { + return { + bestRoute: result?.route, + amountOut, + } } - } - return currentBest - }, bestRouteResults) + return currentBest + }, bestRouteResults) return { bestRoute, amountOut } }, [amountOutResults]) @@ -288,7 +358,7 @@ export function useBestV3TradeExactOut( // const quoter = useV3Quoter() const deadline = useTransactionDeadline() const { routes, loading: routesLoading } = useAllV3Routes(allPools, currencyIn, amountOut?.currency) - const { address, account, chainId } = useAccountDetails() + const { address, account, chainId, connector } = useAccountDetails() const swapRouterAddress = SWAP_ROUTER_ADDRESS[chainId ?? DEFAULT_CHAIN_ID] const quoteExactOutInputs = useMemo(() => { @@ -335,9 +405,8 @@ export function useBestV3TradeExactOut( } const call = { - contractAddress: swapRouterAddress, - entrypoint: 'exact_output', - calldata: CallData.compile(exactOutputSingleParams), + calldata: exactOutputSingleParams, + route, } return call @@ -359,9 +428,8 @@ export function useBestV3TradeExactOut( } const call = { - contractAddress: swapRouterAddress, - entrypoint: 'exact_output_single', - calldata: CallData.compile(exactOutputSingleParams), + calldata: exactOutputSingleParams, + route, } return call @@ -369,53 +437,108 @@ export function useBestV3TradeExactOut( }) }, [routes && routes.length, amountOut]) - const approveCall = useMemo(() => { - if (!amountOut || !currencyIn) return - const approveParams = { - spender: swapRouterAddress, - approveAmount: cairo.uint256(2 ** 128), - } - + const approveSelector = useMemo(() => { + if (!currencyIn) return return { - contractAddress: (currencyIn as any).address, - entrypoint: 'approve', - calldata: CallData.compile(approveParams), + currency_address: (currencyIn as any).address, + selector: hash.getSelectorFromName('approve'), } - }, [amountOut, currencyIn]) + }, [currencyIn]) - // const { data, error } = useQuoteExactInput(compiledCallData) + const totalTx = { + totalTx: '0x2', + } - const message: BigNumberish[] = [1, 128, 18, 14] + const approve_call_data_length = { approve_call_data_length: '0x03' } + const approve_call_data = { + router_address: swapRouterAddress, + approveAmount: cairo.uint256(2 ** 128), + } - const callsArr = useMemo(() => { - if (!quoteExactOutInputs || !quoteExactOutInputs.length) return + //exact input + const outputSelector = { + contract_address: swapRouterAddress, + entry_point: hash.getSelectorFromName('exact_output_single'), + } + const output_call_data_length = { output_call_data_length: '0xb' } - const results = quoteExactOutInputs.map((input, index) => { - return [approveCall, input] - }) + const nonce_results = useQuery({ + queryKey: [`nonce/${address}`], + queryFn: async () => { + if (!account) return + const results = await account?.getNonce() + return cairo.felt(results.toString()) + }, + onSuccess: (data) => { + // Handle the successful data fetching here if needed + }, + }) - return results - }, [quoteExactOutInputs, approveCall]) + const privateKey = '0x1234567890987654321' + + const message: BigNumberish[] = [1, 128, 18, 14] + + const msgHash = hash.computeHashOnElements(message) + const signature: WeierstrassSignatureType = ec.starkCurve.sign(msgHash, privateKey) const amountInResults = useQuery({ queryKey: [address, amountOut], queryFn: async () => { - if (!address || !account || !callsArr || !callsArr.length) return + if (!address || !account || !quoteExactOutInputs || !approveSelector || !connector || !nonce_results) return + const nonce = Number(nonce_results.data) + + const callPromises = quoteExactOutInputs.map(async (call: any) => { + const isConnectorBraavos = connector.id === 'braavos' + + const payload = isConnectorBraavos + ? { + contractAddress: address, + calldata: CallData.compile({ + ...totalTx, + ...approveSelector, + ...{ approve_offset: '0x0' }, + ...approve_call_data_length, + ...outputSelector, + ...{ input_offset: approve_call_data_length }, + ...output_call_data_length, + ...{ total_call_data_length: '0xe' }, + ...approve_call_data, + ...call.calldata, + }), + } + : { + contractAddress: address, + calldata: CallData.compile({ + ...totalTx, + ...approveSelector, + ...approve_call_data_length, + ...approve_call_data, + ...outputSelector, + ...output_call_data_length, + ...call.calldata, + }), + } - const callPromises = callsArr.map(async (call: any) => { - const response = await account.simulateTransaction([{ type: TransactionType.INVOKE, payload: call }], { - skipValidate: true, - }) + // const compiledCall = CallData. + const response = provider.simulateTransaction( + [{ type: TransactionType.INVOKE, ...payload, signature, nonce }], + { + skipValidate: true, + } + ) return response }) const settledResults = await Promise.allSettled(callPromises as any) + const settledResultsWithRoute = settledResults.map((result, i) => ({ ...result, route: routes[i] })) - const resolvedResults = settledResults + const resolvedResults = settledResultsWithRoute .filter((result) => result.status === 'fulfilled') - .map((result: any) => result.value) - + .map((result: any) => { + const response = { ...result.value, route: result.route } + return response + }) return resolvedResults }, onSuccess: (data) => { @@ -434,27 +557,29 @@ export function useBestV3TradeExactOut( const data = amountInResults?.data if (!data) return - const subRoutesArray = data.map((subArray) => subArray[0]) + const subRoutesArray = data.map((subArray) => ({ ...subArray[0], route: subArray.route })) const bestRouteResults = { bestRoute: null, amountIn: null } - const { bestRoute, amountIn } = subRoutesArray.reduce((currentBest: any, result: any, i: any) => { - const selected_tx_result = result?.transaction_trace?.execute_invocation?.result - const value = selected_tx_result[selected_tx_result.length - 2] - const amountIn = fromUint256ToNumber({ high: value }) - if (!result) return currentBest - if (currentBest.amountIn === null) { - return { - bestRoute: routes[i], - amountIn, - } - } else if (Number(cairo.felt(currentBest.amountIn)) < Number(cairo.felt(amountIn))) { - return { - bestRoute: routes[i], - amountIn, + const { bestRoute, amountIn } = subRoutesArray + .filter((result: any) => result?.transaction_trace?.execute_invocation?.result) + .reduce((currentBest: any, result: any, i: any) => { + const selected_tx_result = result?.transaction_trace?.execute_invocation?.result + const value = selected_tx_result[selected_tx_result.length - 2] + const amountIn = fromUint256ToNumber({ high: value }) + if (!result) return currentBest + if (currentBest.amountIn === null) { + return { + bestRoute: result?.route, + amountIn, + } + } else if (Number(cairo.felt(currentBest.amountIn)) < Number(cairo.felt(amountIn))) { + return { + bestRoute: result?.route, + amountIn, + } } - } - return currentBest - }, bestRouteResults) + return currentBest + }, bestRouteResults) return { bestRoute, amountIn } }, [amountInResults]) diff --git a/src/hooks/usePools.ts b/src/hooks/usePools.ts index a0a99975..6ed998b8 100644 --- a/src/hooks/usePools.ts +++ b/src/hooks/usePools.ts @@ -241,8 +241,8 @@ export function usePoolAddress( currencyB: Currency | undefined, feeAmount: FeeAmount | undefined ): string | undefined { + const { chainId } = useAccountDetails() return useMemo(() => { - const { chainId } = useAccountDetails() if (currencyA && currencyB && feeAmount && chainId) { const tokenA = currencyA.wrapped const tokenB = currencyB.wrapped @@ -266,5 +266,5 @@ export function usePoolAddress( } return undefined - }, [currencyA, currencyB, feeAmount]) + }, [currencyA, currencyB, feeAmount, chainId]) } diff --git a/src/hooks/useV3PositionFees.ts b/src/hooks/useV3PositionFees.ts index 41cd9872..295fdddc 100644 --- a/src/hooks/useV3PositionFees.ts +++ b/src/hooks/useV3PositionFees.ts @@ -9,10 +9,25 @@ import { useBlockNumber as uBlockNumber, useContract, useContractRead } from '@s import NFTPositionManagerABI from 'contracts/nonfungiblepositionmanager/abi.json' import { useV3NFTPositionManagerContract } from './useContract' import { DEFAULT_CHAIN_ID, MAX_UINT128, NONFUNGIBLE_POOL_MANAGER_ADDRESS } from 'constants/tokens' -import { CallData, cairo, validateAndParseAddress } from 'starknet' +import { + BigNumberish, + CallData, + RpcProvider, + TransactionType, + WeierstrassSignatureType, + cairo, + ec, + hash, + validateAndParseAddress, +} from 'starknet' import POOL_ABI from 'contracts/pool/abi.json' import { toI32 } from 'utils/toI32' import { useAccountDetails } from './starknet-react' +import { useQuery } from 'react-query' + +const provider = new RpcProvider({ + nodeUrl: 'https://starknet-testnet.public.blastapi.io/rpc/v0_6', +}) // compute current + counterfactual fees for a v3 position export function useV3PositionFees( @@ -82,38 +97,125 @@ export const usePositionOwner = (tokenId: number) => { return { ownerOf: ownerOf ? validateAndParseAddress(ownerOf.toString()) : undefined, isLoading, error } } -export const useStaticFeeResults = (poolAddress: string, owner: string, position: Position, asWETH: false) => { - const callData = { - owner, - tick_lower: toI32(position.tickLower), - tick_upper: toI32(position.tickUpper), - amount0_requested: MAX_UINT128, - amount1_requested: MAX_UINT128, +export const useStaticFeeResults = ( + poolAddress: string, + owner: string, + position: Position, + asWETH: false, + parsedTokenId: number +) => { + const { account, address, connector, chainId } = useAccountDetails() + + const privateKey = '0x1234567890987654321' + + const message: BigNumberish[] = [1, 128, 18, 14] + + const msgHash = hash.computeHashOnElements(message) + const signature: WeierstrassSignatureType = ec.starkCurve.sign(msgHash, privateKey) + + const collectSelector = useMemo(() => { + if (!chainId) return + return { + contract_address: NONFUNGIBLE_POOL_MANAGER_ADDRESS[chainId], + selector: hash.getSelectorFromName('collect'), + } + }, [chainId]) + + const totalTx = { + totalTx: '0x1', } - position.pool.token0 + const collect_call_data_length = { approve_call_data_length: '0x05' } + + const nonce_results = useQuery({ + queryKey: [`nonce/${poolAddress}/${parsedTokenId}/${account?.address}`], + queryFn: async () => { + if (!account) return + const results = await account?.getNonce() + return cairo.felt(results.toString()) + }, + onSuccess: (data) => { + // Handle the successful data fetching here if needed + }, + }) - const compiledData = CallData.compile(callData) + const fee_results = useQuery({ + queryKey: [`fee/${address}/${nonce_results.data}/${parsedTokenId}`], + queryFn: async () => { + if (!account || !address || !nonce_results || !parsedTokenId || !connector || !collectSelector) return + const nonce_data = nonce_results.data + if (!nonce_data) return undefined + const nonce = Number(nonce_data) + const isConnectorBraavos = connector.id === 'braavos' - const { data } = useContractRead({ - functionName: 'static_collect', - args: [compiledData], - abi: POOL_ABI, - address: poolAddress, - watch: true, + const collect_call_data = { + tokenId: cairo.uint256(parsedTokenId), + recipient: address, + amount0_max: MAX_UINT128, + amount1_max: MAX_UINT128, + } + const payload = isConnectorBraavos + ? { + contractAddress: address, + calldata: CallData.compile({ + ...totalTx, + ...collectSelector, + ...{ collect_offset: '0x0' }, + ...collect_call_data_length, + ...{ total_call_data_length: '0x5' }, + ...collect_call_data, + }), + } + : { + contractAddress: address, + calldata: CallData.compile({ + ...totalTx, + ...collectSelector, + ...collect_call_data_length, + ...collect_call_data, + }), + } + + // const compiledCall = CallData. + const response = await provider.simulateTransaction( + [ + { + type: TransactionType.INVOKE, + ...payload, + signature, + nonce, + }, + ], + { + skipValidate: true, + } + ) + + // return response + + const typedResponse: any = response + + const tx_response: any = typedResponse + ? typedResponse[0]?.transaction_trace.execute_invocation?.result + : undefined + + return tx_response + }, }) - if (data) { - const dataObj = data as any - return [ - CurrencyAmount.fromRawAmount( - asWETH ? position.pool.token0 : unwrappedToken(position.pool.token0), - dataObj[0].toString() - ), - CurrencyAmount.fromRawAmount( - asWETH ? position.pool.token1 : unwrappedToken(position.pool.token1), - dataObj[1].toString() - ), - ] + if (fee_results && fee_results.data) { + const results: string[] = fee_results.data + if (results) { + return [ + CurrencyAmount.fromRawAmount( + asWETH ? position.pool.token0 : unwrappedToken(position.pool.token0), + results[results.length - 2].toString() + ), + CurrencyAmount.fromRawAmount( + asWETH ? position.pool.token1 : unwrappedToken(position.pool.token1), + results[results.length - 1].toString() + ), + ] + } else return [undefined, undefined] } else { return [undefined, undefined] } diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 0c6aedfc..142b32ee 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -18,7 +18,7 @@ import { flexRowNoWrap } from 'theme/styles' import { Z_INDEX } from 'theme/zIndex' import { RouteDefinition, routes, useRouterConfig } from './RouteDefinitions' import useFetchAllPairsCallback from 'hooks/useFetchAllPairs' -import Footer from 'components/Footer' +// import Footer from 'components/Footer' const BodyWrapper = styled.div<{ bannerIsVisible?: boolean }>` display: flex; @@ -139,7 +139,7 @@ export default function App() { )} -