Skip to content

Commit 6e2d0a2

Browse files
committed
feat(onramp): migrate Phantom integration from manual deeplinks to SDK
Replace hand-rolled deeplink protocol (URL building, X25519 key exchange, NaCl encryption, callback activity) with phantom-connect-wallet SDK. - PhantomWalletConnector now drives connect and sign in-process - connectAndSign() handles the full ceremony; signAndSend() covers the deferred-amount path (Token.Info / CurrencyCreator) - Remove manual deeplink parsing, BoxKeyPair, and DeeplinkType routing - Update Hilt module to provide SDK-backed connector - Clean up manifest (callback activity, query intent) Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent afcfd5b commit 6e2d0a2

26 files changed

Lines changed: 240 additions & 789 deletions

File tree

apps/flipcash/app/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ dependencies {
153153
implementation(project(":apps:flipcash:shared:notifications"))
154154
implementation(project(":apps:flipcash:shared:onramp:coinbase"))
155155
implementation(project(":apps:flipcash:shared:onramp:deeplinks"))
156+
implementation(libs.phantom.connect) {
157+
exclude(group = "com.ionspin.kotlin", module = "multiplatform-crypto-libsodium-bindings-android-debug")
158+
}
156159
implementation(project(":apps:flipcash:shared:payments"))
157160
implementation(project(":apps:flipcash:shared:permissions"))
158161
implementation(project(":apps:flipcash:shared:phone"))

apps/flipcash/app/src/main/AndroidManifest.xml

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,6 @@
2828
<action android:name="android.intent.action.VIEW" />
2929
<data android:host="phantom.app" />
3030
</intent>
31-
32-
<intent>
33-
<action android:name="android.intent.action.VIEW" />
34-
<data android:host="backpack.app" />
35-
</intent>
36-
37-
<intent>
38-
<action android:name="android.intent.action.VIEW" />
39-
<data android:host="solflare.com" />
40-
</intent>
4131
</queries>
4232

4333
<application
@@ -82,8 +72,6 @@
8272
<data android:pathPattern="/login.*" />
8373
<data android:pathPattern="/token.*" />
8474
<data android:pathPattern="/verify.*" />
85-
<data android:pathPattern="/external/.*/connected" />
86-
<data android:pathPattern="/external/.*/signed" />
8775
</intent-filter>
8876

8977
<!-- send.flipcash.com App Links -->
@@ -160,29 +148,6 @@
160148
android:scheme="https" />
161149
</intent-filter>
162150

163-
<intent-filter android:autoVerify="true">
164-
<action android:name="android.intent.action.VIEW" />
165-
166-
<category android:name="android.intent.category.DEFAULT" />
167-
<category android:name="android.intent.category.BROWSABLE" />
168-
169-
<data
170-
android:host="app.flipcash.com"
171-
android:pathPattern="/external/.*/connected"
172-
android:scheme="https" />
173-
</intent-filter>
174-
175-
<intent-filter android:autoVerify="true">
176-
<action android:name="android.intent.action.VIEW" />
177-
178-
<category android:name="android.intent.category.DEFAULT" />
179-
<category android:name="android.intent.category.BROWSABLE" />
180-
181-
<data
182-
android:host="app.flipcash.com"
183-
android:pathPattern="/external/.*/signed"
184-
android:scheme="https" />
185-
</intent-filter>
186151

187152
<intent-filter android:autoVerify="true">
188153
<action android:name="android.intent.action.VIEW" />
@@ -232,6 +197,19 @@
232197
</intent-filter>
233198
</activity>
234199

200+
<activity
201+
android:name="dev.bmcreations.phantom.connect.wallet.PhantomWalletCallbackActivity"
202+
android:exported="true"
203+
android:launchMode="singleTask"
204+
android:theme="@android:style/Theme.Translucent.NoTitleBar">
205+
<intent-filter>
206+
<action android:name="android.intent.action.VIEW" />
207+
<category android:name="android.intent.category.DEFAULT" />
208+
<category android:name="android.intent.category.BROWSABLE" />
209+
<data android:scheme="codewallet" android:host="phantom-wallet-callback" />
210+
</intent-filter>
211+
</activity>
212+
235213
<!--
236214
Prompt Google Play services to install the backported photo picker module
237215
https://developer.android.com/training/data-storage/shared/photopicker#device-availability

apps/flipcash/app/src/main/kotlin/com/flipcash/app/FlipcashApp.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.flipcash.app.currency.PreferredCurrencyController
1616
import com.getcode.opencode.repositories.EventRepository
1717
import com.getcode.utils.ErrorUtils
1818
import com.getcode.utils.trace
19+
import dev.bmcreations.phantom.connect.PhantomSdk
1920
import dagger.hilt.android.HiltAndroidApp
2021
import io.reactivex.rxjava3.plugins.RxJavaPlugins
2122
import javax.inject.Inject
@@ -48,6 +49,7 @@ class FlipcashApp : Application(), Configuration.Provider, SingletonImageLoader.
4849
ErrorUtils.handleError(it)
4950
}
5051

52+
PhantomSdk.init(this)
5153
authManager.init()
5254

5355
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)

apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/App.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ import com.flipcash.app.internal.ui.navigation.decorators.rememberNavBlockingOve
4747
import com.flipcash.app.internal.ui.navigation.decorators.rememberNavMessagingEntryDecorator
4848
import com.flipcash.app.onramp.CoinbaseOnRampHandler
4949
import com.flipcash.app.onramp.ExternalWalletOnRampHandler
50-
import com.flipcash.app.onramp.LocalExternalWalletOnRampController
5150
import com.flipcash.app.router.LocalRouter
5251
import com.flipcash.app.session.LocalSessionController
5352
import com.flipcash.app.theme.FlipcashTheme
@@ -238,7 +237,6 @@ internal fun App(
238237
}
239238

240239
val emailCodeChannel = LocalEmailCodeChannel.current
241-
val externalWalletController = LocalExternalWalletOnRampController.current
242240
LaunchedEffect(deepLink) {
243241
val link = deepLink ?: return@LaunchedEffect
244242

@@ -267,10 +265,6 @@ internal fun App(
267265
}
268266
}
269267

270-
is DeeplinkAction.ExternalWallet -> externalWalletController.handleWalletDeeplink(
271-
action.type
272-
)
273-
274268
is DeeplinkAction.Login -> viewModel.handleLoginEntropy(
275269
action.entropy,
276270
onSwitchAccount = {

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/navigation/DeeplinkAction.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package com.flipcash.app.core.navigation
22

33
sealed interface DeeplinkAction {
44
data class Navigate(val routes: List<com.flipcash.app.core.AppRoute>) : DeeplinkAction
5-
data class ExternalWallet(val type: DeeplinkType) : DeeplinkAction
65
data class Login(val entropy: String) : DeeplinkAction
76
data class OpenCashLink(val entropy: String) : DeeplinkAction
87
data object None : DeeplinkAction

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/navigation/DeeplinkType.kt

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@ package com.flipcash.app.core.navigation
22

33
import android.net.Uri
44
import android.os.Parcelable
5-
import com.flipcash.app.core.onramp.deeplinks.WalletDeeplinkConnectionResult
6-
import com.flipcash.app.core.onramp.deeplinks.ExternalWalletDeeplinkError
7-
import com.flipcash.app.core.onramp.deeplinks.OnRampDeeplinkOrigin
8-
import com.flipcash.app.core.onramp.deeplinks.WalletDeeplinkSigningResult
9-
import com.getcode.ed25519.Ed25519
105
import com.getcode.solana.keys.Mint
11-
import com.getcode.vendor.Base58
126
import kotlinx.parcelize.Parcelize
137
import kotlinx.serialization.Serializable
148

@@ -21,24 +15,6 @@ sealed interface DeeplinkType: Parcelable {
2115

2216
@Serializable data class TokenInfo(val mint: Mint): DeeplinkType, Navigatable
2317

24-
sealed interface ExternalWalletStep {
25-
val origin: OnRampDeeplinkOrigin
26-
}
27-
28-
@Serializable
29-
data class ExternalWalletConnection(
30-
override val origin: OnRampDeeplinkOrigin,
31-
val result: WalletDeeplinkConnectionResult?,
32-
val error: ExternalWalletDeeplinkError? = null
33-
): DeeplinkType, ExternalWalletStep, Navigatable
34-
35-
@Serializable
36-
data class ExternalWalletSignedTransaction(
37-
override val origin: OnRampDeeplinkOrigin,
38-
val result: WalletDeeplinkSigningResult?,
39-
val error: ExternalWalletDeeplinkError? = null
40-
): DeeplinkType, ExternalWalletStep, Navigatable
41-
4218
@Serializable
4319
data class EmailVerification(
4420
val email: String,
@@ -88,4 +64,4 @@ sealed interface Key {
8864
}
8965

9066
private operator fun Regex.contains(text: String?): Boolean =
91-
text?.let { this.matches(it) } ?: false
67+
text?.let { this.matches(it) } ?: false
Lines changed: 0 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,7 @@
11
package com.flipcash.app.core.onramp.deeplinks
22

33
import android.os.Parcelable
4-
import com.flipcash.app.core.AppRoute
5-
import com.getcode.ed25519.Ed25519
6-
import com.getcode.opencode.model.core.ID
7-
import com.getcode.opencode.utils.base64
8-
import com.getcode.opencode.utils.base64UrlSafe
9-
import com.getcode.solana.keys.Mint
104
import com.getcode.solana.keys.PublicKey
11-
import com.getcode.solana.keys.base58
12-
import com.getcode.utils.base58
13-
import com.getcode.utils.decodeBase58
14-
import com.getcode.utils.decodeBase64
15-
import com.getcode.utils.serializer.ByteListAsBase64Serializer
165
import kotlinx.parcelize.Parcelize
176
import kotlinx.serialization.SerialName
187
import kotlinx.serialization.Serializable
@@ -31,101 +20,3 @@ data class ExternallySignedTransaction(
3120
@SerialName("transaction")
3221
val serializedTransaction: String,
3322
): Parcelable
34-
35-
@Serializable
36-
@Parcelize
37-
sealed class OnRampDeeplinkOrigin: Parcelable {
38-
@Serializable @Parcelize
39-
data object Menu : OnRampDeeplinkOrigin()
40-
41-
@Serializable @Parcelize
42-
data class Give(val tokenAddress: Mint?) : OnRampDeeplinkOrigin()
43-
44-
@Serializable @Parcelize
45-
data object Wallet: OnRampDeeplinkOrigin()
46-
47-
@Serializable @Parcelize
48-
data class TokenInfo(val mint: Mint): OnRampDeeplinkOrigin()
49-
50-
@Serializable @Parcelize
51-
data object Reserves: OnRampDeeplinkOrigin()
52-
53-
@Serializable @Parcelize
54-
data object CurrencyCreator: OnRampDeeplinkOrigin()
55-
56-
57-
fun forUri(): String {
58-
return when(this) {
59-
Menu -> "menu"
60-
is Give -> "give-${tokenAddress?.base58()?.base64UrlSafe}"
61-
Wallet -> "wallet"
62-
is TokenInfo -> "token-${mint.base58().base64UrlSafe}"
63-
Reserves -> "reserves"
64-
CurrencyCreator -> "currency-creator"
65-
}.lowercase()
66-
}
67-
68-
companion object Companion {
69-
fun fromRoute(route: AppRoute?): OnRampDeeplinkOrigin? {
70-
return when (route) {
71-
is AppRoute.Sheets.Menu -> Menu
72-
is AppRoute.Sheets.Give -> Give(route.mint)
73-
is AppRoute.Sheets.Wallet -> Wallet
74-
is AppRoute.Token.Info -> {
75-
if (route.mint == Mint.usdf) Reserves else TokenInfo(route.mint)
76-
}
77-
is AppRoute.Token.CurrencyCreator -> CurrencyCreator
78-
79-
else -> null
80-
}
81-
}
82-
83-
fun fromString(value: String?): OnRampDeeplinkOrigin? {
84-
return when {
85-
value == "menu" -> Menu
86-
value?.startsWith("give-") == true -> {
87-
val tokenAddress = value.removePrefix("give-").decodeBase64().base58
88-
val mint = runCatching {
89-
Mint(tokenAddress)
90-
}.getOrNull() ?: return null
91-
Give(mint)
92-
}
93-
value == "wallet" -> Wallet
94-
value == "reserves" -> Reserves
95-
value == "currency-creator" -> CurrencyCreator
96-
value?.startsWith("token-") == true -> {
97-
val mintString = value.removePrefix("token-").decodeBase64().base58
98-
val mint = runCatching {
99-
Mint(mintString)
100-
}.onFailure { it.printStackTrace() }.getOrNull() ?: return null
101-
102-
TokenInfo(mint)
103-
}
104-
105-
else -> return null
106-
}
107-
}
108-
}
109-
}
110-
111-
@Serializable
112-
@Parcelize
113-
data class WalletDeeplinkConnectionResult(
114-
@Serializable(with = ByteListAsBase64Serializer::class) val encryptionPublicKey: List<Byte>,
115-
@Serializable(with = ByteListAsBase64Serializer::class) val nonce: List<Byte>,
116-
@Serializable(with = ByteListAsBase64Serializer::class) val encryptedData: List<Byte>
117-
): Parcelable
118-
119-
@Serializable
120-
@Parcelize
121-
data class WalletDeeplinkSigningResult(
122-
@Serializable(with = ByteListAsBase64Serializer::class) val nonce: List<Byte>,
123-
@Serializable(with = ByteListAsBase64Serializer::class) val encryptedData: List<Byte>,
124-
): Parcelable
125-
126-
@Serializable
127-
@Parcelize
128-
data class ExternalWalletDeeplinkError(
129-
val errorCode: String,
130-
val errorMessage: String,
131-
): Parcelable

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/onramp/ui/ConfirmInWalletButtonLabel.kt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,9 @@ import androidx.compose.foundation.text.appendInlineContent
99
import androidx.compose.material.ButtonColors
1010
import androidx.compose.runtime.Composable
1111
import androidx.compose.runtime.getValue
12-
import androidx.compose.runtime.key
13-
import androidx.compose.runtime.remember
1412
import androidx.compose.runtime.rememberUpdatedState
1513
import androidx.compose.ui.Alignment
1614
import androidx.compose.ui.Modifier
17-
import androidx.compose.ui.graphics.Color
1815
import androidx.compose.ui.graphics.ColorFilter
1916
import androidx.compose.ui.res.painterResource
2017
import androidx.compose.ui.res.stringResource
@@ -36,9 +33,7 @@ fun buildExternalWalletButtonLabel(
3633
colors: ButtonColors = ButtonState.Filled.colors(),
3734
): AnnotatedButtonLabel {
3835
val (title, icon) = when (provider) {
39-
OnRampProvider.Backpack -> stringResource(R.string.label_backpack) to painterResource(R.drawable.ic_backpack_wallet)
4036
OnRampProvider.Phantom -> stringResource(R.string.label_phantom) to painterResource(R.drawable.ic_phantom_wallet)
41-
OnRampProvider.Solflare -> stringResource(R.string.label_solflare) to painterResource(R.drawable.ic_solflare_wallet)
4237
}
4338

4439
val currentIsEnabled by rememberUpdatedState(isEnabled)
@@ -74,4 +69,4 @@ fun buildExternalWalletButtonLabel(
7469
}
7570
)
7671
)
77-
}
72+
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,6 @@ internal sealed interface AnalyticsEvent {
157157
sealed interface WalletEvent : AnalyticsEvent {
158158
val provider: OnRampProvider.UsesDeeplinks
159159
val providerName get() = when (provider) {
160-
OnRampProvider.Backpack -> "Backpack"
161-
OnRampProvider.Solflare -> "Solflare"
162160
OnRampProvider.Phantom -> "Phantom"
163161
}
164162
}

apps/flipcash/shared/onramp/deeplinks/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ dependencies {
1212
testImplementation(project(":libs:test-utils"))
1313

1414
implementation(libs.bundles.kotlinx.serialization)
15+
implementation(libs.phantom.connect.wallet) {
16+
exclude(group = "com.ionspin.kotlin", module = "multiplatform-crypto-libsodium-bindings-android-debug")
17+
}
1518

1619
implementation(project(":apps:flipcash:shared:analytics"))
1720
implementation(project(":apps:flipcash:shared:userflags"))

0 commit comments

Comments
 (0)