diff --git a/src/graphql/config.js b/src/graphql/config.js index 8b809049e5b..690a437f009 100644 --- a/src/graphql/config.js +++ b/src/graphql/config.js @@ -4,7 +4,7 @@ exports.config = { document: './queries/ens.graphql', schema: { method: 'POST', - url: 'https://api.thegraph.com/subgraphs/name/ensdomains/ens', + url: 'https://gateway-arbitrum.network.thegraph.com/api/35a75cae48aab2b771d1e53543a37a0f/subgraphs/id/5XqPmWe6gjyrJtFn9cLy237i4cWw2j9HcUJEXsP5qGtH', }, }, metadata: { diff --git a/src/raps/actions/claim.ts b/src/raps/actions/claim.ts index ec6b9092da8..d9fa50c7419 100644 --- a/src/raps/actions/claim.ts +++ b/src/raps/actions/claim.ts @@ -10,6 +10,8 @@ const CLAIM_MOCK_DATA = { }, }; +const DO_FAKE_CLAIM = false; + // This action is used to claim the rewards of the user // by making an api call to the backend which would use a relayer // to do the claim and send the funds to the user @@ -18,50 +20,34 @@ export async function claim({ parameters, wallet, baseNonce }: ActionProps<'clai if (!address) { throw new Error('Invalid address'); } - - console.log('claim action called with params', parameters); - // when IS_TESTING is true, we use mock data (can do as many as we want) + // when DO_FAKE_CLAIM is true, we use mock data (can do as many as we want) // otherwise we do a real claim (can be done once, then backend needs to reset it) - const claimInfo = process.env.IS_TESTING === 'true' ? CLAIM_MOCK_DATA : await metadataPOSTClient.claimUserRewards({ address }); - - // Just for testing purposes so we know what the state of the ENV VARS are - console.log('ENV VARS', { - IS_TESTING: process.env.IS_TESTING, - INTERNAL_BUILD: process.env.INTERNAL_BUILD, - }); + const claimInfo = DO_FAKE_CLAIM ? CLAIM_MOCK_DATA : await metadataPOSTClient.claimUserRewards({ address }); - console.log('got claim tx hash', claimInfo); // Checking ig we got the tx hash const txHash = claimInfo.claimUserRewards?.txHash; if (!txHash) { // If there's no transaction hash the relayer didn't submit the transaction // so we can't contnue - console.log('did not get tx hash', claimInfo); throw new Error('Failed to claim rewards'); } - console.log('getting claim tx'); // We need to make sure the transaction is mined // so we get the transaction const claimTx = await wallet?.provider?.getTransaction(txHash); - console.log('got claim tx', claimTx); - console.log('waiting for claim tx to be mined'); // then we wait for the receipt of the transaction // to conirm it was mined const receipt = await claimTx?.wait(); - console.log('got claim tx receipt', receipt); // finally we check if the transaction was successful const success = receipt?.status === 1; if (!success) { // The transaction failed, we can't continue - console.log('claim tx failed', receipt); throw new Error('Failed to claim rewards'); } // If the transaction was successful we can return the hash - console.log('Claimed succesful'); return { nonce: (baseNonce || 0) - 1, diff --git a/src/raps/actions/claimBridge.ts b/src/raps/actions/claimBridge.ts index 0c510f0a83d..8efd1726313 100644 --- a/src/raps/actions/claimBridge.ts +++ b/src/raps/actions/claimBridge.ts @@ -1,24 +1,20 @@ import { AddressZero } from '@ethersproject/constants'; -import { ChainId, CrosschainQuote, QuoteError, SwapType, getClaimBridgeQuote } from '@rainbow-me/swaps'; -import BigNumber from 'bignumber.js'; +import { CrosschainQuote, QuoteError, SwapType, getClaimBridgeQuote } from '@rainbow-me/swaps'; import { Address } from 'viem'; import { ActionProps } from '../references'; import { executeCrosschainSwap } from './crosschainSwap'; import { RainbowError, logger } from '@/logger'; -import { TransactionGasParams } from '@/__swaps__/types/gas'; import { add, lessThan, multiply, subtract } from '@/helpers/utilities'; import { getProviderForNetwork } from '@/handlers/web3'; import { Network } from '@/helpers'; import { TxHash } from '@/resources/transactions/types'; -import { NewTransaction } from '@/entities'; +import { NewTransaction, TransactionGasParamAmounts } from '@/entities'; import { addNewTransaction } from '@/state/pendingTransactions'; import { getNetworkFromChainId } from '@/utils/ethereumUtils'; -import { getSelectedGas } from '@/__swaps__/screens/Swap/hooks/useSelectedGas'; // This action is used to bridge the claimed funds to another chain export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps<'claimBridge'>) { const { address, toChainId, sellAmount, chainId } = parameters; - console.log('claimBridge action called with params', parameters); // Check if the address and toChainId are valid // otherwise we can't continue @@ -26,8 +22,6 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps throw new RainbowError('claimBridge: error getClaimBridgeQuote'); } - console.log('getting claim bridge quote'); - let maxBridgeableAmount = sellAmount; let needsNewQuote = false; @@ -43,8 +37,6 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps swapType: SwapType.crossChain, }); - console.log('got claim bridge quote', claimBridgeQuote); - // if we don't get a quote or there's an error we can't continue if (!claimBridgeQuote || (claimBridgeQuote as QuoteError)?.error) { throw new RainbowError('claimBridge: error getClaimBridgeQuote'); @@ -55,42 +47,30 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps // 2 - We use the default gas limit (already inflated) from the quote to calculate the aproximate gas fee const initalGasLimit = bridgeQuote.defaultGasLimit as string; - const selectedGas = getSelectedGas(parameters.chainId); - console.log('selectedGas', selectedGas); // Force typing since we only deal with 1559 gas params here - const gasParams = selectedGas as unknown as TransactionGasParams; + const gasParams = parameters.gasParams as TransactionGasParamAmounts; const feeAmount = add(gasParams.maxFeePerGas, gasParams.maxPriorityFeePerGas); - console.log('fee amount', new BigNumber(feeAmount).toNumber()); const gasFeeInWei = multiply(initalGasLimit, feeAmount); - console.log('gas fee in wei', new BigNumber(gasFeeInWei).toNumber()); // 3 - Check if the user has enough balance to pay the gas fee const provider = getProviderForNetwork(Network.optimism); const balance = await provider.getBalance(address); - console.log('balance', balance.toString()); // if the balance minus the sell amount is less than the gas fee we need to make adjustments if (lessThan(subtract(balance.toString(), sellAmount), gasFeeInWei)) { // if the balance is less than the gas fee we can't continue if (lessThan(sellAmount, gasFeeInWei)) { - console.log('not enough balance to bridge at all'); throw new RainbowError('claimBridge: error insufficient funds to pay gas fee'); } else { // otherwie we bridge the maximum amount we can afford - console.log('enough balance to bridge some'); maxBridgeableAmount = subtract(sellAmount, gasFeeInWei); - console.log('will bridge instead', { - claimed: sellAmount, - maxBridgeableAmount, - }); needsNewQuote = true; } } // if we need to bridge a different amount we get a new quote if (needsNewQuote) { - console.log('getting new quote with maxBridgeableAmount'); const newQuote = await getClaimBridgeQuote({ chainId, toChainId, @@ -102,10 +82,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps swapType: SwapType.crossChain, }); - console.log('got new quote', newQuote); - if (!newQuote || (newQuote as QuoteError)?.error) { - console.log('error getting new quote', newQuote); throw new RainbowError('claimBridge: error getClaimBridgeQuote (new)'); } @@ -115,7 +92,6 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps // now that we have a valid quote for the maxBridgeableAmount we can estimate the gas limit let gasLimit; try { - console.log('estimating gas limit'); try { gasLimit = await provider.estimateGas({ from: address, @@ -125,10 +101,9 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps ...gasParams, }); } catch (e) { - console.log('error estimating gas limit', e); + // Instead of failing we'll try using the default gas limit + 20% + gasLimit = (Number(bridgeQuote.defaultGasLimit) * 1.2).toString(); } - - console.log('estimated gas limit', gasLimit); } catch (e) { logger.error(new RainbowError('crosschainSwap: error estimateCrosschainSwapGasLimit'), { message: (e as Error)?.message, @@ -156,11 +131,8 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps let swap; try { - console.log('claimBridge executing crosschain swap', swapParams); swap = await executeCrosschainSwap(swapParams); - console.log('claimBridge executed crosschain swap', swap); } catch (e) { - console.log('claimBridge executeCrosschainSwap error', e); logger.error(new RainbowError('crosschainSwap: error executeCrosschainSwap'), { message: (e as Error)?.message }); throw e; } @@ -209,15 +181,12 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps ...gasParams, } satisfies NewTransaction; - console.log('claimBridge adding new transaction', transaction); - addNewTransaction({ address: bridgeQuote.from as Address, network: getNetworkFromChainId(parameters.chainId), transaction, }); - console.log('claimBridge returning nonce and hash', swap.nonce, swap.hash); return { nonce: swap.nonce, hash: swap.hash, diff --git a/src/raps/claimAndBridge.ts b/src/raps/claimAndBridge.ts index 66676476010..da46e30e87f 100644 --- a/src/raps/claimAndBridge.ts +++ b/src/raps/claimAndBridge.ts @@ -3,7 +3,7 @@ import { RapAction, RapClaimActionParameters } from './references'; export const createClaimAndBridgeRap = async (claimParameters: RapClaimActionParameters) => { let actions: RapAction<'crosschainSwap' | 'claim' | 'claimBridge'>[] = []; - const { assetToSell, sellAmount, assetToBuy, meta, chainId, toChainId, address } = claimParameters; + const { assetToSell, sellAmount, assetToBuy, meta, chainId, toChainId, address, gasParams } = claimParameters; const claim = createNewAction('claim', claimParameters); actions = actions.concat(claim); @@ -20,6 +20,7 @@ export const createClaimAndBridgeRap = async (claimParameters: RapClaimActionPar sellAmount, assetToBuy, quote: undefined, + gasParams, } satisfies RapClaimActionParameters); actions = actions.concat(bridge); diff --git a/src/raps/references.ts b/src/raps/references.ts index d4c31e38507..d8f68cf8bb9 100644 --- a/src/raps/references.ts +++ b/src/raps/references.ts @@ -76,6 +76,7 @@ export interface RapClaimActionParameters { chainId: ChainId; toChainId?: ChainId; quote: undefined; + gasParams: TransactionGasParamAmounts | LegacyTransactionGasParamAmounts; } export type RapActionParameters = diff --git a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx index 75b4cce3242..1e25f5a2917 100644 --- a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx +++ b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx @@ -5,18 +5,16 @@ import { Bleed, Box, Text, TextShadow, globalColors, useBackgroundColor, useColo import * as i18n from '@/languages'; import { ListHeader, ListPanel, Panel, TapToDismiss, controlPanelStyles } from '@/components/SmoothPager/ListPanel'; import { ChainImage } from '@/components/coin-icon/ChainImage'; -import { ChainId } from '@rainbow-me/provider/dist/references/chains'; -import { ChainNameDisplay } from '@/__swaps__/types/chains'; -import { getNetworkFromChainId, useNativeAssetForNetwork } from '@/utils/ethereumUtils'; +import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; +import ethereumUtils, { getNetworkFromChainId, useNativeAssetForNetwork } from '@/utils/ethereumUtils'; import { useAccountAccentColor, useAccountProfile, useAccountSettings } from '@/hooks'; import { safeAreaInsetValues } from '@/utils'; import { NanoXDeviceAnimation } from '@/screens/hardware-wallets/components/NanoXDeviceAnimation'; import { EthRewardsCoinIcon } from '../content/PointsContent'; -import { View } from 'react-native'; +import { Alert, View } from 'react-native'; import { IS_IOS } from '@/env'; -import { ClaimUserRewardsMutation, PointsErrorType } from '@/graphql/__generated__/metadata'; +import { PointsErrorType } from '@/graphql/__generated__/metadata'; import { useMutation } from '@tanstack/react-query'; -import { metadataPOSTClient } from '@/graphql'; import { invalidatePointsQuery, usePoints } from '@/resources/points'; import { convertAmountAndPriceToNativeDisplay, convertRawAmountToBalance } from '@/helpers/utilities'; import { Network } from '@/helpers'; @@ -26,11 +24,20 @@ import { NeonRainbowButtonMask } from '../components/NeonRainbowButtonMask'; import MaskedView from '@react-native-masked-view/masked-view'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { useNavigation } from '@/navigation'; +import { RapSwapActionParameters } from '@/raps/references'; +import { walletExecuteRap } from '@/raps/execute'; +import { ParsedAsset } from '@/__swaps__/types/assets'; +import { chainNameFromChainId } from '@/__swaps__/utils/chains'; +import { loadWallet } from '@/model/wallet'; +import { getProviderForNetwork } from '@/handlers/web3'; +import { LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; +import { getGasSettingsBySpeed } from '@/__swaps__/screens/Swap/hooks/useSelectedGas'; +import { useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; -type ClaimStatus = 'idle' | 'claiming' | 'success' | PointsErrorType; +type ClaimStatus = 'idle' | 'claiming' | 'success' | PointsErrorType | 'error'; type ClaimNetwork = '10' | '8453' | '7777777'; -const CLAIM_NETWORKS = [ChainId.optimism, ChainId.base, ChainId.zora]; +const CLAIM_NETWORKS = [ChainId.base, ChainId.optimism, ChainId.zora]; const PAGES = { CHOOSE_CLAIM_NETWORK: 'choose-claim-network', @@ -77,8 +84,8 @@ const ChooseClaimNetwork = ({ const networkListItems = useMemo(() => { const claimFees = { - [ChainId.optimism]: i18n.t(i18n.l.points.points.free_to_claim), [ChainId.base]: i18n.t(i18n.l.points.points.has_bridge_fee), + [ChainId.optimism]: i18n.t(i18n.l.points.points.free_to_claim), [ChainId.zora]: i18n.t(i18n.l.points.points.has_bridge_fee), }; @@ -152,6 +159,10 @@ const ClaimingRewards = ({ const { isDarkMode } = useColorMode(); const { goBack: closeClaimPanel } = useNavigation(); const { data: points, refetch } = usePoints({ walletAddress: address }); + const { data: meteorologyData } = useMeteorologySuggestions({ + chainId: ChainId.optimism, + enabled: true, + }); const green = useBackgroundColor('green'); @@ -179,24 +190,106 @@ const ClaimingRewards = ({ [chainId] ); - const { mutate: claimRewards } = useMutation({ + const { mutate: claimRewards } = useMutation<{ + nonce: number | null; + }>({ mutationFn: async () => { - const response = await metadataPOSTClient.claimUserRewards({ address }); - const claimInfo = response?.claimUserRewards; + // Fetch the native asset from the origin chain + const opEth_ = await ethereumUtils.getNativeAssetForNetwork(getNetworkFromChainId(ChainId.optimism)); + const opEth = { + ...opEth_, + chainName: chainNameFromChainId(ChainId.optimism), + }; - if (claimInfo?.error) { - setClaimStatus(claimInfo?.error.type); + // fetch the native asset from the destination chain + let destinationEth_; + if (chainId === ChainId.base) { + destinationEth_ = await ethereumUtils.getNativeAssetForNetwork(getNetworkFromChainId(ChainId.base)); + } else if (chainId === ChainId.zora) { + destinationEth_ = await ethereumUtils.getNativeAssetForNetwork(getNetworkFromChainId(ChainId.zora)); + } else { + destinationEth_ = opEth; } - // clear and refresh claim data so available claim UI disappears - invalidatePointsQuery(address); - await refetch(); + // Add missing properties to match types + const destinationEth = { + ...destinationEth_, + chainName: chainNameFromChainId(chainId as ChainId), + }; - return claimInfo; + const selectedGas = { + maxBaseFee: meteorologyData?.fast.maxBaseFee, + maxPriorityFee: meteorologyData?.fast.maxPriorityFee, + }; + + // We can't continue if we don't have the gas settings + // from meteorology! It's almost impossible for this to happen tho + if (!selectedGas) { + setClaimStatus('error'); + return { nonce: null }; + } + + let gasParams: TransactionGasParamAmounts | LegacyTransactionGasParamAmounts = {} as + | TransactionGasParamAmounts + | LegacyTransactionGasParamAmounts; + + gasParams = { + maxFeePerGas: selectedGas?.maxBaseFee as string, + maxPriorityFeePerGas: selectedGas?.maxPriorityFee as string, + }; + const gasFeeParamsBySpeed = getGasSettingsBySpeed(ChainId.optimism); + + const actionParams = { + address, + toChainId: chainId, + sellAmount: claimable as string, + chainId: ChainId.optimism, + assetToSell: opEth as ParsedAsset, + assetToBuy: destinationEth as ParsedAsset, + quote: undefined, + // @ts-expect-error - collision between old gas types and new + gasFeeParamsBySpeed, + gasParams, + } satisfies RapSwapActionParameters<'claimBridge'>; + + const provider = await getProviderForNetwork(Network.optimism); + const wallet = await loadWallet(address, false, provider); + if (!wallet) { + Alert.alert(i18n.t(i18n.l.swap.unable_to_load_wallet)); + return { nonce: null }; + } + + try { + const { errorMessage, nonce: bridgeNonce } = await walletExecuteRap( + wallet, + 'claimBridge', + // @ts-expect-error - collision between old gas types and new + actionParams + ); + + if (errorMessage) { + setClaimStatus('error'); + return { nonce: null }; + } + + if (typeof bridgeNonce === 'number' && bridgeNonce >= 0) { + // clear and refresh claim data so available claim UI disappears + invalidatePointsQuery(address); + refetch(); + return { nonce: bridgeNonce }; + } else { + setClaimStatus('error'); + return { nonce: null }; + } + } catch (e) { + setClaimStatus('error'); + return { nonce: null }; + } }, - onSuccess: async (data: ClaimUserRewardsMutation['claimUserRewards']) => { - setClaimStatus('success'); - // do bridging and clean up here + onSuccess: async ({ nonce }: { nonce: number | null }) => { + if (typeof nonce === 'number' && nonce >= 0) { + setClaimStatus('success'); + } }, });