From 679a0d1ec833b8dd103c1c47416f265bef2f13fa Mon Sep 17 00:00:00 2001 From: katzman Date: Fri, 27 Jun 2025 14:04:06 -0700 Subject: [PATCH 1/4] Update paymaster integration to use latest recommendations --- apps/web/app/CryptoProviders.tsx | 1 + .../src/hooks/useWriteContractsWithLogs.ts | 50 +++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/apps/web/app/CryptoProviders.tsx b/apps/web/app/CryptoProviders.tsx index c87f589ba42..17c58c9d6ab 100644 --- a/apps/web/app/CryptoProviders.tsx +++ b/apps/web/app/CryptoProviders.tsx @@ -17,6 +17,7 @@ export type CryptoProvidersProps = { const config = createConfig({ chains: [base, baseSepolia, mainnet], + multiInjectedProviderDiscovery: false, connectors: [coinbaseWallet()], transports: { [base.id]: http(cdpBaseRpcEndpoint), diff --git a/apps/web/src/hooks/useWriteContractsWithLogs.ts b/apps/web/src/hooks/useWriteContractsWithLogs.ts index 40ebb147805..6cd6c596837 100644 --- a/apps/web/src/hooks/useWriteContractsWithLogs.ts +++ b/apps/web/src/hooks/useWriteContractsWithLogs.ts @@ -2,11 +2,12 @@ import { useAnalytics } from 'apps/web/contexts/Analytics'; import { useErrors } from 'apps/web/contexts/Errors'; import { decodeRawLog, USER_OPERATION_EVENT_LOG_NAME } from 'apps/web/src/utils/transactionLogs'; import { ActionType } from 'libs/base-ui/utils/logEvent'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState, useMemo } from 'react'; import { Chain } from 'viem'; +import { base } from 'viem/chains'; import { WriteContractsParameters } from 'viem/experimental'; import { useAccount, useSwitchChain, useWaitForTransactionReceipt } from 'wagmi'; -import { useCallsStatus, useWriteContracts } from 'wagmi/experimental'; +import { useCallsStatus, useWriteContracts, useCapabilities } from 'wagmi/experimental'; import useCapabilitiesSafe from 'apps/web/src/hooks/useCapabilitiesSafe'; /* @@ -55,11 +56,45 @@ export default function useWriteContractsWithLogs({ // Errors & Analytics const { logEventWithContext } = useAnalytics(); const { logError } = useErrors(); - const { atomicBatch: atomicBatchEnabled } = useCapabilitiesSafe({ chainId: chain.id }); - const { chain: connectedChain } = useAccount(); + const { atomicBatch: atomicBatchEnabled, paymasterService: paymasterServiceEnabled } = + useCapabilitiesSafe({ chainId: chain.id }); + const { chain: connectedChain, address } = useAccount(); const [batchCallsStatus, setBatchCallsStatus] = useState(BatchCallsStatus.Idle); + // Get available capabilities for paymaster URL + const { data: availableCapabilities } = useCapabilities({ + account: address, + }); + + // Construct capabilities object with paymaster URL + const capabilities = useMemo(() => { + if (!paymasterServiceEnabled || !availableCapabilities || !chain.id) return {}; + + const capabilitiesForChain = availableCapabilities[chain.id]; + if ( + capabilitiesForChain?.['paymasterService'] && + capabilitiesForChain['paymasterService'].supported + ) { + // Use environment variable for paymaster URL + const paymasterUrl = + chain.id === base.id + ? process.env.NEXT_PUBLIC_BASE_PAYMASTER_SERVICE + : process.env.NEXT_PUBLIC_BASE_SEPOLIA_PAYMASTER_SERVICE; + + if (paymasterUrl) { + return { + paymasterService: { + url: paymasterUrl, + }, + }; + } else { + console.warn(`Paymaster service is supported but no URL configured for chain ${chain.id}`); + } + } + return {}; + }, [paymasterServiceEnabled, availableCapabilities, chain.id]); + // Write the contract const { writeContractsAsync, @@ -113,7 +148,11 @@ export default function useWriteContractsWithLogs({ try { setBatchCallsStatus(BatchCallsStatus.Initiated); logEventWithContext(`${eventName}_transaction_initiated`, ActionType.change); - await writeContractsAsync(writeContractParameters); + + await writeContractsAsync({ + ...writeContractParameters, + capabilities, + }); logEventWithContext(`${eventName}_transaction_approved`, ActionType.change); setBatchCallsStatus(BatchCallsStatus.Approved); @@ -131,6 +170,7 @@ export default function useWriteContractsWithLogs({ eventName, writeContractsAsync, logError, + capabilities, ], ); From ae88133732826ac667505f5cf81c5533bea07a75 Mon Sep 17 00:00:00 2001 From: katzman Date: Mon, 30 Jun 2025 11:07:32 -0700 Subject: [PATCH 2/4] Unify capabilities into useCapabilities, type def for useWrite --- apps/web/src/hooks/useCapabilitiesSafe.ts | 44 ++++++++++++- .../src/hooks/useWriteContractsWithLogs.ts | 66 +++++++------------ 2 files changed, 66 insertions(+), 44 deletions(-) diff --git a/apps/web/src/hooks/useCapabilitiesSafe.ts b/apps/web/src/hooks/useCapabilitiesSafe.ts index c3142a5ebf4..d51fa599bba 100644 --- a/apps/web/src/hooks/useCapabilitiesSafe.ts +++ b/apps/web/src/hooks/useCapabilitiesSafe.ts @@ -6,12 +6,14 @@ - Use experimental useCapabilities - Check atomicBatch (batchcall) and paymasterService for a given chain - Default to false + - Provide utilities for constructing capabilities objects */ import { Chain } from 'viem'; import { base } from 'viem/chains'; import { useAccount } from 'wagmi'; import { useCapabilities } from 'wagmi/experimental'; +import { useMemo } from 'react'; // To add a new capability, add it to this list const CAPABILITIES_LIST = ['atomicBatch', 'paymasterService', 'auxiliaryFunds'] as const; @@ -22,6 +24,12 @@ export type UseCapabilitiesSafeProps = { chainId?: Chain['id']; }; +export type PaymasterCapabilities = { + paymasterService?: { + url: string; + }; +}; + export default function useCapabilitiesSafe({ chainId }: UseCapabilitiesSafeProps) { const { connector, isConnected, chainId: currentChainId } = useAccount(); @@ -45,5 +53,39 @@ export default function useCapabilitiesSafe({ chainId }: UseCapabilitiesSafeProp return acc; }, {} as Record); - return capabilities; + // Construct paymaster capabilities object with URL + const paymasterCapabilities = useMemo((): PaymasterCapabilities => { + if (!capabilities.paymasterService || !rawCapabilities || !featureChainId) return {}; + + const capabilitiesForChain = rawCapabilities[featureChainId]; + if ( + capabilitiesForChain?.['paymasterService'] && + capabilitiesForChain['paymasterService'].supported + ) { + // Use environment variable for paymaster URL + const paymasterUrl = + featureChainId === base.id + ? process.env.NEXT_PUBLIC_BASE_PAYMASTER_SERVICE + : process.env.NEXT_PUBLIC_BASE_SEPOLIA_PAYMASTER_SERVICE; + + if (paymasterUrl) { + return { + paymasterService: { + url: paymasterUrl, + }, + }; + } else { + console.warn( + `Paymaster service is supported but no URL configured for chain ${featureChainId}`, + ); + } + } + return {}; + }, [capabilities.paymasterService, rawCapabilities, featureChainId]); + + return { + ...capabilities, + rawCapabilities, + paymasterCapabilities, + }; } diff --git a/apps/web/src/hooks/useWriteContractsWithLogs.ts b/apps/web/src/hooks/useWriteContractsWithLogs.ts index 6cd6c596837..7a914754a3d 100644 --- a/apps/web/src/hooks/useWriteContractsWithLogs.ts +++ b/apps/web/src/hooks/useWriteContractsWithLogs.ts @@ -2,12 +2,11 @@ import { useAnalytics } from 'apps/web/contexts/Analytics'; import { useErrors } from 'apps/web/contexts/Errors'; import { decodeRawLog, USER_OPERATION_EVENT_LOG_NAME } from 'apps/web/src/utils/transactionLogs'; import { ActionType } from 'libs/base-ui/utils/logEvent'; -import { useCallback, useEffect, useState, useMemo } from 'react'; -import { Chain } from 'viem'; -import { base } from 'viem/chains'; +import { useCallback, useEffect, useState } from 'react'; +import { Chain, TransactionReceipt } from 'viem'; import { WriteContractsParameters } from 'viem/experimental'; import { useAccount, useSwitchChain, useWaitForTransactionReceipt } from 'wagmi'; -import { useCallsStatus, useWriteContracts, useCapabilities } from 'wagmi/experimental'; +import { useCallsStatus, useWriteContracts } from 'wagmi/experimental'; import useCapabilitiesSafe from 'apps/web/src/hooks/useCapabilitiesSafe'; /* @@ -49,51 +48,32 @@ export type UseWriteContractsWithLogsProps = { eventName: string; }; +export type UseWriteContractsWithLogsReturn = { + initiateBatchCalls: (writeContractParameters: WriteContractsParameters) => Promise; + batchCallTransactionReceiptHash: string | undefined; + batchCallsStatus: BatchCallsStatus; + transactionReceipt: TransactionReceipt | undefined; + batchCallTransactionHash: string | undefined; + batchCallsIsLoading: boolean; + batchCallsIsSuccess: boolean; + batchCallsIsError: boolean; + batchCallsError: Error | null; + batchCallsEnabled: boolean; +}; + export default function useWriteContractsWithLogs({ chain, eventName, -}: UseWriteContractsWithLogsProps) { +}: UseWriteContractsWithLogsProps): UseWriteContractsWithLogsReturn { // Errors & Analytics const { logEventWithContext } = useAnalytics(); const { logError } = useErrors(); - const { atomicBatch: atomicBatchEnabled, paymasterService: paymasterServiceEnabled } = - useCapabilitiesSafe({ chainId: chain.id }); - const { chain: connectedChain, address } = useAccount(); - - const [batchCallsStatus, setBatchCallsStatus] = useState(BatchCallsStatus.Idle); - - // Get available capabilities for paymaster URL - const { data: availableCapabilities } = useCapabilities({ - account: address, + const { atomicBatch: atomicBatchEnabled, paymasterCapabilities } = useCapabilitiesSafe({ + chainId: chain.id, }); + const { chain: connectedChain } = useAccount(); - // Construct capabilities object with paymaster URL - const capabilities = useMemo(() => { - if (!paymasterServiceEnabled || !availableCapabilities || !chain.id) return {}; - - const capabilitiesForChain = availableCapabilities[chain.id]; - if ( - capabilitiesForChain?.['paymasterService'] && - capabilitiesForChain['paymasterService'].supported - ) { - // Use environment variable for paymaster URL - const paymasterUrl = - chain.id === base.id - ? process.env.NEXT_PUBLIC_BASE_PAYMASTER_SERVICE - : process.env.NEXT_PUBLIC_BASE_SEPOLIA_PAYMASTER_SERVICE; - - if (paymasterUrl) { - return { - paymasterService: { - url: paymasterUrl, - }, - }; - } else { - console.warn(`Paymaster service is supported but no URL configured for chain ${chain.id}`); - } - } - return {}; - }, [paymasterServiceEnabled, availableCapabilities, chain.id]); + const [batchCallsStatus, setBatchCallsStatus] = useState(BatchCallsStatus.Idle); // Write the contract const { @@ -151,7 +131,7 @@ export default function useWriteContractsWithLogs({ await writeContractsAsync({ ...writeContractParameters, - capabilities, + capabilities: paymasterCapabilities, }); logEventWithContext(`${eventName}_transaction_approved`, ActionType.change); @@ -170,7 +150,7 @@ export default function useWriteContractsWithLogs({ eventName, writeContractsAsync, logError, - capabilities, + paymasterCapabilities, ], ); From 90c3eb31c06d740f90e804d6c70c97d5b3a71263 Mon Sep 17 00:00:00 2001 From: katzman Date: Mon, 30 Jun 2025 11:25:26 -0700 Subject: [PATCH 3/4] Fix type --- apps/web/src/hooks/useWriteContractsWithLogs.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/web/src/hooks/useWriteContractsWithLogs.ts b/apps/web/src/hooks/useWriteContractsWithLogs.ts index 7a914754a3d..9a0e192a2c6 100644 --- a/apps/web/src/hooks/useWriteContractsWithLogs.ts +++ b/apps/web/src/hooks/useWriteContractsWithLogs.ts @@ -49,7 +49,9 @@ export type UseWriteContractsWithLogsProps = { }; export type UseWriteContractsWithLogsReturn = { - initiateBatchCalls: (writeContractParameters: WriteContractsParameters) => Promise; + initiateBatchCalls: ( + writeContractParameters: WriteContractsParameters, + ) => Promise; batchCallTransactionReceiptHash: string | undefined; batchCallsStatus: BatchCallsStatus; transactionReceipt: TransactionReceipt | undefined; From 9ba378ee352196bd2c6996f3f53cd6a249ebd172 Mon Sep 17 00:00:00 2001 From: katzman Date: Mon, 30 Jun 2025 15:16:01 -0700 Subject: [PATCH 4/4] fix linter error --- apps/web/src/hooks/useCapabilitiesSafe.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/web/src/hooks/useCapabilitiesSafe.ts b/apps/web/src/hooks/useCapabilitiesSafe.ts index d51fa599bba..8d23d901a90 100644 --- a/apps/web/src/hooks/useCapabilitiesSafe.ts +++ b/apps/web/src/hooks/useCapabilitiesSafe.ts @@ -58,11 +58,7 @@ export default function useCapabilitiesSafe({ chainId }: UseCapabilitiesSafeProp if (!capabilities.paymasterService || !rawCapabilities || !featureChainId) return {}; const capabilitiesForChain = rawCapabilities[featureChainId]; - if ( - capabilitiesForChain?.['paymasterService'] && - capabilitiesForChain['paymasterService'].supported - ) { - // Use environment variable for paymaster URL + if (capabilitiesForChain?.paymasterService?.supported) { const paymasterUrl = featureChainId === base.id ? process.env.NEXT_PUBLIC_BASE_PAYMASTER_SERVICE