From 4380bdfe170ffbe1a4c5898128faa4e710b15663 Mon Sep 17 00:00:00 2001 From: Joseph Bergeron Date: Thu, 16 Oct 2025 14:29:59 -0400 Subject: [PATCH 01/20] Progress --- .../mobile/locales/base/translation.json | 10 ++ .../@divvi/mobile/src/navigator/types.tsx | 166 +++++++++--------- .../@divvi/mobile/src/statsig/constants.ts | 2 +- .../mobile/src/walletConnect/request.test.ts | 4 +- .../@divvi/mobile/src/walletConnect/saga.ts | 30 +++- .../walletConnect/screens/RequestContent.tsx | 39 +++- .../screens/SmartAccountConversionRequest.tsx | 130 ++++++++++++++ .../screens/WalletConnectRequest.tsx | 7 +- .../@divvi/mobile/src/walletConnect/types.ts | 10 ++ 9 files changed, 299 insertions(+), 99 deletions(-) create mode 100644 packages/@divvi/mobile/src/walletConnect/screens/SmartAccountConversionRequest.tsx diff --git a/packages/@divvi/mobile/locales/base/translation.json b/packages/@divvi/mobile/locales/base/translation.json index 5efa08ca34..9a0af95051 100644 --- a/packages/@divvi/mobile/locales/base/translation.json +++ b/packages/@divvi/mobile/locales/base/translation.json @@ -1220,6 +1220,16 @@ "title": "Unsupported events", "description": "{{dappName}} specified some events that are not supported by {{appName}}. Some features may not work as expected." } + }, + "smartAccountConversion": { + "title": "Convert to Smart Account", + "convertButton": "Convert Account", + "continueWithoutConverting": "Continue Without Converting", + "descriptionRequired": "{{dappName}} requires smart wallet capabilities for this transaction. Converting your account will enable advanced features like atomic transactions.", + "descriptionOptional": "{{dappName}} can use smart wallet capabilities for this transaction. Converting your account will enable advanced features like atomic transactions.", + "notificationTitle": "Smart Account Benefits", + "notificationDescriptionRequired": "This transaction requires smart wallet capabilities. If you deny conversion, the transaction will be cancelled.", + "notificationDescriptionOptional": "Smart accounts enable advanced features like atomic transactions and paymaster services. You can still proceed without conversion." } }, "sessionInfo": "This application may ask to perform the following actions:", diff --git a/packages/@divvi/mobile/src/navigator/types.tsx b/packages/@divvi/mobile/src/navigator/types.tsx index ed21f001bf..d2fa888a6d 100644 --- a/packages/@divvi/mobile/src/navigator/types.tsx +++ b/packages/@divvi/mobile/src/navigator/types.tsx @@ -20,13 +20,14 @@ import { Currency } from 'src/utils/currencies' import { type SerializablePreparedTransactionsPossible } from 'src/viem/preparedTransactionSerialization' import { ActionRequestProps } from 'src/walletConnect/screens/ActionRequest' import { SessionRequestProps } from 'src/walletConnect/screens/SessionRequest' +import { SmartAccountConversionRequestProps } from 'src/walletConnect/screens/SmartAccountConversionRequest' import { WalletConnectRequestType } from 'src/walletConnect/types' // Typed nested navigator params type NestedNavigatorParams = { [K in keyof ParamList]: undefined extends ParamList[K] - ? { screen: K; params?: ParamList[K] } - : { screen: K; params: ParamList[K] } + ? { screen: K; params?: ParamList[K] } + : { screen: K; params: ParamList[K] } }[keyof ParamList] interface SendConfirmationParams { @@ -56,11 +57,11 @@ export type StackParamList = { [Screens.BackupComplete]: { isAccountRemoval?: boolean } | undefined [Screens.BackupIntroduction]: {} | undefined [Screens.AccountKeyEducation]: - | undefined - | { - nextScreen?: keyof StackParamList - origin?: 'cabOnboarding' - } + | undefined + | { + nextScreen?: keyof StackParamList + origin?: 'cabOnboarding' + } [Screens.AccounSetupFailureScreen]: undefined [Screens.BackupPhrase]: { isAccountRemoval?: boolean } | undefined [Screens.BackupQuiz]: { isAccountRemoval?: boolean } | undefined @@ -92,16 +93,16 @@ export type StackParamList = { swapTransaction?: SwapTransaction } [Screens.EarnWithdrawConfirmationScreen]: - | { - mode: Extract - pool: EarnPosition - } - | { - mode: Extract - pool: EarnPosition - inputTokenAmount: string - useMax: boolean - } + | { + mode: Extract + pool: EarnPosition + } + | { + mode: Extract + pool: EarnPosition + inputTokenAmount: string + useMax: boolean + } [Screens.EarnHome]: { activeEarnTab?: EarnTabType } | undefined [Screens.TabEarn]: { activeEarnTab?: EarnTabType } | undefined @@ -189,22 +190,22 @@ export type StackParamList = { [Screens.GoldEducation]: undefined [Screens.ImportSelect]: undefined [Screens.ImportWallet]: - | { - clean: boolean - showZeroBalanceModal?: boolean - } - | undefined + | { + clean: boolean + showZeroBalanceModal?: boolean + } + | undefined [Screens.EnableBiometry]: undefined [Screens.Language]: - | { - nextScreen: keyof StackParamList - } - | undefined + | { + nextScreen: keyof StackParamList + } + | undefined [Screens.LanguageModal]: - | { - nextScreen: keyof StackParamList - } - | undefined + | { + nextScreen: keyof StackParamList + } + | undefined [Screens.Licenses]: undefined [Screens.LinkPhoneNumber]: undefined [Screens.JumpstartTransactionDetailsScreen]: { @@ -221,22 +222,22 @@ export type StackParamList = { account?: string } [Screens.PincodeSet]: - | { - changePin?: boolean - choseToRestoreAccount?: boolean - registrationStep?: { step: number; totalSteps: number } - showGuidedOnboarding?: boolean - } - | undefined + | { + changePin?: boolean + choseToRestoreAccount?: boolean + registrationStep?: { step: number; totalSteps: number } + showGuidedOnboarding?: boolean + } + | undefined [Screens.PointsHome]: undefined [Screens.PointsIntro]: undefined [Screens.PrivateKey]: undefined [Screens.ProtectWallet]: undefined [Screens.OnboardingRecoveryPhrase]: - | { - origin?: 'cabOnboarding' - } - | undefined + | { + origin?: 'cabOnboarding' + } + | undefined [Screens.Profile]: undefined [Screens.ProfileSubmenu]: undefined [Screens.LegalSubmenu]: undefined @@ -261,11 +262,11 @@ export type StackParamList = { } } [Screens.SendSelectRecipient]: - | { - forceTokenId?: boolean - defaultTokenIdOverride?: string - } - | undefined + | { + forceTokenId?: boolean + defaultTokenIdOverride?: string + } + | undefined [Screens.SendConfirmation]: SendConfirmationParams [Screens.SendEnterAmount]: SendEnterAmountParams [Screens.SignInWithEmail]: { @@ -276,23 +277,23 @@ export type StackParamList = { [Screens.StoreWipeRecoveryScreen]: undefined [Screens.Support]: undefined [Screens.SupportContact]: - | { - prefilledText: string - } - | undefined + | { + prefilledText: string + } + | undefined [Screens.SwapScreenWithBack]: - | { - fromTokenId?: string - toTokenId?: string - toTokenNetworkId?: NetworkId - } - | undefined + | { + fromTokenId?: string + toTokenId?: string + toTokenNetworkId?: NetworkId + } + | undefined [Screens.TabDiscover]: {} | undefined [Screens.TabHome]: {} | undefined [Screens.TabWallet]: { activeAssetTab?: AssetTabType } | undefined [Screens.TabNavigator]: - | { initialScreen?: Screens.TabHome | Screens.TabWallet | Screens.TabDiscover | string } - | undefined + | { initialScreen?: Screens.TabHome | Screens.TabWallet | Screens.TabDiscover | string } + | undefined [Screens.TokenDetails]: { tokenId: string } [Screens.TokenImport]: undefined [Screens.TransactionDetailsScreen]: { @@ -302,11 +303,11 @@ export type StackParamList = { [Screens.ValidateRecipientIntro]: ValidateRecipientParams [Screens.ValidateRecipientAccount]: ValidateRecipientParams [Screens.VerificationStartScreen]: - | { - hasOnboarded?: boolean - selectedCountryCodeAlpha2?: string - } - | undefined + | { + hasOnboarded?: boolean + selectedCountryCodeAlpha2?: string + } + | undefined [Screens.VerificationCodeInputScreen]: { registrationStep?: { step: number; totalSteps: number } e164Number: string @@ -315,14 +316,17 @@ export type StackParamList = { } [Screens.OnboardingSuccessScreen]: undefined [Screens.WalletConnectRequest]: - | { type: WalletConnectRequestType.Loading; origin: WalletConnectPairingOrigin } - | ({ - type: WalletConnectRequestType.Action - } & ActionRequestProps) - | ({ - type: WalletConnectRequestType.Session - } & SessionRequestProps) - | { type: WalletConnectRequestType.TimeOut } + | { type: WalletConnectRequestType.Loading; origin: WalletConnectPairingOrigin } + | ({ + type: WalletConnectRequestType.Action + } & ActionRequestProps) + | ({ + type: WalletConnectRequestType.Session + } & SessionRequestProps) + | ({ + type: WalletConnectRequestType.SmartAccountConversion + } & SmartAccountConversionRequestProps) + | { type: WalletConnectRequestType.TimeOut } [Screens.WalletConnectSessions]: undefined [Screens.WalletSecurityPrimer]: undefined [Screens.WebViewScreen]: { uri: string } @@ -332,15 +336,15 @@ export type StackParamList = { export type QRTabParamList = { [Screens.QRCode]: - | { - showSecureSendStyling?: true - } - | undefined + | { + showSecureSendStyling?: true + } + | undefined [Screens.QRScanner]: - | { - showSecureSendStyling?: true - onQRCodeDetected?: (qrCode: QrCode) => void - defaultTokenIdOverride?: string - } - | undefined + | { + showSecureSendStyling?: true + onQRCodeDetected?: (qrCode: QrCode) => void + defaultTokenIdOverride?: string + } + | undefined } diff --git a/packages/@divvi/mobile/src/statsig/constants.ts b/packages/@divvi/mobile/src/statsig/constants.ts index 0b0eea0018..f7e6639351 100644 --- a/packages/@divvi/mobile/src/statsig/constants.ts +++ b/packages/@divvi/mobile/src/statsig/constants.ts @@ -35,7 +35,7 @@ export const FeatureGates = { [StatsigFeatureGates.DISABLE_WALLET_CONNECT_V2]: false, [StatsigFeatureGates.SHOW_DIVVI_SLICES_BOTTOM_SHEET]: false, [StatsigFeatureGates.RECAPTCHA_ENABLED]: false, - [StatsigFeatureGates.USE_SMART_ACCOUNT_CAPABILITIES]: false, + [StatsigFeatureGates.USE_SMART_ACCOUNT_CAPABILITIES]: true, } satisfies { [key in StatsigFeatureGates]: boolean } export const ExperimentConfigs = { diff --git a/packages/@divvi/mobile/src/walletConnect/request.test.ts b/packages/@divvi/mobile/src/walletConnect/request.test.ts index 7ed0b1e88a..dd125efe60 100644 --- a/packages/@divvi/mobile/src/walletConnect/request.test.ts +++ b/packages/@divvi/mobile/src/walletConnect/request.test.ts @@ -59,8 +59,8 @@ const createMockActionableRequest = ({ params: any[] chainId: string preparedRequest?: - | PreparedTransactionResult - | PreparedTransactionResult + | PreparedTransactionResult + | PreparedTransactionResult }) => ({ method, diff --git a/packages/@divvi/mobile/src/walletConnect/saga.ts b/packages/@divvi/mobile/src/walletConnect/saga.ts index 11ea8923ba..2290f00afb 100644 --- a/packages/@divvi/mobile/src/walletConnect/saga.ts +++ b/packages/@divvi/mobile/src/walletConnect/saga.ts @@ -393,7 +393,7 @@ function convertToBigInt(value: any) { return isHex(value) ? hexToBigInt(value) : // make sure that we can safely parse the value as a BigInt - typeof value === 'string' || typeof value === 'number' || typeof value === 'bigint' + typeof value === 'string' || typeof value === 'number' || typeof value === 'bigint' ? BigInt(value) : undefined } @@ -508,8 +508,8 @@ function* prepareNormalizedTransactions( hasInsufficientGasFunds: boolean feeCurrenciesSymbols: string[] result: - | PreparedTransactionResult - | PreparedTransactionResult + | PreparedTransactionResult + | PreparedTransactionResult }> { const networkId = walletConnectChainIdToNetworkId[walletConnectChainId] const network = walletConnectChainIdToNetwork[walletConnectChainId] @@ -686,8 +686,28 @@ function* showActionRequest(request: WalletKitTypes.EventArguments['session_requ } if (atomic === 'ready') { - // TODO: suggest user to enable atomic operations - // NOTE: deny if atomicRequired is true, and user didn't enable atomic operations + // Show smart account conversion prompt + const rawTxs: unknown[] = request.params.request.params[0].calls + const { + hasInsufficientGasFunds, + feeCurrenciesSymbols, + result: preparedRequest, + } = yield* prepareNormalizedTransactions(rawTxs, request.params.chainId) + + const supportedChains = yield* call(getSupportedChains) + + navigate(Screens.WalletConnectRequest, { + type: WalletConnectRequestType.SmartAccountConversion, + method, + request, + supportedChains, + version: 2, + atomicRequired: request.params.request.params[0].atomicRequired, + hasInsufficientGasFunds, + feeCurrenciesSymbols, + preparedRequest, + }) + return } } diff --git a/packages/@divvi/mobile/src/walletConnect/screens/RequestContent.tsx b/packages/@divvi/mobile/src/walletConnect/screens/RequestContent.tsx index 27b1fcb816..999c878839 100644 --- a/packages/@divvi/mobile/src/walletConnect/screens/RequestContent.tsx +++ b/packages/@divvi/mobile/src/walletConnect/screens/RequestContent.tsx @@ -25,6 +25,7 @@ interface BaseProps { testId: string children?: React.ReactNode buttonText?: string | null + secondaryButtonText?: string | null buttonLoading?: boolean } @@ -91,6 +92,7 @@ function RequestContent(props: Props) { testId, children, buttonText, + secondaryButtonText, buttonLoading, } = props const { t } = useTranslation() @@ -174,15 +176,31 @@ function RequestContent(props: Props) { {children} {type == 'confirm' && ( -