Skip to content

Commit 17a8563

Browse files
committed
feat(analytics): add Add Money funnel tracking
Adds a unified 'Add Money' event family, modeled after the existing transfer events (single terminal event with State/Error properties): - Add Money: Opened — Source: Menu | Give Shortfall | Buy Shortfall | Chat | Scanner | Balance - Add Money: Method Selected — Method: Coinbase | Phantom | Other Wallet | Reserves - Add Money: Amount Confirmed — Method + amount properties - Add Money: Payment Invoked — Method + amount properties - Add Money: Address Copied — Mint (Other Wallet path) - Add Money — terminal, State=Success/Failure + Error + amount Instrumented across the flow: - MenuScreenViewModel: opened from menu tile - CashScreenViewModel: opened from give-shortfall alert (USDF only) - SwapViewModel: opened from buy-shortfall alert, amount confirmed, payment invoked (Coinbase order placed / Phantom pre-sign), terminal success on balance delivery, terminal failure on order delivery failure/timeout, Coinbase web errors, order placement errors, and Phantom transaction errors - ChatViewModel: opened from no-balance prompt when starting a chat - DepositDelegate: opened from scanner Give action with nothing giveable - BalanceViewModel: opened from balance screen (AddMoneyUX only) - InternalPurchaseMethodController: method selected from deposit sheet - DepositViewModel: address copied Funnel events only fire when acquiring USDF via an external funding source; launchpad token buys remain tracked via buy()/sell(). Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 7211ad1 commit 17a8563

14 files changed

Lines changed: 134 additions & 67 deletions

File tree

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/BalanceViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.flipcash.app.balance.internal
22

33
import androidx.lifecycle.viewModelScope
4+
import com.flipcash.app.analytics.Analytics
5+
import com.flipcash.app.analytics.FlipcashAnalyticsService
46
import com.flipcash.app.core.AppRoute
57
import com.flipcash.app.featureflags.FeatureFlag
68
import com.flipcash.app.featureflags.FeatureFlagController
@@ -28,6 +30,7 @@ internal class BalanceViewModel @Inject constructor(
2830
dispatchers: DispatcherProvider,
2931
purchaseMethodController: PurchaseMethodController,
3032
featureFlags: FeatureFlagController,
33+
analytics: FlipcashAnalyticsService,
3134
) : BaseViewModel<BalanceViewModel.State, BalanceViewModel.Event>(
3235
initialState = State(),
3336
updateStateForEvent = updateStateForEvent,
@@ -68,6 +71,7 @@ internal class BalanceViewModel @Inject constructor(
6871
dispatchEvent(Event.OpenScreen(AppRoute.Token.Discovery))
6972
return@mapNotNull null
7073
}
74+
analytics.addMoneyOpened(Analytics.AddMoneySource.Balance)
7175
purchaseMethodController.presentDepositOptions(popToRoot = true) }
7276
.onEach { route -> dispatchEvent(Event.OpenScreen(route)) }
7377
.launchIn(viewModelScope)

apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.flipcash.app.cash.internal
22

33
import androidx.lifecycle.viewModelScope
4+
import com.flipcash.app.analytics.Analytics
5+
import com.flipcash.app.analytics.FlipcashAnalyticsService
46
import com.flipcash.app.core.AppRoute
57
import com.flipcash.app.core.bill.Bill
68
import com.flipcash.app.core.tokens.SwapPurpose
@@ -24,6 +26,7 @@ import com.getcode.opencode.model.financial.LocalFiat
2426
import com.getcode.opencode.model.financial.SendLimit
2527
import com.getcode.opencode.model.financial.TokenWithLocalizedBalance
2628
import com.getcode.opencode.model.financial.minus
29+
import com.getcode.opencode.model.financial.usdf
2730
import com.getcode.solana.keys.Mint
2831
import com.getcode.util.resources.ResourceHelper
2932
import com.getcode.view.BaseViewModel
@@ -55,6 +58,7 @@ internal class CashScreenViewModel @Inject constructor(
5558
private val verifiedFiatCalculator: VerifiedFiatCalculator,
5659
tokenCoordinator: TokenCoordinator,
5760
transactionController: TransactionOperations,
61+
analytics: FlipcashAnalyticsService,
5862
dispatchers: DispatcherProvider,
5963
) : BaseViewModel<CashScreenViewModel.State, CashScreenViewModel.Event>(
6064
initialState = State(),
@@ -272,6 +276,9 @@ internal class CashScreenViewModel @Inject constructor(
272276
.onEach { shortfall ->
273277
// route directly to the swap amount screen, skipping token info
274278
val mint = stateFlow.value.selectedTokenAddress!!
279+
if (mint == Mint.usdf) {
280+
analytics.addMoneyOpened(Analytics.AddMoneySource.GiveShortfall)
281+
}
275282
dispatchEvent(
276283
Event.OpenScreen(
277284
AppRoute.Token.Swap(

apps/flipcash/features/cash/src/test/kotlin/com/flipcash/app/cash/internal/CashScreenViewModelTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.flipcash.app.cash.internal
22

33
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
4+
import com.flipcash.app.analytics.StubFlipcashAnalytics
45
import com.flipcash.app.core.MainCoroutineRule
56
import com.flipcash.app.core.dispatchers.TestDispatchers
67
import com.flipcash.app.tokens.TokenCoordinator
@@ -78,6 +79,7 @@ class CashScreenViewModelTest {
7879
verifiedFiatCalculator = verifiedFiatCalculator,
7980
tokenCoordinator = tokenCoordinator,
8081
transactionController = transactionController,
82+
analytics = StubFlipcashAnalytics(),
8183
dispatchers = dispatchers,
8284
)
8385
}

apps/flipcash/features/deposit/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ dependencies {
1111

1212
implementation(project(":libs:messaging"))
1313

14+
implementation(project(":apps:flipcash:shared:analytics"))
1415
implementation(project(":apps:flipcash:shared:featureflags"))
1516
implementation(project(":apps:flipcash:shared:tokens"))
1617
implementation(project(":services:flipcash"))

apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/internal/DepositViewModel.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.flipcash.app.deposit.internal
22

33
import android.content.ClipboardManager
44
import androidx.lifecycle.viewModelScope
5+
import com.flipcash.app.analytics.FlipcashAnalyticsService
56
import com.flipcash.app.core.extensions.onResult
67
import com.flipcash.app.core.extensions.setText
78
import com.flipcash.features.deposit.R
@@ -34,6 +35,7 @@ internal class DepositViewModel @Inject constructor(
3435
resources: ResourceHelper,
3536
dispatchers: DispatcherProvider,
3637
featureFlags: FeatureFlagController,
38+
analytics: FlipcashAnalyticsService,
3739
) : BaseViewModel<DepositViewModel.State, DepositViewModel.Event>(
3840
initialState = State(),
3941
updateStateForEvent = updateStateForEvent,
@@ -106,6 +108,9 @@ internal class DepositViewModel @Inject constructor(
106108
text = stateFlow.value.depositAddress,
107109
label = resources.getString(R.string.title_clipboardLabelDepositAddress),
108110
)
111+
stateFlow.value.selectedTokenAddress?.let { mint ->
112+
analytics.addMoneyAddressCopied(mint)
113+
}
109114
}
110115
.onEach {
111116
dispatchEvent(Event.SetCopied(true))

apps/flipcash/features/menu/src/main/kotlin/com/flipcash/app/menu/internal/MenuScreenViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.flipcash.app.menu.internal
22

33
import androidx.lifecycle.viewModelScope
4+
import com.flipcash.app.analytics.Analytics
5+
import com.flipcash.app.analytics.FlipcashAnalyticsService
46
import com.flipcash.app.auth.AuthManager
57
import com.flipcash.app.core.AppRoute
68
import com.flipcash.app.core.android.VersionInfo
@@ -53,6 +55,7 @@ internal class MenuScreenViewModel @Inject constructor(
5355
dispatchers: DispatcherProvider,
5456
releaseStageProvider: ReleaseStageProvider,
5557
purchaseMethodController: PurchaseMethodController,
58+
analytics: FlipcashAnalyticsService,
5659
) :
5760
BaseViewModel<MenuScreenViewModel.State, MenuScreenViewModel.Event>(
5861
initialState = State(),
@@ -165,6 +168,7 @@ internal class MenuScreenViewModel @Inject constructor(
165168
eventFlow
166169
.filterIsInstance<Event.PresentDepositOptions>()
167170
.mapNotNull {
171+
analytics.addMoneyOpened(Analytics.AddMoneySource.Menu)
168172
val depositFirstUx = featureFlags.get(FeatureFlag.AddMoneyUX)
169173
if (!depositFirstUx) {
170174
return@mapNotNull AppRoute.Transfers.Deposit()

apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/ChatViewModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ internal class ChatViewModel @Inject constructor(
535535
eventFlow
536536
.filterIsInstance<Event.PresentDepositOptions>()
537537
.onEach {
538+
analytics.addMoneyOpened(Analytics.AddMoneySource.Chat)
538539
purchaseMethodController.presentDepositOptions()?.let { route ->
539540
dispatchEvent(Event.OpenScreen(route))
540541
}

apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/Analytics.kt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ interface FlipcashAnalyticsService : AnalyticsService {
2626
fun addMoneyAmountConfirmed(method: Analytics.AddMoneyMethod, amount: Fiat)
2727
fun addMoneyPaymentInvoked(method: Analytics.AddMoneyMethod, amount: Fiat)
2828
fun addMoneyAddressCopied(mint: Mint)
29-
fun addMoneyCompleted(method: Analytics.AddMoneyMethod, amount: Fiat? = null)
30-
fun addMoneyFailed(method: Analytics.AddMoneyMethod, stage: Analytics.AddMoneyStage, error: Throwable? = null)
31-
fun addMoneyCancelled(method: Analytics.AddMoneyMethod?, stage: Analytics.AddMoneyStage)
29+
fun addMoney(method: Analytics.AddMoneyMethod, amount: Fiat? = null, successful: Boolean = true, error: Throwable? = null)
3230
fun connectWallet(provider: OnRampProvider.UsesDeeplinks)
3331
fun amountSelectedForWalletTransfer(provider: OnRampProvider.UsesDeeplinks, amount: Fiat)
3432
fun transactionSubmittedToWallet(provider: OnRampProvider.UsesDeeplinks)
@@ -68,9 +66,8 @@ object Analytics {
6866
data object SentCash : Transfer
6967
}
7068
enum class OnrampSource { Settings, Balance, Give }
71-
enum class AddMoneySource { Menu, GiveShortfall, BuyShortfall }
69+
enum class AddMoneySource { Menu, GiveShortfall, BuyShortfall, Chat, Scanner, Balance }
7270
enum class AddMoneyMethod { Coinbase, Phantom, OtherWallet, Reserves }
73-
enum class AddMoneyStage { MethodSelection, AmountEntry, Verification, Payment, Processing }
7471
enum class OnrampVerificationStep { ShowInfo, EnterPhone, ConfirmPhone, EnterEmail, ConfirmEmail }
7572
enum class OnrampPurchaseStep { PresetSelected, EnterCustomAmount, InvokePayment, InvokePaymentCustom, Completed }
7673
enum class TokenInfoSource { Deeplink, Wallet, Give }
@@ -105,9 +102,7 @@ class StubFlipcashAnalytics : FlipcashAnalyticsService {
105102
override fun addMoneyAmountConfirmed(method: Analytics.AddMoneyMethod, amount: Fiat) = Unit
106103
override fun addMoneyPaymentInvoked(method: Analytics.AddMoneyMethod, amount: Fiat) = Unit
107104
override fun addMoneyAddressCopied(mint: Mint) = Unit
108-
override fun addMoneyCompleted(method: Analytics.AddMoneyMethod, amount: Fiat?) = Unit
109-
override fun addMoneyFailed(method: Analytics.AddMoneyMethod, stage: Analytics.AddMoneyStage, error: Throwable?) = Unit
110-
override fun addMoneyCancelled(method: Analytics.AddMoneyMethod?, stage: Analytics.AddMoneyStage) = Unit
105+
override fun addMoney(method: Analytics.AddMoneyMethod, amount: Fiat?, successful: Boolean, error: Throwable?) = Unit
111106

112107
override fun connectWallet(provider: OnRampProvider.UsesDeeplinks) = Unit
113108
override fun amountSelectedForWalletTransfer(provider: OnRampProvider.UsesDeeplinks, amount: Fiat) = Unit

apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/Events.kt

Lines changed: 6 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -280,39 +280,9 @@ internal sealed interface AnalyticsEvent {
280280
override fun toProperties() = mapOf("Mint" to mint.base58())
281281
}
282282

283-
data class Completed(
284-
val method: Analytics.AddMoneyMethod,
285-
val amount: Fiat?,
286-
) : AddMoneyEvent {
287-
override val name = "Add Money: Completed"
288-
override fun toProperties() = buildMap {
289-
put("Method", method.propertyValue)
290-
amount?.let { putAll(it.asProperties()) }
291-
}
292-
}
293-
294-
data class Failed(
295-
val method: Analytics.AddMoneyMethod,
296-
val stage: Analytics.AddMoneyStage,
297-
val error: Throwable?,
298-
) : AddMoneyEvent {
299-
override val name = "Add Money: Failed"
300-
override fun toProperties() = buildMap {
301-
put("Method", method.propertyValue)
302-
put("Stage", stage.propertyValue)
303-
error?.let { put("Error", it.message.orEmpty()) }
304-
}
305-
}
306-
307-
data class Cancelled(
308-
val method: Analytics.AddMoneyMethod?,
309-
val stage: Analytics.AddMoneyStage,
310-
) : AddMoneyEvent {
311-
override val name = "Add Money: Cancelled"
312-
override fun toProperties() = buildMap {
313-
method?.let { put("Method", it.propertyValue) }
314-
put("Stage", stage.propertyValue)
315-
}
283+
data class Terminal(val method: Analytics.AddMoneyMethod) : AddMoneyEvent {
284+
override val name = "Add Money"
285+
override fun toProperties() = mapOf("Method" to method.propertyValue)
316286
}
317287
}
318288

@@ -376,6 +346,9 @@ internal val Analytics.AddMoneySource.propertyValue: String
376346
Analytics.AddMoneySource.Menu -> "Menu"
377347
Analytics.AddMoneySource.GiveShortfall -> "Give Shortfall"
378348
Analytics.AddMoneySource.BuyShortfall -> "Buy Shortfall"
349+
Analytics.AddMoneySource.Chat -> "Chat"
350+
Analytics.AddMoneySource.Scanner -> "Scanner"
351+
Analytics.AddMoneySource.Balance -> "Balance"
379352
}
380353

381354
internal val Analytics.AddMoneyMethod.propertyValue: String
@@ -386,15 +359,6 @@ internal val Analytics.AddMoneyMethod.propertyValue: String
386359
Analytics.AddMoneyMethod.Reserves -> "Reserves"
387360
}
388361

389-
internal val Analytics.AddMoneyStage.propertyValue: String
390-
get() = when (this) {
391-
Analytics.AddMoneyStage.MethodSelection -> "Method Selection"
392-
Analytics.AddMoneyStage.AmountEntry -> "Amount Entry"
393-
Analytics.AddMoneyStage.Verification -> "Verification"
394-
Analytics.AddMoneyStage.Payment -> "Payment"
395-
Analytics.AddMoneyStage.Processing -> "Processing"
396-
}
397-
398362
internal fun LocalFiat.asProperties(): Map<String, String> {
399363
return buildMap {
400364
putAll(underlyingTokenAmount.asProperties())

apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/internal/MixpanelAnalyticsDelegate.kt

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -160,23 +160,18 @@ internal class MixpanelAnalyticsDelegate @Inject constructor(
160160
track(AnalyticsEvent.AddMoneyEvent.AddressCopied(mint))
161161
}
162162

163-
override fun addMoneyCompleted(method: Analytics.AddMoneyMethod, amount: Fiat?) {
164-
track(AnalyticsEvent.AddMoneyEvent.Completed(method, amount))
165-
}
166-
167-
override fun addMoneyFailed(
163+
override fun addMoney(
168164
method: Analytics.AddMoneyMethod,
169-
stage: Analytics.AddMoneyStage,
165+
amount: Fiat?,
166+
successful: Boolean,
170167
error: Throwable?
171168
) {
172-
track(AnalyticsEvent.AddMoneyEvent.Failed(method, stage, error))
173-
}
174-
175-
override fun addMoneyCancelled(
176-
method: Analytics.AddMoneyMethod?,
177-
stage: Analytics.AddMoneyStage
178-
) {
179-
track(AnalyticsEvent.AddMoneyEvent.Cancelled(method, stage))
169+
track(
170+
AnalyticsEvent.AddMoneyEvent.Terminal(method),
171+
"State" to if (successful) "Success" else "Failure",
172+
*amount?.asProperties()?.toList()?.toTypedArray() ?: emptyArray(),
173+
*error.asProperty()
174+
)
180175
}
181176

182177
override fun connectWallet(provider: OnRampProvider.UsesDeeplinks) {

0 commit comments

Comments
 (0)