wallet-stack 1.0.0-alpha.122 → 1.0.0-alpha.124

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +0 -7
  2. package/package.json +1 -2
  3. package/plugin/build/withAndroidUserAgent.js +2 -2
  4. package/plugin/build/withIosAppDelegateResetKeychain.js +2 -2
  5. package/plugin/build/withIosUserAgent.js +2 -2
  6. package/src/analytics/AppAnalytics.test.ts +7 -1
  7. package/src/analytics/AppAnalytics.ts +20 -13
  8. package/src/analytics/Events.tsx +0 -2
  9. package/src/analytics/Properties.tsx +0 -2
  10. package/src/analytics/docs.ts +2 -2
  11. package/src/config.ts +1 -1
  12. package/src/home/TabHome.tsx +0 -5
  13. package/src/home/actions.ts +0 -10
  14. package/src/home/reducers.ts +0 -7
  15. package/src/home/selectors.ts +0 -9
  16. package/src/images/Images.ts +0 -1
  17. package/src/images/WelcomeLogo.tsx +10 -9
  18. package/src/navigator/SettingsMenu.tsx +0 -9
  19. package/src/onboarding/welcome/Welcome.tsx +1 -10
  20. package/src/public/createApp.ts +1 -1
  21. package/src/public/navigate.ts +3 -3
  22. package/src/public/types.tsx +0 -5
  23. package/src/redux/migrations.test.ts +15 -0
  24. package/src/redux/migrations.ts +4 -0
  25. package/src/redux/store.test.ts +1 -2
  26. package/src/redux/store.ts +1 -1
  27. package/src/statsig/constants.ts +0 -5
  28. package/src/statsig/types.ts +0 -2
  29. package/src/viem/prepareTransactions.test.ts +1 -105
  30. package/src/viem/prepareTransactions.ts +1 -20
  31. package/src/viem/saga.test.ts +0 -13
  32. package/src/viem/saga.ts +0 -7
  33. package/tsconfig.base.json +1 -1
  34. package/src/divviProtocol/saga.test.ts +0 -51
  35. package/src/divviProtocol/saga.ts +0 -44
  36. package/src/home/DivviBottomSheet.test.tsx +0 -59
  37. package/src/home/DivviBottomSheet.tsx +0 -197
  38. package/src/images/DivviLogo.tsx +0 -22
  39. package/src/images/assets/pie.png +0 -0
  40. package/src/images/assets/pie@1.5x.png +0 -0
  41. package/src/images/assets/pie@2x.png +0 -0
  42. package/src/images/assets/pie@3x.png +0 -0
  43. package/src/images/assets/pie@4x.png +0 -0
package/README.md CHANGED
@@ -22,7 +22,6 @@ Built on top of [Expo](https://expo.dev), the industry standard for React Native
22
22
  - Optimism Mainnet
23
23
  - Polygon PoS
24
24
  - Base
25
- - 🔌 **Protocol Integration**: Native support for the Divvi protocol
26
25
  - 📱 **App Store Ready**: Streamlined deployment process using Expo EAS
27
26
 
28
27
  ## Quick Start
@@ -75,12 +74,6 @@ We welcome contributions! Please read our [Contributing Guidelines](CONTRIBUTING
75
74
 
76
75
  Found a security issue? Please report it following our [Security Policy](SECURITY.md).
77
76
 
78
- ## Community
79
-
80
- - 💬 Join our [Discord](https://discord.com/invite/EaxZDhMuDn)
81
- - 🐦 Follow us on [X](https://x.com/letsdivvi)
82
- - 📝 Read our [Blog](https://blog.divvi.xyz)
83
-
84
77
  ## License
85
78
 
86
79
  This project is licensed under the [Apache License 2.0](LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wallet-stack",
3
- "version": "1.0.0-alpha.122",
3
+ "version": "1.0.0-alpha.124",
4
4
  "author": "Valora Inc",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -122,7 +122,6 @@
122
122
  "dependencies": {
123
123
  "@badrap/result": "~0.2.13",
124
124
  "@crowdin/ota-client": "^2.0.1",
125
- "@divvi/referral-sdk": "^2.2.0",
126
125
  "@fiatconnect/fiatconnect-sdk": "^0.5.74",
127
126
  "@fiatconnect/fiatconnect-types": "^13.3.11",
128
127
  "@gorhom/bottom-sheet": "^5.1.1",
@@ -68,7 +68,7 @@ function addUserAgentCode(src, appName) {
68
68
  const withAndroidUserAgent = (config, { appName }) => {
69
69
  return (0, config_plugins_1.withMainApplication)(config, (config) => {
70
70
  if (!['kt'].includes(config.modResults.language)) {
71
- throw new Error(`Cannot setup Divvi mobile because the project MainApplication is not a supported language: ${config.modResults.language}`);
71
+ throw new Error(`Cannot setup Wallet Stack because the project MainApplication is not a supported language: ${config.modResults.language}`);
72
72
  }
73
73
  try {
74
74
  config.modResults.contents = addNeededImports(config.modResults.contents).contents;
@@ -76,7 +76,7 @@ const withAndroidUserAgent = (config, { appName }) => {
76
76
  }
77
77
  catch (error) {
78
78
  if (error.code === 'ERR_NO_MATCH') {
79
- throw new Error(`Cannot add Divvi mobile to the project's MainApplication because it's malformed. Please report this with a copy of your project MainApplication.`);
79
+ throw new Error(`Cannot add Wallet Stack to the project's MainApplication because it's malformed. Please report this with a copy of your project MainApplication.`);
80
80
  }
81
81
  throw error;
82
82
  }
@@ -59,7 +59,7 @@ function addCallResetKeychain(src) {
59
59
  const withIosAppDelegateResetKeychain = (config) => {
60
60
  return (0, config_plugins_1.withAppDelegate)(config, (config) => {
61
61
  if (config.modResults.language !== 'swift') {
62
- throw new Error(`Cannot setup Divvi mobile because the project AppDelegate is not Swift: ${config.modResults.language}`);
62
+ throw new Error(`Cannot setup Wallet Stack because the project AppDelegate is not Swift: ${config.modResults.language}`);
63
63
  }
64
64
  try {
65
65
  config.modResults.contents = addResetKeychainFunction(config.modResults.contents).contents;
@@ -67,7 +67,7 @@ const withIosAppDelegateResetKeychain = (config) => {
67
67
  }
68
68
  catch (error) {
69
69
  if (error.code === 'ERR_NO_MATCH') {
70
- throw new Error(`Cannot add Divvi mobile to the project's AppDelegate because it's malformed. Please report this with a copy of your project AppDelegate.`);
70
+ throw new Error(`Cannot add Wallet Stack to the project's AppDelegate because it's malformed. Please report this with a copy of your project AppDelegate.`);
71
71
  }
72
72
  throw error;
73
73
  }
@@ -39,14 +39,14 @@ function addUserAgentCode(src, appName) {
39
39
  const withIosUserAgent = (config, { appName }) => {
40
40
  return (0, config_plugins_1.withAppDelegate)(config, (config) => {
41
41
  if (config.modResults.language !== 'swift') {
42
- throw new Error(`Cannot setup Divvi mobile because the project AppDelegate is not Swift: ${config.modResults.language}`);
42
+ throw new Error(`Cannot setup Wallet Stack because the project AppDelegate is not Swift: ${config.modResults.language}`);
43
43
  }
44
44
  try {
45
45
  config.modResults.contents = addUserAgentCode(config.modResults.contents, appName ?? config.name).contents;
46
46
  }
47
47
  catch (error) {
48
48
  if (error.code === 'ERR_NO_MATCH') {
49
- throw new Error(`Cannot add Divvi mobile to the project's AppDelegate because it's malformed. Please report this with a copy of your project AppDelegate.`);
49
+ throw new Error(`Cannot add Wallet Stack to the project's AppDelegate because it's malformed. Please report this with a copy of your project AppDelegate.`);
50
50
  }
51
51
  throw error;
52
52
  }
@@ -222,11 +222,17 @@ describe('AppAnalytics', () => {
222
222
  expect(mockCreateSegmentClient).not.toHaveBeenCalled()
223
223
  })
224
224
 
225
- it('creates statsig client on initialization with default statsig user', async () => {
225
+ it('creates statsig client on initialization with Segment anonymous ID when Segment is available', async () => {
226
226
  await AppAnalytics.init()
227
227
  expect(StatsigClientSingleton.initialize).toHaveBeenCalledWith('anonId')
228
228
  })
229
229
 
230
+ it('creates statsig client with device UniqueID when Segment is not available', async () => {
231
+ mockConfig.SEGMENT_API_KEY = undefined
232
+ await AppAnalytics.init()
233
+ expect(StatsigClientSingleton.initialize).toHaveBeenCalledWith('abc-def-123')
234
+ })
235
+
230
236
  it('does not initialize statsig if STATSIG_ENABLED is false', async () => {
231
237
  mockConfig.STATSIG_ENABLED = false
232
238
  await AppAnalytics.init()
@@ -97,6 +97,15 @@ class AppAnalytics {
97
97
 
98
98
  async init() {
99
99
  let uniqueID
100
+ try {
101
+ const deviceInfo = await getDeviceInfo()
102
+ this.deviceInfo = deviceInfo
103
+ uniqueID = deviceInfo.UniqueID
104
+ this.sessionId = sha256(Buffer.from(uniqueID + String(Date.now()))).slice(2)
105
+ } catch (error) {
106
+ Logger.error(TAG, 'getDeviceInfo error', error)
107
+ }
108
+
100
109
  if (SEGMENT_API_KEY) {
101
110
  try {
102
111
  this.segmentClient = createClient({
@@ -114,15 +123,6 @@ class AppAnalytics {
114
123
  this.segmentClient.add({ plugin: new FirebasePlugin() })
115
124
  }
116
125
 
117
- try {
118
- const deviceInfo = await getDeviceInfo()
119
- this.deviceInfo = deviceInfo
120
- uniqueID = deviceInfo.UniqueID
121
- this.sessionId = sha256(Buffer.from(uniqueID + String(Date.now()))).slice(2)
122
- } catch (error) {
123
- Logger.error(TAG, 'getDeviceInfo error', error)
124
- }
125
-
126
126
  Logger.info(TAG, 'Segment Analytics Integration initialized!')
127
127
  } catch (err) {
128
128
  const error = ensureError(err)
@@ -134,11 +134,18 @@ class AppAnalytics {
134
134
 
135
135
  if (STATSIG_ENABLED) {
136
136
  try {
137
- if (!this.segmentClient) {
138
- throw new Error('segmentClient is undefined, cannot get anonymous ID')
137
+ let overrideStableID: string
138
+ if (this.segmentClient) {
139
+ overrideStableID = this.segmentClient.userInfo.get().anonymousId
140
+ Logger.debug(TAG, 'Statsig stable ID from Segment', overrideStableID)
141
+ } else if (uniqueID) {
142
+ overrideStableID = uniqueID
143
+ Logger.debug(TAG, 'Statsig stable ID from device UniqueID', overrideStableID)
144
+ } else {
145
+ throw new Error(
146
+ 'Cannot get stable ID: segmentClient is undefined and device UniqueID is unavailable'
147
+ )
139
148
  }
140
- const overrideStableID = this.segmentClient.userInfo.get().anonymousId
141
- Logger.debug(TAG, 'Statsig stable ID', overrideStableID)
142
149
  await StatsigClientSingleton.initialize(overrideStableID)
143
150
  } catch (error) {
144
151
  Logger.warn(TAG, `Statsig setup error`, error)
@@ -47,8 +47,6 @@ export enum HomeEvents {
47
47
  nft_celebration_animation_displayed = 'nft_celebration_animation_displayed',
48
48
  nft_reward_accept = 'nft_reward_accept',
49
49
  nft_reward_dismiss = 'nft_reward_dismiss',
50
- divvi_bottom_sheet_displayed = 'divvi_bottom_sheet_displayed',
51
- divvi_bottom_sheet_cta_pressed = 'divvi_bottom_sheet_cta_pressed',
52
50
  }
53
51
 
54
52
  export enum SettingsEvents {
@@ -197,8 +197,6 @@ interface HomeEventsProperties {
197
197
  contractAddress: string
198
198
  remainingDays: number
199
199
  }
200
- [HomeEvents.divvi_bottom_sheet_displayed]: undefined
201
- [HomeEvents.divvi_bottom_sheet_cta_pressed]: undefined
202
200
  }
203
201
 
204
202
  interface SettingsEventsProperties {
@@ -84,8 +84,6 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
84
84
  [HomeEvents.nft_celebration_animation_displayed]: `When user has seen an NFT celebration confetti animation`,
85
85
  [HomeEvents.nft_reward_accept]: `When user press "Use reward" button when presented with an NFT reward`,
86
86
  [HomeEvents.nft_reward_dismiss]: `When user dismiss the bottom sheet when presented with an NFT reward`,
87
- [HomeEvents.divvi_bottom_sheet_displayed]: `When the Divvi bottom sheet is displayed to the user`,
88
- [HomeEvents.divvi_bottom_sheet_cta_pressed]: `When the user presses the CTA button in the Divvi bottom sheet.`,
89
87
  [SettingsEvents.settings_profile_edit]: ``,
90
88
  [SettingsEvents.profile_generate_name]: ``,
91
89
  [SettingsEvents.profile_save]: ``,
@@ -672,4 +670,6 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
672
670
  // [JumpstartEvents.jumpstart_add_assets_action_press]: 'When user selects an add assets action from the available options',
673
671
  // [JumpstartEvents.jumpstart_intro_seen]: `when jumpstart intro is seen by the user`,
674
672
  // [OnboardingEvents.stale_keychain_items_cleared]: `Stale keychain items where found during onboarding and cleared`,
673
+ // [HomeEvents.divvi_bottom_sheet_displayed]: `When the Divvi bottom sheet is displayed to the user`,
674
+ // [HomeEvents.divvi_bottom_sheet_cta_pressed]: `When the user presses the CTA button in the Divvi bottom sheet.`,
675
675
  }
package/src/config.ts CHANGED
@@ -32,7 +32,7 @@ export const APP_NAME = appConfig.displayName
32
32
  export const APP_REGISTRY_NAME = configOrThrow('APP_REGISTRY_NAME')
33
33
 
34
34
  // DEV only related settings
35
- export const isE2EEnv = stringToBoolean(process.env.EXPO_PUBLIC_DIVVI_E2E || 'false')
35
+ export const isE2EEnv = stringToBoolean(process.env.EXPO_PUBLIC_WALLET_STACK_E2E || 'false')
36
36
  export const DEV_RESTORE_NAV_STATE_ON_RELOAD = stringToBoolean(
37
37
  Config.DEV_RESTORE_NAV_STATE_ON_RELOAD || 'false'
38
38
  )
@@ -22,7 +22,6 @@ import NftCelebration from 'src/home/celebration/NftCelebration'
22
22
  import NftReward from 'src/home/celebration/NftReward'
23
23
  import {
24
24
  balancesLoadingSelector,
25
- showDivviBottomSheetSelector,
26
25
  showNftCelebrationSelector,
27
26
  showNftRewardSelector,
28
27
  } from 'src/home/selectors'
@@ -38,7 +37,6 @@ import colors from 'src/styles/colors'
38
37
  import TransactionFeed from 'src/transactions/feed/TransactionFeed'
39
38
  import TransactionFeedV2 from 'src/transactions/feed/TransactionFeedV2'
40
39
  import { hasGrantedContactsPermission } from 'src/utils/contacts'
41
- import DivviBottomSheet from './DivviBottomSheet'
42
40
 
43
41
  const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
44
42
 
@@ -62,8 +60,6 @@ function TabHome(_props: Props) {
62
60
  const showNftCelebration = canShowNftCelebration && isFocused && !showNotificationSpotlight
63
61
  const canShowNftReward = useSelector(showNftRewardSelector)
64
62
  const showNftReward = canShowNftReward && isFocused && !showNotificationSpotlight
65
- const canShowDivviBottomSheet = useSelector(showDivviBottomSheetSelector)
66
- const showDivviBottomSheet = canShowDivviBottomSheet && isFocused && !showNotificationSpotlight
67
63
  const showZerionTransactionFeed = getFeatureGate(StatsigFeatureGates.SHOW_ZERION_TRANSACTION_FEED)
68
64
  const hideActionsCarousel = getAppConfig().experimental?.activity?.hideActionsCarousel ?? false
69
65
 
@@ -166,7 +162,6 @@ function TabHome(_props: Props) {
166
162
  )}
167
163
  {showNftCelebration && <NftCelebration />}
168
164
  {showNftReward && <NftReward />}
169
- {showDivviBottomSheet && <DivviBottomSheet />}
170
165
  </SafeAreaView>
171
166
  )
172
167
  }
@@ -11,7 +11,6 @@ export enum Actions {
11
11
  NFT_CELEBRATION_DISPLAYED = 'HOME/NFT_CELEBRATION_DISPLAYED',
12
12
  NFT_REWARD_READY_TO_DISPLAY = 'HOME/NFT_REWARD_READY_TO_DISPLAY',
13
13
  NFT_REWARD_DISPLAYED = 'HOME/NFT_REWARD_DISPLAYED',
14
- DIVVI_BOTTOM_SHEET_SEEN = 'HOME/DIVVI_BOTTOM_SHEET_SEEN',
15
14
  }
16
15
 
17
16
  export interface VisitHomeAction {
@@ -64,10 +63,6 @@ interface NftRewardDisplayedAction {
64
63
  type: Actions.NFT_REWARD_DISPLAYED
65
64
  }
66
65
 
67
- interface DivviBottomSheetSeenAction {
68
- type: Actions.DIVVI_BOTTOM_SHEET_SEEN
69
- }
70
-
71
66
  export type ActionTypes =
72
67
  | SetLoadingAction
73
68
  | UpdateNotificationsAction
@@ -77,7 +72,6 @@ export type ActionTypes =
77
72
  | NftCelebrationDisplayedAction
78
73
  | NftRewardReadyToDisplayAction
79
74
  | NftRewardDisplayedAction
80
- | DivviBottomSheetSeenAction
81
75
 
82
76
  export const visitHome = (): VisitHomeAction => ({
83
77
  type: Actions.VISIT_HOME,
@@ -148,7 +142,3 @@ export const nftRewardReadyToDisplay = ({
148
142
  export const nftRewardDisplayed = (): NftRewardDisplayedAction => ({
149
143
  type: Actions.NFT_REWARD_DISPLAYED,
150
144
  })
151
-
152
- export const divviBottomSheetSeen = (): DivviBottomSheetSeenAction => ({
153
- type: Actions.DIVVI_BOTTOM_SHEET_SEEN,
154
- })
@@ -42,7 +42,6 @@ export interface State {
42
42
  loading: boolean
43
43
  notifications: IdToNotification
44
44
  hasVisitedHome: boolean
45
- hasSeenDivviBottomSheet: boolean
46
45
  nftCelebration: {
47
46
  networkId: NetworkId
48
47
  contractAddress: string
@@ -57,7 +56,6 @@ export const initialState = {
57
56
  loading: false,
58
57
  notifications: {},
59
58
  hasVisitedHome: false,
60
- hasSeenDivviBottomSheet: false,
61
59
  nftCelebration: null,
62
60
  }
63
61
 
@@ -180,11 +178,6 @@ export const homeReducer = (
180
178
  : NftCelebrationStatus.rewardDisplayed,
181
179
  },
182
180
  }
183
- case Actions.DIVVI_BOTTOM_SHEET_SEEN:
184
- return {
185
- ...state,
186
- hasSeenDivviBottomSheet: true,
187
- }
188
181
  default:
189
182
  return state
190
183
  }
@@ -71,12 +71,3 @@ export const showNftRewardSelector = (state: RootState) => {
71
71
  state.home.nftCelebration.status === NftCelebrationStatus.reminderReadyToDisplay
72
72
  )
73
73
  }
74
-
75
- export const showDivviBottomSheetSelector = (state: RootState) => {
76
- const featureGateEnabled = getFeatureGate(StatsigFeatureGates.SHOW_DIVVI_SLICES_BOTTOM_SHEET)
77
- if (!featureGateEnabled) {
78
- return false
79
- }
80
-
81
- return !state.home.hasSeenDivviBottomSheet
82
- }
@@ -19,4 +19,3 @@ export const pointsCardBackground = require('src/images/assets/points-card-backg
19
19
  export const pointsIllustration = require('src/images/assets/points-illustration.png')
20
20
  export const walletSafe = require('src/images/assets/wallet-safe.png')
21
21
  export const earnCardBackground = require('src/images/assets/earn-card-background.png')
22
- export const divviPie = require('src/images/assets/pie.png')
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react'
2
- import Svg, { Path } from 'react-native-svg'
2
+ import { StyleSheet, Text } from 'react-native'
3
3
  import { getAppConfig } from 'src/appConfig'
4
+ import { typeScale } from 'src/styles/fonts'
4
5
 
5
6
  export default function WelcomeLogo() {
6
7
  const CustomWelcomeLogo = getAppConfig().themes?.default?.assets?.welcomeLogo
@@ -8,12 +9,12 @@ export default function WelcomeLogo() {
8
9
  return <CustomWelcomeLogo />
9
10
  }
10
11
 
11
- return (
12
- <Svg width={168} height={58} viewBox={`0 0 168 58`} fill="none">
13
- <Path
14
- fill="#000"
15
- d="m30.447 1.167 8.835 3.723V57h-7.331l-1.805-3.685a11.426 11.426 0 0 1-2.143 1.993c-.777.526-1.617.965-2.52 1.316-.876.351-1.816.602-2.819.752-1.003.15-2.055.226-3.158.226-2.757 0-5.301-.502-7.633-1.504-2.33-1.003-4.348-2.382-6.053-4.136-1.68-1.78-3.008-3.86-3.985-6.241C.882 43.34.406 40.783.406 38.051c0-2.757.476-5.327 1.429-7.708.978-2.381 2.306-4.449 3.985-6.203 1.68-1.755 3.685-3.133 6.016-4.136 2.356-1.003 4.913-1.504 7.67-1.504 1.078 0 2.118.088 3.12.263 1.003.151 1.968.389 2.895.715.928.325 1.805.751 2.632 1.278a11.78 11.78 0 0 1 2.294 1.917V1.167Zm.075 36.846c0-1.754-.263-3.321-.79-4.7-.5-1.378-1.228-2.544-2.18-3.496-.928-.953-2.043-1.679-3.346-2.181-1.304-.501-2.758-.752-4.362-.752-1.579 0-3.033.289-4.361.865a9.572 9.572 0 0 0-3.346 2.331c-.928 1.003-1.655 2.181-2.18 3.534-.527 1.354-.79 2.82-.79 4.399 0 1.604.263 3.096.79 4.474.525 1.379 1.252 2.569 2.18 3.572.927.978 2.043 1.755 3.346 2.331 1.328.552 2.782.827 4.361.827 1.604 0 3.058-.238 4.362-.714 1.303-.501 2.418-1.228 3.346-2.181.952-.977 1.68-2.155 2.18-3.534.527-1.404.79-2.995.79-4.775Zm20.227-18.648h8.836V57h-8.836V19.365ZM49.773 5.98c0-.752.138-1.466.413-2.143a6.18 6.18 0 0 1 1.166-1.767A4.994 4.994 0 0 1 53.043.904a4.975 4.975 0 0 1 2.105-.451 5.23 5.23 0 0 1 2.143.451A5.305 5.305 0 0 1 59.06 2.07a5.29 5.29 0 0 1 1.165 1.767c.276.677.414 1.391.414 2.143 0 .752-.138 1.466-.414 2.143A5.29 5.29 0 0 1 59.06 9.89c-.502.501-1.09.89-1.767 1.166a5.639 5.639 0 0 1-2.144.413 5.36 5.36 0 0 1-2.105-.413 4.994 4.994 0 0 1-1.692-1.166 6.18 6.18 0 0 1-1.166-1.767 5.626 5.626 0 0 1-.413-2.143ZM83.159 57l-16.43-37.635h9.436l10.152 24.777 10.678-24.777h9.437L89.325 57h-6.166Zm41.056 0-16.43-37.635h9.437l10.152 24.777 10.677-24.777h9.437L130.381 57h-6.166Zm29.778-37.635h8.835V57h-8.835V19.365Zm-.978-13.385c0-.752.138-1.466.414-2.143.3-.677.689-1.266 1.165-1.767a4.99 4.99 0 0 1 1.692-1.166 4.978 4.978 0 0 1 2.106-.451c.752 0 1.466.151 2.143.451a5.32 5.32 0 0 1 1.767 1.166c.501.501.89 1.09 1.165 1.767a5.62 5.62 0 0 1 .414 2.143 5.62 5.62 0 0 1-.414 2.143 5.282 5.282 0 0 1-1.165 1.767 5.32 5.32 0 0 1-1.767 1.166 5.639 5.639 0 0 1-2.143.413 5.363 5.363 0 0 1-2.106-.413 4.99 4.99 0 0 1-1.692-1.166 6.168 6.168 0 0 1-1.165-1.767 5.62 5.62 0 0 1-.414-2.143Z"
16
- />
17
- </Svg>
18
- )
12
+ return <Text style={styles.header}>Wallet Stack</Text>
19
13
  }
14
+
15
+ const styles = StyleSheet.create({
16
+ header: {
17
+ ...typeScale.displaySmall,
18
+ textAlign: 'center',
19
+ },
20
+ })
@@ -35,7 +35,6 @@ import Lock from 'src/icons/Lock'
35
35
  import Wallet from 'src/icons/navigator/Wallet'
36
36
  import Preferences from 'src/icons/Preferences'
37
37
  import Stack from 'src/icons/Stack'
38
- import DivviLogo from 'src/images/DivviLogo'
39
38
  import { headerWithCloseButton } from 'src/navigator/Headers'
40
39
  import { navigate } from 'src/navigator/NavigationService'
41
40
  import { Screens } from 'src/navigator/Screens'
@@ -277,9 +276,6 @@ export default function SettingsMenu({ route }: Props) {
277
276
  </View>
278
277
  </TouchableWithoutFeedback>
279
278
  {getDevSettingsComp()}
280
- <View style={styles.logo}>
281
- <DivviLogo />
282
- </View>
283
279
  </ScrollView>
284
280
  </SafeAreaView>
285
281
  )
@@ -345,9 +341,4 @@ const styles = StyleSheet.create({
345
341
  debugInfoText: {
346
342
  ...typeScale.bodySmall,
347
343
  },
348
- logo: {
349
- marginTop: 'auto',
350
- paddingVertical: Spacing.Thick24,
351
- alignItems: 'center',
352
- },
353
344
  })
@@ -10,7 +10,6 @@ import { OnboardingEvents } from 'src/analytics/Events'
10
10
  import { getAppConfig } from 'src/appConfig'
11
11
  import BottomSheet, { BottomSheetModalRefType } from 'src/components/BottomSheet'
12
12
  import Button, { BtnSizes, BtnTypes } from 'src/components/Button'
13
- import DivviLogo from 'src/images/DivviLogo'
14
13
  import WelcomeLogo from 'src/images/WelcomeLogo'
15
14
  import { nuxNavigationOptions } from 'src/navigator/Headers'
16
15
  import { navigate, navigateInitialTab } from 'src/navigator/NavigationService'
@@ -124,9 +123,6 @@ export default function Welcome() {
124
123
  testID={'RestoreAccountButton'}
125
124
  />
126
125
  </View>
127
- <View style={styles.divviLogoContainer}>
128
- <DivviLogo />
129
- </View>
130
126
  </ImageBackground>
131
127
  <BottomSheet
132
128
  forwardedRef={demoModeBottomSheetRef}
@@ -164,18 +160,13 @@ const styles = StyleSheet.create({
164
160
  },
165
161
  buttonView: {
166
162
  paddingHorizontal: Spacing.Thick24,
163
+ paddingBottom: 76,
167
164
  },
168
165
  image: {
169
166
  flex: 1,
170
167
  justifyContent: 'center',
171
168
  marginTop: Spacing.XLarge48,
172
169
  },
173
- divviLogoContainer: {
174
- width: '100%',
175
- marginTop: Spacing.Large32,
176
- paddingBottom: Spacing.Regular16,
177
- alignItems: 'center',
178
- },
179
170
  demoModeButton: {
180
171
  marginTop: Spacing.Thick24,
181
172
  },
@@ -22,7 +22,7 @@ function getOnboardingFeatures(config: PublicAppConfig) {
22
22
  }
23
23
  // Special case for e2e to test phone number verification
24
24
  // As we're not yet sure how we wanna expose this feature in the runtime
25
- if (process.env.EXPO_PUBLIC_DIVVI_E2E === 'true') {
25
+ if (process.env.EXPO_PUBLIC_WALLET_STACK_E2E === 'true') {
26
26
  onboardingFeatures.push(ToggleableOnboardingFeatures.PhoneVerification)
27
27
  }
28
28
  if (onboardingConfig) {
@@ -15,7 +15,7 @@ const TAG = 'public/navigate'
15
15
 
16
16
  declare global {
17
17
  // eslint-disable-next-line @typescript-eslint/no-namespace
18
- namespace DivviNavigation {
18
+ namespace WalletNavigation {
19
19
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
20
20
  interface RootParamList extends StackParamList {}
21
21
  }
@@ -25,7 +25,7 @@ export type NavigatorScreen = ReturnType<
25
25
  // TODO: this weird looking type works around a type error when checking the lib vs example app
26
26
  // Maybe we can get rid of this once we're able to ship declaration files
27
27
  typeof createNativeStackNavigator<
28
- DivviNavigation.RootParamList extends ParamListBase ? DivviNavigation.RootParamList : never
28
+ WalletNavigation.RootParamList extends ParamListBase ? WalletNavigation.RootParamList : never
29
29
  >
30
30
  >['Screen']
31
31
 
@@ -57,7 +57,7 @@ export type StackParamList = {
57
57
 
58
58
  export type { NativeStackScreenProps } from '@react-navigation/native-stack'
59
59
 
60
- type NavigateArgs<ParamList = DivviNavigation.RootParamList> = {
60
+ type NavigateArgs<ParamList = WalletNavigation.RootParamList> = {
61
61
  [RouteName in keyof ParamList]: undefined extends ParamList[RouteName]
62
62
  ? [RouteName] | [RouteName, ParamList[RouteName]]
63
63
  : [RouteName, ParamList[RouteName]]
@@ -1,5 +1,4 @@
1
1
  import type { ImageSourcePropType } from 'react-native'
2
- import { Address } from 'viem'
3
2
  import type { NavigatorScreen } from './navigate'
4
3
 
5
4
  // Type for tab configuration
@@ -262,10 +261,6 @@ export interface PublicAppConfig<tabScreenConfigs extends TabScreenConfig[] = Ta
262
261
  showSwapTokenFilters?: boolean
263
262
  enableSwapAppFee?: boolean
264
263
  }
265
-
266
- divviProtocol?: {
267
- divviId: Address
268
- }
269
264
  }
270
265
 
271
266
  // TODO: we'll use this type throughout the framework once we're able to make bigger refactor, eliminating the current NetworkId enum
@@ -66,6 +66,7 @@ import {
66
66
  v233Schema,
67
67
  v235Schema,
68
68
  v251Schema,
69
+ v253Schema,
69
70
  v28Schema,
70
71
  v2Schema,
71
72
  v35Schema,
@@ -1922,4 +1923,18 @@ describe('Redux persist migrations', () => {
1922
1923
  expectedSchema.app = _.omit(oldSchema.app, 'inviterAddress')
1923
1924
  expect(migratedSchema).toStrictEqual(expectedSchema)
1924
1925
  })
1926
+
1927
+ it('works from 253 to 254', () => {
1928
+ const oldSchema = {
1929
+ ...v253Schema,
1930
+ home: {
1931
+ ...v253Schema.home,
1932
+ hasSeenDivviBottomSheet: false,
1933
+ },
1934
+ }
1935
+ const migratedSchema = migrations[254](oldSchema)
1936
+ const expectedSchema: any = _.cloneDeep(oldSchema)
1937
+ expectedSchema.home = _.omit(oldSchema.home, 'hasSeenDivviBottomSheet')
1938
+ expect(migratedSchema).toStrictEqual(expectedSchema)
1939
+ })
1925
1940
  })
@@ -2066,4 +2066,8 @@ export const migrations = {
2066
2066
  ...state,
2067
2067
  sendCalls: { batchById: {} },
2068
2068
  }),
2069
+ 254: (state: any) => ({
2070
+ ...state,
2071
+ home: _.omit(state.home, 'hasSeenDivviBottomSheet'),
2072
+ }),
2069
2073
  }
@@ -143,7 +143,7 @@ describe('store state', () => {
143
143
  {
144
144
  "_persist": {
145
145
  "rehydrated": true,
146
- "version": 253,
146
+ "version": 254,
147
147
  },
148
148
  "account": {
149
149
  "acceptedTerms": false,
@@ -230,7 +230,6 @@ describe('store state', () => {
230
230
  "txHashToProvider": {},
231
231
  },
232
232
  "home": {
233
- "hasSeenDivviBottomSheet": false,
234
233
  "hasVisitedHome": true,
235
234
  "loading": false,
236
235
  "nftCelebration": null,
@@ -30,7 +30,7 @@ const persistConfig: PersistConfig<ReducersRootState> = {
30
30
  key: 'root',
31
31
  // default is -1, increment as we make migrations
32
32
  // See https://github.com/valora-xyz/wallet/tree/main/WALLET.md#redux-state-migration
33
- version: 253,
33
+ version: 254,
34
34
  keyPrefix: `reduxStore-`, // the redux-persist default is `persist:` which doesn't work with some file systems.
35
35
  storage: FSStorage(),
36
36
  blacklist: ['networkInfo', 'alert', 'imports', 'keylessBackup', transactionFeedV2Api.reducerPath],
@@ -33,7 +33,6 @@ export const FeatureGates = {
33
33
  [StatsigFeatureGates.SHOW_NEW_ENTER_AMOUNT_FOR_SWAP]: true,
34
34
  [StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAP_AND_DEPOSIT]: false,
35
35
  [StatsigFeatureGates.DISABLE_WALLET_CONNECT_V2]: false,
36
- [StatsigFeatureGates.SHOW_DIVVI_SLICES_BOTTOM_SHEET]: false,
37
36
  [StatsigFeatureGates.RECAPTCHA_ENABLED]: false,
38
37
  [StatsigFeatureGates.USE_SMART_ACCOUNT_CAPABILITIES]: false,
39
38
  } satisfies { [key in StatsigFeatureGates]: boolean }
@@ -165,10 +164,6 @@ export const DynamicConfigs = {
165
164
  inviteRewardsVersion: 'none',
166
165
  },
167
166
  },
168
- [StatsigDynamicConfigs.DIVVI_SLICES_BOTTOM_SHEET_CONFIG]: {
169
- configName: StatsigDynamicConfigs.DIVVI_SLICES_BOTTOM_SHEET_CONFIG,
170
- defaultValues: {} as { url?: string },
171
- },
172
167
  } satisfies {
173
168
  [key in StatsigDynamicConfigs]: {
174
169
  configName: key
@@ -11,7 +11,6 @@ export enum StatsigDynamicConfigs {
11
11
  DEMO_MODE_CONFIG = 'demo_mode_config',
12
12
  FIAT_CONNECT_CONFIG = 'fiat_connect_config',
13
13
  INVITE_REWARDS_CONFIG = 'invite_rewards_config',
14
- DIVVI_SLICES_BOTTOM_SHEET_CONFIG = 'divvi_slices_bottom_sheet_config',
15
14
  }
16
15
 
17
16
  export enum StatsigFeatureGates {
@@ -35,7 +34,6 @@ export enum StatsigFeatureGates {
35
34
  SHOW_NEW_ENTER_AMOUNT_FOR_SWAP = 'show_new_enter_amount_for_swap',
36
35
  ALLOW_CROSS_CHAIN_SWAP_AND_DEPOSIT = 'allow_cross_chain_swap_and_deposit',
37
36
  DISABLE_WALLET_CONNECT_V2 = 'disable_wallet_connect_v2',
38
- SHOW_DIVVI_SLICES_BOTTOM_SHEET = 'show_divvi_slices_bottom_sheet',
39
37
  RECAPTCHA_ENABLED = 'recaptcha_enabled',
40
38
  USE_SMART_ACCOUNT_CAPABILITIES = 'use_smart_account_capabilities',
41
39
  }
@@ -1,8 +1,6 @@
1
- import { getReferralTag } from '@divvi/referral-sdk'
2
1
  import BigNumber from 'bignumber.js'
3
2
  import AppAnalytics from 'src/analytics/AppAnalytics'
4
3
  import { TransactionEvents } from 'src/analytics/Events'
5
- import { getAppConfig } from 'src/appConfig'
6
4
  import { TokenBalanceWithAddress } from 'src/tokens/slice'
7
5
  import { Network, NetworkId } from 'src/transactions/types'
8
6
  import { estimateFeesPerGas } from 'src/viem/estimateFeesPerGas'
@@ -22,7 +20,7 @@ import {
22
20
  tryEstimateTransaction,
23
21
  tryEstimateTransactions,
24
22
  } from 'src/viem/prepareTransactions'
25
- import { mockAppConfig, mockCeloTokenBalance, mockEthTokenBalance } from 'test/values'
23
+ import { mockCeloTokenBalance, mockEthTokenBalance } from 'test/values'
26
24
  import {
27
25
  Address,
28
26
  BaseError,
@@ -37,7 +35,6 @@ import {
37
35
  import { estimateGas } from 'viem/actions'
38
36
  import mocked = jest.mocked
39
37
 
40
- jest.mock('@divvi/referral-sdk')
41
38
  jest.mock('src/viem/estimateFeesPerGas')
42
39
  jest.mock('viem', () => ({
43
40
  ...jest.requireActual('viem'),
@@ -61,7 +58,6 @@ jest.mock('src/viem/index', () => ({
61
58
 
62
59
  beforeEach(() => {
63
60
  jest.clearAllMocks()
64
- jest.mocked(getReferralTag).mockReturnValue('divviData')
65
61
  })
66
62
 
67
63
  describe('prepareTransactions module', () => {
@@ -144,67 +140,6 @@ describe('prepareTransactions module', () => {
144
140
  }
145
141
  const mockPublicClient = {} as unknown as jest.Mocked<(typeof publicClient)[Network.Celo]>
146
142
  describe('prepareTransactions function', () => {
147
- it.each([
148
- {
149
- description: 'is attached when data is provided',
150
- inputData: '0xdata' as Hex,
151
- expectedData: '0xdatadivviData',
152
- },
153
- {
154
- description: 'is not attached when data is undefined',
155
- inputData: undefined,
156
- expectedData: undefined,
157
- },
158
- ])('divvi data $description', async ({ inputData, expectedData }) => {
159
- jest.mocked(getAppConfig).mockReturnValueOnce({
160
- ...mockAppConfig,
161
- divviProtocol: {
162
- divviId: '0xdivviId',
163
- },
164
- })
165
- jest.mocked(getReferralTag).mockReturnValue('divviData')
166
- mocked(estimateFeesPerGas).mockResolvedValue({
167
- maxFeePerGas: BigInt(100),
168
- maxPriorityFeePerGas: BigInt(2),
169
- baseFeePerGas: BigInt(50),
170
- })
171
- mocked(estimateGas).mockResolvedValue(BigInt(1_000))
172
-
173
- // max gas fee is 100 * 1k = 100k units, too high for either fee currency
174
-
175
- const result = await prepareTransactions({
176
- feeCurrencies: mockFeeCurrencies,
177
- spendToken: mockSpendToken,
178
- spendTokenAmount: new BigNumber(45_000),
179
- decreasedAmountGasFeeMultiplier: 1,
180
- baseTransactions: [
181
- {
182
- from: '0xfrom' as Address,
183
- to: '0xto' as Address,
184
- data: inputData,
185
- },
186
- ],
187
- isGasSubsidized: true,
188
- origin: 'send',
189
- })
190
- expect(result).toStrictEqual({
191
- type: 'possible',
192
- feeCurrency: mockFeeCurrencies[0],
193
- transactions: [
194
- {
195
- from: '0xfrom',
196
- to: '0xto',
197
- data: expectedData,
198
-
199
- gas: BigInt(1000),
200
- maxFeePerGas: BigInt(100),
201
- maxPriorityFeePerGas: BigInt(2),
202
- _baseFeePerGas: BigInt(50),
203
- },
204
- ],
205
- })
206
- })
207
-
208
143
  it('throws if trying to sendAmount > sendToken balance', async () => {
209
144
  await expect(() =>
210
145
  prepareTransactions({
@@ -1001,45 +936,6 @@ describe('prepareTransactions module', () => {
1001
936
  expect(result?.data).toBe(originalTransferData)
1002
937
  expect(result?.gas).toBe(BigInt(21000))
1003
938
  })
1004
- it('estimates with reduced amount for gas-token ERC20 transfers with divvi suffix and restores original amount', async () => {
1005
- const originalTransferData =
1006
- '0xa9059cbb0000000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f000000000000000000000000000000000000000000000000000000000000003e8divviData' as Hex // 1000
1007
- const baseTransaction: TransactionRequest = {
1008
- from: '0x123' as Address,
1009
- to: mockSpendToken.address as Address,
1010
- data: originalTransferData,
1011
- }
1012
-
1013
- // Mock estimateGas to capture what data is passed
1014
- let capturedData: Hex | undefined
1015
- mocked(estimateGas).mockImplementation(async (_client, tx: any) => {
1016
- capturedData = tx.data
1017
- return BigInt(21000)
1018
- })
1019
-
1020
- const result = await tryEstimateTransaction({
1021
- client: mockPublicClient,
1022
- baseTransaction,
1023
- maxFeePerGas: BigInt(100),
1024
- maxPriorityFeePerGas: BigInt(2),
1025
- baseFeePerGas: BigInt(50),
1026
- feeCurrencySymbol: mockSpendToken.symbol,
1027
- feeCurrencyAddress: mockSpendToken.address as Address,
1028
- spendToken: mockSpendToken,
1029
- spendTokenAmount: new BigNumber(1000),
1030
- isGasSubsidized: false,
1031
- })
1032
-
1033
- // The estimateGas should have been called with reduced amount (1)
1034
- expect(capturedData).toContain(
1035
- '0000000000000000000000000000000000000000000000000000000000000001divviData'
1036
- ) // 1 in hex
1037
-
1038
- // But the returned transaction should have the original data
1039
- expect(result).toBeDefined()
1040
- expect(result?.data).toBe(originalTransferData)
1041
- expect(result?.gas).toBe(BigInt(21000))
1042
- })
1043
939
  it('does not modify amount for gas-token transfers when gas is subsidized', async () => {
1044
940
  const originalTransferData =
1045
941
  '0xa9059cbb0000000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f000000000000000000000000000000000000000000000000000000000000003e8' as Hex // 1000
@@ -1,9 +1,7 @@
1
- import { getReferralTag } from '@divvi/referral-sdk'
2
1
  import BigNumber from 'bignumber.js'
3
2
  import AppAnalytics from 'src/analytics/AppAnalytics'
4
3
  import { TransactionEvents } from 'src/analytics/Events'
5
4
  import { TransactionOrigin } from 'src/analytics/types'
6
- import { getAppConfig } from 'src/appConfig'
7
5
  import { STATIC_GAS_PADDING } from 'src/config'
8
6
  import {
9
7
  NativeTokenBalance,
@@ -134,7 +132,7 @@ function modifyERC20TransferAmount(originalData: Hex, newAmount: BigNumber): Hex
134
132
  const newAmountHex = newAmount.toString(16).padStart(AMOUNT_HEX_LENGTH, '0')
135
133
 
136
134
  // Return the modified data, appending the original data after the expected
137
- // length (for any suffix like the divvi suffix)
135
+ // length (for any suffix)
138
136
  return (recipientPart + newAmountHex + originalData.slice(expectedLength)) as Hex
139
137
  }
140
138
 
@@ -413,23 +411,6 @@ export async function prepareTransactions({
413
411
  )
414
412
  }
415
413
 
416
- // Attach divvi tag to all transactions if divvi is enabled
417
- const config = getAppConfig()
418
- if (config.divviProtocol) {
419
- const walletAddress = baseTransactions[0].from
420
- const referralTag =
421
- walletAddress &&
422
- getReferralTag({
423
- consumer: config.divviProtocol.divviId,
424
- user: walletAddress,
425
- })
426
- if (referralTag) {
427
- baseTransactions.forEach((tx) => {
428
- tx.data = tx.data && ((tx.data + referralTag) as Hex)
429
- })
430
- }
431
- }
432
-
433
414
  const gasFees: Array<{
434
415
  feeCurrency: TokenBalance
435
416
  maxGasFeeInDecimal: BigNumber
@@ -3,7 +3,6 @@ import { expectSaga } from 'redux-saga-test-plan'
3
3
  import * as matchers from 'redux-saga-test-plan/matchers'
4
4
  import { EffectProviders, StaticProvider, throwError } from 'redux-saga-test-plan/providers'
5
5
  import { call } from 'redux-saga/effects'
6
- import { submitDivviReferralSaga } from 'src/divviProtocol/saga'
7
6
  import { BaseStandbyTransaction, addStandbyTransaction } from 'src/transactions/slice'
8
7
  import { NetworkId, TokenTransactionTypeV2 } from 'src/transactions/types'
9
8
  import { ViemWallet } from 'src/viem/getLockableWallet'
@@ -23,10 +22,6 @@ import {
23
22
  } from 'test/values'
24
23
  import { getTransactionCount } from 'viem/actions'
25
24
 
26
- jest.mock('src/divviProtocol/saga', () => ({
27
- submitDivviReferralSaga: jest.fn().mockResolvedValue(true),
28
- }))
29
-
30
25
  const preparedTransactions: TransactionRequest[] = [
31
26
  {
32
27
  from: '0xa',
@@ -121,14 +116,6 @@ describe('sendPreparedTransactions', () => {
121
116
  .withState(createMockStore({}).getState())
122
117
  .provide(createDefaultProviders())
123
118
  .call(getViemWallet, networkConfig.viemChain.celo, false)
124
- .call(submitDivviReferralSaga, {
125
- txHash: '0xmockTxHash1',
126
- chainId: 42220,
127
- })
128
- .call(submitDivviReferralSaga, {
129
- txHash: '0xmockTxHash2',
130
- chainId: 42220,
131
- })
132
119
  .put(
133
120
  addStandbyTransaction({
134
121
  ...mockStandbyTransactions[0],
package/src/viem/saga.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { submitDivviReferralSaga } from 'src/divviProtocol/saga'
2
1
  import { navigate } from 'src/navigator/NavigationService'
3
2
  import { Screens } from 'src/navigator/Screens'
4
3
  import { CANCELLED_PIN_INPUT } from 'src/pincode/authentication'
@@ -79,7 +78,6 @@ export function* sendPreparedTransactions(
79
78
  blockTag: 'pending',
80
79
  })
81
80
 
82
- const chainId = yield* call([wallet, 'getChainId'])
83
81
  const txHashes: Hash[] = []
84
82
  for (let i = 0; i < preparedTransactions.length; i++) {
85
83
  const preparedTransaction = preparedTransactions[i]
@@ -99,11 +97,6 @@ export function* sendPreparedTransactions(
99
97
  hash
100
98
  )
101
99
 
102
- yield* call(submitDivviReferralSaga, {
103
- txHash: hash,
104
- chainId,
105
- })
106
-
107
100
  const tokensById = yield* select(tokensByIdSelector)
108
101
  const feeCurrencyId = getFeeCurrencyToken([preparedTransaction], networkId, tokensById)?.tokenId
109
102
 
@@ -1,5 +1,5 @@
1
1
  {
2
- // This tsconfig is intended to be used by apps consuming the divvi mobile package
2
+ // This tsconfig is intended to be used by apps consuming the wallet-stack package
3
3
  // TODO: extend from "expo/tsconfig.base" once we're able to get rid of `src` absolute paths
4
4
  "extends": "./tsconfig.json"
5
5
  }
@@ -1,51 +0,0 @@
1
- import { submitReferral } from '@divvi/referral-sdk'
2
- import { expectSaga } from 'redux-saga-test-plan'
3
- import * as matchers from 'redux-saga-test-plan/matchers'
4
- import { call } from 'redux-saga-test-plan/matchers'
5
- import { throwError } from 'redux-saga-test-plan/providers'
6
- import { Address } from 'viem'
7
- import { submitDivviReferralSaga } from './saga'
8
-
9
- const provideDelay = ({ fn }: { fn: { name: string } }, next: () => void) =>
10
- fn.name === 'delayP' || fn.name === 'delay' ? null : next()
11
-
12
- describe('submitDivviReferralSaga', () => {
13
- const mockParams = {
14
- txHash: '0x123' as Address,
15
- chainId: 1,
16
- }
17
-
18
- beforeEach(() => {
19
- jest.useFakeTimers()
20
- })
21
-
22
- afterEach(() => {
23
- jest.useRealTimers()
24
- })
25
-
26
- it('successfully submits a referral on first try', async () => {
27
- await expectSaga(submitDivviReferralSaga, mockParams)
28
- .provide([[matchers.call.fn(submitReferral), undefined]])
29
- .run()
30
- })
31
-
32
- it('retries on server error and succeeds', async () => {
33
- const serverError = new Error('Server error')
34
-
35
- await expectSaga(submitDivviReferralSaga, mockParams)
36
- .provide([
37
- [call(submitReferral), throwError(serverError)],
38
- { call: provideDelay },
39
- [call(submitReferral), undefined],
40
- ])
41
- .run()
42
- })
43
-
44
- it('stops retrying on client error', async () => {
45
- const clientError = new Error('Client error')
46
-
47
- await expectSaga(submitDivviReferralSaga, mockParams)
48
- .provide([[matchers.call.fn(submitReferral), throwError(clientError)]])
49
- .run()
50
- })
51
- })
@@ -1,44 +0,0 @@
1
- import { Address, submitReferral } from '@divvi/referral-sdk'
2
- import Logger from 'src/utils/Logger'
3
- import { call, delay } from 'typed-redux-saga'
4
-
5
- const TAG = 'divviProtocol/saga'
6
-
7
- export function* submitDivviReferralSaga({
8
- txHash,
9
- chainId,
10
- }: {
11
- txHash: Address
12
- chainId: number
13
- }) {
14
- let attempt = 0
15
- const MAX_ATTEMPTS = 5
16
-
17
- while (attempt < MAX_ATTEMPTS) {
18
- try {
19
- Logger.info(TAG, `Submitting divvi referral ${txHash} attempt ${attempt + 1}`)
20
- yield* call(submitReferral, { txHash, chainId })
21
- Logger.info(TAG, `Divvi referral ${txHash} successful`)
22
- break // Exit on success
23
- } catch (error: unknown) {
24
- const message = error instanceof Error ? error.message : 'Unknown error'
25
- if (message.includes('Client error')) {
26
- // Do not retry on client errors
27
- Logger.info(TAG, `Divvi referral ${txHash} cancelled due to client error`)
28
- break
29
- }
30
-
31
- // Retry on server errors (5xx)
32
- attempt += 1
33
- if (attempt >= MAX_ATTEMPTS) {
34
- // If we've reached the max attempts, pause. We'll retry on the next app load.
35
- Logger.warn(TAG, `Divvi referral ${txHash} failed after ${MAX_ATTEMPTS} attempts`)
36
- break
37
- }
38
-
39
- const backoff = Math.min(2 ** attempt * 1000, 30000) // Exponential, capped at 30s
40
- Logger.info(TAG, `Retrying divvi referral ${txHash} in ${backoff}ms`)
41
- yield* delay(backoff)
42
- }
43
- }
44
- }
@@ -1,59 +0,0 @@
1
- import { render } from '@testing-library/react-native'
2
- import React from 'react'
3
- import { Provider } from 'react-redux'
4
- import { getFeatureGate } from 'src/statsig'
5
- import { createMockStore } from 'test/utils'
6
- import DivviBottomSheet from './DivviBottomSheet'
7
-
8
- jest.mock('src/analytics/AppAnalytics')
9
- jest.mock('src/statsig')
10
-
11
- describe('DivviBottomSheet', () => {
12
- beforeEach(() => {
13
- jest.mocked(getFeatureGate).mockReturnValue(true)
14
- })
15
-
16
- afterEach(() => {
17
- jest.clearAllMocks()
18
- })
19
-
20
- it('renders correctly when bottom sheet has not been seen before and feature gate is true', () => {
21
- const store = createMockStore({
22
- home: { hasSeenDivviBottomSheet: false },
23
- })
24
- const { getByText } = render(
25
- <Provider store={store}>
26
- <DivviBottomSheet />
27
- </Provider>
28
- )
29
- expect(getByText('divviBottomSheet.title')).toBeTruthy()
30
- expect(getByText('divviBottomSheet.cta')).toBeTruthy()
31
- })
32
-
33
- it('does not render when bottom sheet has been seen before', () => {
34
- const store = createMockStore({
35
- home: { hasSeenDivviBottomSheet: true },
36
- })
37
- const { queryByText } = render(
38
- <Provider store={store}>
39
- <DivviBottomSheet />
40
- </Provider>
41
- )
42
- expect(queryByText('divviBottomSheet.title')).toBeNull()
43
- expect(queryByText('divviBottomSheet.cta')).toBeNull()
44
- })
45
-
46
- it('does not render when bottom sheet has not been seen and feature gate is false', () => {
47
- jest.mocked(getFeatureGate).mockReturnValue(false)
48
- const store = createMockStore({
49
- home: { hasSeenDivviBottomSheet: false },
50
- })
51
- const { queryByText } = render(
52
- <Provider store={store}>
53
- <DivviBottomSheet />
54
- </Provider>
55
- )
56
- expect(queryByText('divviBottomSheet.title')).toBeNull()
57
- expect(queryByText('divviBottomSheet.cta')).toBeNull()
58
- })
59
- })
@@ -1,197 +0,0 @@
1
- import { BottomSheetView } from '@gorhom/bottom-sheet'
2
- import { Image } from 'expo-image'
3
- import React, { useEffect, useRef } from 'react'
4
- import { useTranslation } from 'react-i18next'
5
- import { StyleSheet, Text, View } from 'react-native'
6
- import { useSafeAreaInsets } from 'react-native-safe-area-context'
7
- import AppAnalytics from 'src/analytics/AppAnalytics'
8
- import { HomeEvents } from 'src/analytics/Events'
9
- import { BottomSheetModalRefType } from 'src/components/BottomSheet'
10
- import BottomSheetBase from 'src/components/BottomSheetBase'
11
- import Button, { BtnSizes, BtnTypes } from 'src/components/Button'
12
- import { divviBottomSheetSeen } from 'src/home/actions'
13
- import { showDivviBottomSheetSelector } from 'src/home/selectors'
14
- import { divviPie } from 'src/images/Images'
15
- import Logo from 'src/images/Logo'
16
- import LogoHeart from 'src/images/LogoHeart'
17
- import { navigate } from 'src/navigator/NavigationService'
18
- import { Screens } from 'src/navigator/Screens'
19
- import { useDispatch, useSelector } from 'src/redux/hooks'
20
- import Colors from 'src/styles/colors'
21
- import { typeScale } from 'src/styles/fonts'
22
- import { Spacing } from 'src/styles/styles'
23
-
24
- const DIVVI_SLICES_URL = 'https://slices.divvi.xyz'
25
-
26
- export default function DivviBottomSheet() {
27
- const dispatch = useDispatch()
28
- const { t } = useTranslation()
29
-
30
- const insets = useSafeAreaInsets()
31
- const insetsStyle = { paddingBottom: Math.max(insets.bottom, Spacing.Regular16) }
32
-
33
- const bottomSheetRef = useRef<BottomSheetModalRefType>(null)
34
-
35
- const shouldShowBottomSheet = useSelector(showDivviBottomSheetSelector)
36
-
37
- const handleBottomSheetPositionChange = (index: number) => {
38
- if (index === -1) {
39
- AppAnalytics.track(HomeEvents.divvi_bottom_sheet_displayed)
40
- dispatch(divviBottomSheetSeen())
41
- }
42
- }
43
-
44
- const handleCtaPress = () => {
45
- AppAnalytics.track(HomeEvents.divvi_bottom_sheet_cta_pressed)
46
- bottomSheetRef.current?.close()
47
- navigate(Screens.WebViewScreen, { uri: DIVVI_SLICES_URL })
48
- }
49
-
50
- const handleBottomSheetClose = () => {
51
- dispatch(divviBottomSheetSeen())
52
- }
53
-
54
- useEffect(() => {
55
- if (shouldShowBottomSheet) {
56
- const timeoutId = setTimeout(() => {
57
- bottomSheetRef.current?.expand()
58
- }, 1000)
59
- return () => clearTimeout(timeoutId)
60
- }
61
- }, [shouldShowBottomSheet, bottomSheetRef.current])
62
-
63
- if (!shouldShowBottomSheet) {
64
- return null
65
- }
66
-
67
- return (
68
- <BottomSheetBase
69
- forwardedRef={bottomSheetRef}
70
- handleComponent={() => null} // handle is rendered within the content body
71
- backgroundStyle={styles.bottomSheetBackground}
72
- onChange={handleBottomSheetPositionChange}
73
- onClose={handleBottomSheetClose}
74
- >
75
- <BottomSheetView style={[insetsStyle, styles.sheetContainer]}>
76
- <View style={styles.topSection}>
77
- <View style={styles.handleBar} />
78
- <View style={styles.iconRow}>
79
- <LogoHeart size={72} />
80
- <Text style={styles.iconPlus}>+</Text>
81
- <Logo
82
- size={56}
83
- color={Colors.secondaryAccent}
84
- backgroundColor={Colors.contentPrimary}
85
- />
86
- <Text style={styles.iconEquals}>=</Text>
87
- <Text style={styles.emoji}>🎉</Text>
88
- </View>
89
- </View>
90
- <View style={styles.bottomSection}>
91
- <Text style={styles.title}>{t('divviBottomSheet.title')}</Text>
92
- <View style={styles.descriptionRow}>
93
- <Text style={styles.description}>
94
- {t('divviBottomSheet.body_part1') + ' '}
95
- <Logo size={Spacing.Regular16} translateY={3} />
96
- {' ' + t('divviBottomSheet.body_part2') + ' '}
97
- <LogoHeart size={Spacing.Regular16} translateY={3} />
98
- {' ' + t('divviBottomSheet.body_part3')}
99
- <View style={styles.inlineImageWrapper}>
100
- <Image style={styles.inlineImage} source={divviPie} />
101
- </View>
102
- {t('divviBottomSheet.body_part4')}
103
- </Text>
104
- </View>
105
- <Button
106
- style={styles.button}
107
- type={BtnTypes.PRIMARY}
108
- size={BtnSizes.FULL}
109
- onPress={handleCtaPress}
110
- text={t('divviBottomSheet.cta')}
111
- />
112
- </View>
113
- </BottomSheetView>
114
- </BottomSheetBase>
115
- )
116
- }
117
-
118
- const styles = StyleSheet.create({
119
- sheetContainer: {
120
- padding: 0,
121
- backgroundColor: 'transparent',
122
- },
123
- topSection: {
124
- backgroundColor: Colors.contentPrimary,
125
- borderTopLeftRadius: Spacing.Thick24,
126
- borderTopRightRadius: Spacing.Thick24,
127
- alignItems: 'center',
128
- paddingTop: Spacing.Small12,
129
- paddingBottom: Spacing.Thick24,
130
- },
131
- handleBar: {
132
- width: 40,
133
- height: 4,
134
- borderRadius: 4,
135
- backgroundColor: Colors.borderPrimary,
136
- marginBottom: Spacing.Regular16,
137
- },
138
- iconRow: {
139
- flexDirection: 'row',
140
- alignItems: 'center',
141
- justifyContent: 'center',
142
- gap: Spacing.Regular16,
143
- },
144
- iconPlus: {
145
- ...typeScale.titleMedium,
146
- color: '#fff',
147
- fontWeight: 'bold',
148
- },
149
- iconEquals: {
150
- ...typeScale.titleMedium,
151
- color: '#fff',
152
- fontWeight: 'bold',
153
- },
154
- emoji: {
155
- fontSize: 50,
156
- },
157
- bottomSection: {
158
- paddingHorizontal: Spacing.Thick24,
159
- paddingTop: Spacing.Thick24,
160
- paddingBottom: Spacing.Regular16,
161
- alignItems: 'center',
162
- },
163
- title: {
164
- ...typeScale.titleSmall,
165
- fontWeight: 'bold',
166
- textAlign: 'center',
167
- marginBottom: Spacing.Small12,
168
- },
169
- descriptionRow: {
170
- flexDirection: 'row',
171
- flexWrap: 'wrap',
172
- alignItems: 'center',
173
- justifyContent: 'center',
174
- marginBottom: Spacing.Thick24,
175
- textAlign: 'left',
176
- },
177
- description: {
178
- ...typeScale.bodySmall,
179
- color: Colors.contentSecondary,
180
- },
181
- button: {
182
- ...typeScale.labelSemiBoldSmall,
183
- width: '100%',
184
- },
185
- bottomSheetBackground: {
186
- marginTop: Spacing.Thick24,
187
- },
188
- inlineImageWrapper: {
189
- paddingLeft: 2,
190
- paddingRight: 2,
191
- },
192
- inlineImage: {
193
- width: Spacing.Regular16,
194
- height: Spacing.Regular16,
195
- transform: [{ translateY: 3 }],
196
- },
197
- })
@@ -1,22 +0,0 @@
1
- import * as React from 'react'
2
- import Svg, { Path } from 'react-native-svg'
3
- import Colors from 'src/styles/colors'
4
-
5
- const DivviLogo = () => (
6
- <Svg width={85} height={29} viewBox="0 0 71 24" fill="none">
7
- <Path
8
- fill={Colors.contentPrimary}
9
- d="M18.048 0H5.952A5.952 5.952 0 0 0 0 5.952v12.096A5.952 5.952 0 0 0 5.952 24h12.096A5.952 5.952 0 0 0 24 18.048V5.952A5.952 5.952 0 0 0 18.048 0Z"
10
- />
11
- <Path
12
- fill={Colors.backgroundPrimary}
13
- d="M8.845 5.142 5.143 8.844a.807.807 0 0 0 0 1.142l8.876 8.875a.807.807 0 0 0 1.141 0l3.702-3.702a.807.807 0 0 0 0-1.141L9.987 5.142a.807.807 0 0 0-1.142 0ZM17.872 10.445V6.39a.257.257 0 0 0-.256-.256H13.56a.257.257 0 0 0-.18.441l4.056 4.057c.16.16.441.045.441-.18l-.005-.006ZM6.128 13.56v4.056c0 .14.115.256.256.256h4.056c.231 0 .346-.276.18-.441l-4.056-4.057c-.16-.16-.441-.045-.441.18l.005.006Z"
14
- />
15
- <Path
16
- fill={Colors.contentPrimary}
17
- d="M28.763 7.95H28V2.72h.641l.21.402c.068-.083.142-.152.223-.21a1.304 1.304 0 0 1 .57-.227c.108-.016.223-.025.345-.025.276 0 .53.048.763.142.235.095.437.227.607.395a1.812 1.812 0 0 1 .546 1.312c0 .26-.048.504-.144.729a1.803 1.803 0 0 1-1.005.977 2.015 2.015 0 0 1-.767.143c-.122 0-.238-.01-.35-.029a1.825 1.825 0 0 1-.322-.078 1.585 1.585 0 0 1-.292-.146 1.55 1.55 0 0 1-.262-.217V7.95Zm0-3.438c0 .185.028.35.083.495.059.144.14.266.243.366.107.1.232.175.376.227.147.052.309.079.486.079s.339-.03.486-.09a1.107 1.107 0 0 0 .618-.614c.058-.143.088-.297.088-.463a1.137 1.137 0 0 0-.721-1.084 1.227 1.227 0 0 0-.471-.089c-.177 0-.34.028-.486.082-.144.052-.27.128-.376.228-.104.1-.184.222-.243.37-.055.144-.083.309-.083.493Zm3.674 0a1.724 1.724 0 0 1 .592-1.315c.185-.166.4-.296.649-.391.248-.095.516-.142.805-.142.288 0 .556.047.804.142.25.095.467.225.65.391.184.166.33.361.436.587.106.225.159.468.159.728 0 .261-.053.504-.16.73-.105.224-.251.42-.436.586a2.04 2.04 0 0 1-.649.387 2.235 2.235 0 0 1-.804.143c-.289 0-.557-.048-.805-.143a2.098 2.098 0 0 1-.649-.387 1.724 1.724 0 0 1-.592-1.315Zm.763 0a1.09 1.09 0 0 0 .376.832c.116.105.251.186.405.246.157.059.324.089.502.089.177 0 .344-.03.5-.09.158-.059.294-.14.41-.245a1.127 1.127 0 0 0 .376-.832 1.127 1.127 0 0 0-.376-.832 1.26 1.26 0 0 0-.41-.248 1.408 1.408 0 0 0-.5-.09c-.178 0-.345.03-.502.09a1.264 1.264 0 0 0-.683.618 1.09 1.09 0 0 0-.098.462Zm8.873-1.791h.816l-1.472 3.558h-.642l-.877-2.584-.94 2.584h-.638l-1.404-3.558h.815l.915 2.556.907-2.556h.71l.88 2.545.93-2.545Zm4.767 2.52v.729a1.947 1.947 0 0 1-.694.295 3.81 3.81 0 0 1-.854.093c-.301 0-.576-.048-.824-.143a1.975 1.975 0 0 1-.641-.39 1.77 1.77 0 0 1-.414-.587 1.835 1.835 0 0 1-.144-.73c0-.26.05-.502.148-.724a1.858 1.858 0 0 1 1.73-1.12c.269 0 .517.044.745.131.23.088.428.21.596.366a1.735 1.735 0 0 1 .539 1.276v.246h-2.98c.02.147.06.282.121.405.063.124.15.23.258.317.112.087.247.156.407.206.162.047.353.071.573.071.248 0 .497-.033.747-.1.251-.068.48-.182.687-.34Zm-1.692-1.898c-.147 0-.28.022-.399.067a1.08 1.08 0 0 0-.315.175.97.97 0 0 0-.338.536h2.1a.834.834 0 0 0-.323-.568.935.935 0 0 0-.315-.157 1.46 1.46 0 0 0-.41-.053Zm4.698-.64.232.764a1.197 1.197 0 0 0-.531-.124c-.18 0-.328.027-.445.082a.62.62 0 0 0-.269.23.952.952 0 0 0-.136.367 2.821 2.821 0 0 0-.039.49V6.28h-.763V2.721h.642l.209.401c.063-.073.124-.137.182-.192a.906.906 0 0 1 .402-.224 1.251 1.251 0 0 1 .516-.004Zm4.251 2.538v.729a1.95 1.95 0 0 1-.694.295 3.806 3.806 0 0 1-.854.093c-.302 0-.576-.048-.824-.143a1.97 1.97 0 0 1-.641-.39 1.763 1.763 0 0 1-.414-.587 1.835 1.835 0 0 1-.144-.73c0-.26.05-.502.148-.724a1.85 1.85 0 0 1 .998-.978c.23-.095.474-.142.732-.142.268 0 .517.044.745.131.23.088.428.21.595.366a1.735 1.735 0 0 1 .54 1.276v.246h-2.98c.02.147.06.282.121.405.064.124.15.23.258.317.112.087.247.156.407.206.161.047.352.071.573.071.248 0 .497-.033.748-.1.25-.068.479-.182.686-.34Zm-1.693-1.898a1.118 1.118 0 0 0-.713.242.962.962 0 0 0-.338.536h2.1a.847.847 0 0 0-.107-.323.744.744 0 0 0-.216-.245.942.942 0 0 0-.316-.157 1.457 1.457 0 0 0-.41-.053ZM57.957 1l.763.338v4.941h-.641l-.21-.401a1.18 1.18 0 0 1-.492.355 1.345 1.345 0 0 1-.304.082 2.215 2.215 0 0 1-1.109-.117 1.9 1.9 0 0 1-.607-.392 1.85 1.85 0 0 1-.542-1.315 1.837 1.837 0 0 1 .542-1.315c.17-.166.372-.297.607-.391a2.142 2.142 0 0 1 1.446-.032 1.317 1.317 0 0 1 .547.359V1Zm0 3.488c0-.185-.029-.35-.087-.495a.966.966 0 0 0-.243-.366 1.031 1.031 0 0 0-.376-.227 1.41 1.41 0 0 0-.482-.079c-.177 0-.339.03-.485.09a1.11 1.11 0 0 0-.377.245 1.125 1.125 0 0 0-.242.37 1.216 1.216 0 0 0-.087.462c0 .168.029.324.087.469.058.142.14.265.242.37.105.104.23.186.377.245.146.06.308.089.485.089s.338-.026.482-.078a.999.999 0 0 0 .618-.597 1.34 1.34 0 0 0 .088-.498Zm3.906 1.791V1l.762.338v1.774a1.256 1.256 0 0 1 .547-.36 2.15 2.15 0 0 1 .68-.11c.278 0 .533.048.766.143.235.094.438.225.607.39a1.835 1.835 0 0 1 .543 1.315 1.826 1.826 0 0 1-.546 1.316c-.17.166-.372.297-.608.392a2.002 2.002 0 0 1-.762.142 2.077 2.077 0 0 1-.661-.107 1.432 1.432 0 0 1-.281-.146 1.404 1.404 0 0 1-.246-.22v.412h-.801Zm.762-1.791c0 .184.029.35.084.497.058.145.14.268.243.37.106.1.232.175.376.228.147.052.308.078.486.078.176 0 .339-.03.485-.09a1.104 1.104 0 0 0 .619-.614c.058-.145.087-.301.087-.47 0-.165-.029-.32-.087-.462a1.104 1.104 0 0 0-.618-.615 1.287 1.287 0 0 0-.486-.089c-.178 0-.34.027-.486.079-.144.052-.27.128-.376.227a1.04 1.04 0 0 0-.243.366c-.056.145-.084.31-.084.495Zm5.04 1.653L66.11 2.72h.797l1.13 2.591 1.185-2.591H70L67.62 8l-.656-.292.702-1.567ZM35.217 9.06l2.238.943V23.2h-1.857l-.457-.933a2.896 2.896 0 0 1-.543.505c-.197.133-.41.244-.638.333a3.19 3.19 0 0 1-.714.19 5.4 5.4 0 0 1-.8.057 4.841 4.841 0 0 1-1.933-.38 4.703 4.703 0 0 1-1.533-1.048 5.101 5.101 0 0 1-1.009-1.58 5.181 5.181 0 0 1-.362-1.943c0-.698.121-1.349.362-1.952a4.983 4.983 0 0 1 1.01-1.57 4.595 4.595 0 0 1 1.523-1.048 4.909 4.909 0 0 1 1.942-.381c.273 0 .537.022.79.067.255.038.499.098.734.18.235.083.457.191.666.324.21.133.403.295.581.486V9.06Zm.02 9.332c0-.445-.068-.841-.2-1.19a2.384 2.384 0 0 0-1.4-1.438 3.056 3.056 0 0 0-1.105-.19c-.4 0-.768.072-1.105.218-.33.14-.612.337-.847.59-.235.255-.419.553-.552.896-.133.342-.2.714-.2 1.114 0 .406.067.784.2 1.133s.317.65.552.904c.235.248.517.445.847.59.337.14.705.21 1.105.21.406 0 .774-.06 1.105-.18.33-.128.612-.312.847-.553.241-.248.425-.546.552-.895.133-.356.2-.759.2-1.21Zm5.122-4.723h2.237V23.2H40.36v-9.531Zm-.248-3.39c0-.19.035-.371.105-.542.076-.172.175-.321.295-.448s.264-.225.428-.295a1.32 1.32 0 0 1 1.076 0 1.338 1.338 0 0 1 .743.743c.07.171.105.352.105.542 0 .19-.035.372-.105.543a1.342 1.342 0 0 1-.743.743 1.424 1.424 0 0 1-1.075 0 1.262 1.262 0 0 1-.429-.295 1.567 1.567 0 0 1-.295-.448 1.423 1.423 0 0 1-.105-.543ZM48.566 23.2l-4.16-9.531h2.39l2.57 6.275 2.704-6.275h2.39L50.128 23.2h-1.562Zm10.398 0-4.161-9.531h2.39l2.57 6.275 2.705-6.275h2.39L60.524 23.2h-1.561Zm7.54-9.531h2.238V23.2h-2.237v-9.531Zm-.247-3.39c0-.19.035-.371.105-.542.076-.172.174-.321.295-.448.12-.127.263-.225.428-.295a1.323 1.323 0 0 1 1.076 0 1.34 1.34 0 0 1 .743.743c.07.171.105.352.105.542 0 .19-.035.372-.105.543a1.342 1.342 0 0 1-.742.743 1.427 1.427 0 0 1-1.077 0 1.262 1.262 0 0 1-.428-.295 1.567 1.567 0 0 1-.295-.448 1.423 1.423 0 0 1-.105-.543Z"
18
- />
19
- </Svg>
20
- )
21
-
22
- export default DivviLogo
Binary file
Binary file
Binary file
Binary file
Binary file