diff --git a/src/contexts/ChainTransactionContext.js b/src/contexts/ChainTransactionContext.js index e797b069c..9afc1429a 100644 --- a/src/contexts/ChainTransactionContext.js +++ b/src/contexts/ChainTransactionContext.js @@ -4,6 +4,7 @@ import { isEqual, get } from 'lodash'; import { hash, num, shortString, uint256 } from 'starknet'; import { fetchBuildExecuteTransaction, fetchQuotes } from '@avnu/avnu-sdk'; import * as gasless from '@avnu/gasless-sdk'; +import { ArgentSessionService, SessionDappService } from '@argent/x-sessions'; import useActivitiesContext from '~/hooks/useActivitiesContext'; import useCrewContext from '~/hooks/useCrewContext'; @@ -17,6 +18,7 @@ import api from '~/lib/api'; import { cleanseTxHash, safeBigInt } from '~/lib/utils'; import { TOKEN } from '~/lib/priceUtils'; + const RETRY_INTERVAL = 5e3; // 5 seconds const ChainTransactionContext = createContext(); @@ -405,12 +407,12 @@ export function ChainTransactionProvider({ children }) { blockNumber, blockTime, chainId, - getOutsideExecutionData, isDeployed, logout, payGasWithSwayIfPossible, provider, starknetSession, + starknetSessionData, upgradeInsecureSession, walletAccount } = useSession(); @@ -461,6 +463,45 @@ export function ChainTransactionProvider({ children }) { [blockNumber, crew?.Crew?.actionType, crew?.Crew?.actionRound, crew?._actionTypeTriggered] ); + // Retrieves an outside execution call and signs it + const getOutsideExecutionData = useCallback(async (calldata, gasTokenAddress, maxGasTokenAmount, useSessionKeyIfPossible) => { + const typedData = await gasless.fetchBuildTypedData( + accountAddress, + calldata, + gasTokenAddress, + maxGasTokenAmount, + { baseUrl: process.env.REACT_APP_AVNU_API_URL } + ); + + let signature; + + if (useSessionKeyIfPossible && starknetSessionData) { + const { accountSessionSignature, dappKey, sessionRequest } = starknetSessionData; + const beService = new ArgentSessionService(dappKey.publicKey, accountSessionSignature, process.env.REACT_APP_ARGENT_API); + const sessionDappService = new SessionDappService( + beService, + shortString.encodeShortString(chainId), + dappKey + ); + const { Calldata: feeCalldata } = typedData.message.Calls[0]; + + // Add the fee call to the calldata + calldata.unshift({ contractAddress: gasTokenAddress, entrypoint: 'transfer', calldata: feeCalldata }); + signature = await sessionDappService.getSessionSignatureForOutsideExecutionTypedData( + accountSessionSignature, + sessionRequest, + calldata, + accountAddress, + typedData, + false + ); + } else { + signature = await walletAccount.signMessage(typedData); + } + + return { typedData, signature }; + }, [accountAddress, chainId, starknetSessionData, walletAccount]); + const executeWithAccount = useCallback(async (calls) => { // Format calls for proper stringification const formattedCalls = calls.map((call) => { @@ -490,19 +531,14 @@ export function ChainTransactionProvider({ children }) { [{ type: 'INVOKE_FUNCTION', payload: calls }], { skipValidate: true } ); - console.log('simulation', simulation); const tokens = await gasless.fetchGasTokenPrices({ baseUrl: process.env.REACT_APP_AVNU_API_URL }); - console.log('fetchGasTokenPrices', tokens); gasToken = tokens.find((t) => Address.areEqual(t.tokenAddress, TOKEN.SWAY)); - console.log('gasToken', gasToken); - console.log('swayBalance', swayBalance); // Triple the fee estimation and check for sufficient funds to ensure transaction success // TODO: figure out why some txs require this maxFee = gasless.getGasFeesInGasToken(simulation[0].suggestedMaxFee, gasToken) * 3n; - console.log('maxFee', maxFee); canPayGasWithSway = (swayBalance >= maxFee); } catch (e) { diff --git a/src/contexts/SessionContext.js b/src/contexts/SessionContext.js index e304fb731..486f4e51c 100644 --- a/src/contexts/SessionContext.js +++ b/src/contexts/SessionContext.js @@ -71,6 +71,7 @@ export function SessionProvider({ children }) { const [connecting, setConnecting] = useState(false); const [status, setStatus] = useState(STATUSES.DISCONNECTED); + const [starknetSessionData, setStarknetSessionData] = useState(); const [starknetSession, setStarknetSession] = useState(); const [connectedAccount, setConnectedAccount] = useState(); @@ -415,16 +416,20 @@ export function SessionProvider({ children }) { }, [authenticate, currentSession, gameplay.useSessions]); const createSessionAccount = useCallback(async () => { - const offchainSessionAccount = await buildSessionAccount({ + const currentSessionData = { accountSessionSignature: currentSession.sessionSignature, + dappKey: currentSession.sessionDappKey, sessionRequest: currentSession.sessionRequest, + }; + setStarknetSessionData(currentSessionData); + + const offchainSessionAccount = await buildSessionAccount({ + ...currentSessionData, provider, chainId: resolveChainId(process.env.REACT_APP_CHAIN_ID, 'hex'), address: currentSession.accountAddress, - dappKey: currentSession.sessionDappKey, argentSessionServiceBaseUrl: process.env.REACT_APP_ARGENT_API }); - setStarknetSession(offchainSessionAccount); }, [currentSession, provider]); @@ -452,6 +457,7 @@ export function SessionProvider({ children }) { } setStarknetSession(null); // clear session key if it exists + setStarknetSessionData(null); // clear session key if it exists } else if (status === STATUSES.CONNECTED) { resumeOrAuthenticate().finally(() => { setReadyForChildren(true); @@ -503,43 +509,6 @@ export function SessionProvider({ children }) { isFeeAbstractionCompatible ]); - // Retrieves an outside execution call and signs it - const getOutsideExecutionData = useCallback(async (calldata, gasTokenAddress, maxGasTokenAmount, canUseSessionKey) => { - let typedData = await gasless.fetchBuildTypedData( - currentSession.accountAddress, - calldata, - gasTokenAddress, - maxGasTokenAmount, - { baseUrl: process.env.REACT_APP_AVNU_API_URL } - ); - - let signature; - - if (canUseSessionKey && gameplay.useSessions && currentSession.sessionRequest) { - const dappKey = currentSession.sessionDappKey; - const sessionSignature = currentSession.sessionSignature; - const beService = new ArgentSessionService(dappKey.publicKey, sessionSignature, process.env.REACT_APP_ARGENT_API); - const chainId = shortString.encodeShortString(connectedChainId); - const sessionDappService = new SessionDappService(beService, chainId, dappKey); - const { Calldata: feeCalldata } = typedData.message.Calls[0]; - - // Add the fee call to the calldata - calldata.unshift({ contractAddress: gasTokenAddress, entrypoint: 'transfer', calldata: feeCalldata }); - signature = await sessionDappService.getSessionSignatureForOutsideExecutionTypedData( - currentSession.sessionSignature, - currentSession.sessionRequest, - calldata, - currentSession.accountAddress, - typedData, - false - ); - } else { - signature = await walletAccount.signMessage(typedData); - } - - return { typedData, signature }; - }, [currentSession, gameplay.useSessions, connectedChainId, connectedWalletId, walletAccount]); - // Block management ------------------------------------------------------------------------------------------------- // If using devnet, put "create block" on a timer since otherwise, blocks will not be advancing in the background @@ -659,12 +628,12 @@ export function SessionProvider({ children }) { authenticating: [STATUSES.AUTHENTICATING, STATUSES.CONNECTING].includes(status), chainId: authenticated ? connectedChainId : null, connecting: connecting || !!promptLogin, - getOutsideExecutionData, isDeployed: authenticated ? currentSession?.isDeployed : null, payGasWithSwayIfPossible: authenticated ? payGasWithSwayIfPossible : null, provider, shouldUseSessionKeys, starknetSession, + starknetSessionData, status, token: authenticated ? currentSession?.token : null, upgradeInsecureSession, diff --git a/src/hooks/useShoppingListData.js b/src/hooks/useShoppingListData.js index bfa24a2df..c992a89c7 100644 --- a/src/hooks/useShoppingListData.js +++ b/src/hooks/useShoppingListData.js @@ -19,6 +19,8 @@ const useShoppingListData = (asteroidId, lotId, productIds) => { const [feeEnforcements, setFeeEnforcements] = useState(); const [feesLoading, setFeesLoading] = useState(true); const loadFees = useCallback(async () => { + if (exchangesLoading) return; + const ids = (exchanges || []).map((e) => e.Control?.controller?.id); if (ids?.length > 0) { setFeesLoading(true); @@ -49,7 +51,7 @@ const useShoppingListData = (asteroidId, lotId, productIds) => { } } setFeesLoading(false); - }, [exchangesUpdatedAt]); + }, [exchangesLoading, exchangesUpdatedAt]); useEffect(() => { loadFees(); }, [loadFees]);