Phantom Connect KMP SDK
Kotlin Multiplatform SDK for Phantom embedded wallets. Add social login (Google & Apple) and wallet operations to your Android and iOS apps with a single shared codebase.
Built from the official phantom-connect-sdk and designed to match the React Native SDK feature set. If you're migrating from the React Native SDK, see the API mapping below.
Features
Requirements
| Platform | Minimum Version |
|---|
| Android | API 24 (Android 7.0) |
| iOS | 16.0 |
|
Prerequisites
Create an app in Phantom Portal to get your appId. Configure your redirect URI (e.g. myapp://phantom-auth-callback).
Installation
Android (Gradle)
Add the SDK dependency to your module's build.gradle.kts:
dependencies {
implementation("com.phantom:phantom-connect:<version>")
}
iOS (Swift Package Manager)
Add the package via Xcode:
- File > Add Package Dependencies
- Enter the repository URL
- Import
PhantomConnectSDK in your Swift files
Quick Start
Android (Kotlin)
iOS (Swift)
Wallet Connector (Phantom App Deeplinks)
The optional phantom-connect-wallet module adds support for connecting and signing via the installed Phantom mobile app. All communication happens through encrypted deeplinks using the Phantom deeplink protocol. Solana only.
Android
val walletConnector = PhantomWalletConnector(
deeplinkLauncher = createDeeplinkLauncher(applicationContext),
appUrl = "https://your-app.example.com",
redirectUrl = "myapp://phantom-wallet-callback",
)
val sdk = PhantomSdk.create(
config = PhantomSdkConfig(),
oauthLauncher = createOAuthLauncher(this),
connectors = listOf(walletConnector),
)
iOS (Swift)
The wallet types are included in the PhantomConnectSDK package -- no separate dependency.
import PhantomConnectSDK
let connector = PhantomWalletConnector(
deeplinkLauncher: createDeeplinkLauncher(),
appUrl: "https://your-app.example.com",
redirectUrl: "myapp://phantom-wallet-callback"
)
let phantom = PhantomClient(
appId: "your-app-id",
redirectUri: "myapp://phantom-auth-callback",
connectors: [connector]
)
.onOpenURL { url in
IosDeeplinkLauncher.handleCallback(url: url)
}
When connectors are provided, the connect sheet automatically includes them — each connector's callToAction (default: "Continue with {displayName}") is used as the button label. You can also connect directly via sdk.connect(walletConnector) / phantom.connect(connector:).
For full details, see the wallet module README.
API Reference
Configuration
PhantomSdkConfig(
appId = "your-app-id",
redirectUri = "myapp://callback",
chains = listOf(Chain.Solana),
providers = AuthProvider.all,
network = Network.Mainnet,
persistSession = true,
logger = PhantomLogger { level, tag, message ->
Log.d(tag, "[] ")
},
)
Connection
Session
Solana Operations (sdk.solana)
Ethereum Operations (sdk.ethereum)
Theming
The connect sheet supports dark (default), light, and custom themes:
sdk.theme = ConnectSheetTheme.Dark
sdk.theme = ConnectSheetTheme.Light
sdk.theme = ConnectSheetTheme.Custom(
sheetBackground = 0xFF1A1A2EL,
optionBackground = 0xFF2A2A3CL,
accentColor = 0xFFAB9FF2L,
textPrimary = 0xFFFFFFFFL,
textSecondary = 0xFF9999AAL,
)
phantom.theme = .dark
phantom.theme = .light
phantom.theme = .custom(
sheetBackground: 0xFF1A1A2E,
optionBackground: 0xFF2A2A3C,
accentColor: 0xFFAB9FF2,
textPrimary: 0xFFFFFFFF,
textSecondary: 0xFF9999AA
)
Logging
Pass a logger to receive structured SDK diagnostics:
val config = PhantomSdkConfig(
logger = PhantomLogger { level, tag, message ->
Log.d("Phantom/$tag", "[$level] $message")
},
)
let phantom = PhantomClient(
appId: "your-app-id",
redirectUri: "myapp://phantom-auth-callback",
logger: { level, tag, message in
print("[\(level)] \(tag): \(message)")
}
)
Architecture
phantom-connect/ Core KMP library (commonMain, androidMain, iosMain)
commonMain/ Shared business logic, models, Compose UI
androidMain/ EncryptedSharedPreferences, Custom Tabs OAuth
iosMain/ Keychain storage, ASWebAuthenticationSession OAuth
phantom-connect-wallet/ Optional wallet connector module (deeplink protocol)
commonMain/ X25519 key exchange, NaCl encryption, URL building
androidMain/ Intent-based deeplink launching
iosMain/ UIApplication.openURL-based deeplink launching
Internal/ iOS SPM distribution (Swift wrapper + XCFramework)
sample-android/ Android sample app
sample-ios/ iOS sample app
Security
Testing
The SDK uses interfaces and fakes for testing -- no mocks:
Ed25519KeyStoreProvider -> FakeEd25519KeyStore
SessionStoreProvider -> FakeSessionStore
OAuthLauncher -> FakeOAuthLauncher
TimeProvider -> FakeTimeProvider
./gradlew :phantom-connect:allTests
./gradlew :phantom-connect:testDebugUnitTest
./gradlew :phantom-connect:iosSimulatorArm64Test
Sample Apps
Both sample apps demonstrate the full connect, sign, and disconnect flow. Each supports a mock build flavor/configuration for testing with a local WireMock server.
Android
./gradlew :sample-android:assembleProductionDebug
./gradlew :sample-android:assembleMockDebug
iOS
Open sample-ios/PhantomSample.xcodeproj in Xcode. Set the PHANTOM_MOCK environment variable to use the mock configuration.
Session Persistence
By default, sessions are persisted to platform-secure storage and restored automatically on the next app launch via getSession(). Sessions are maintained for seven days with automatic authenticator renewal.
To disable persistence (e.g. for kiosk apps or shared devices where users should connect fresh each launch):
val config = PhantomSdkConfig(
persistSession = false,
)
let phantom = PhantomClient(
persistSession: false
)
When persistSession is false, sessions are held in memory only. They work normally within a single app lifecycle but are lost on app restart.
React Native SDK Mapping
This SDK provides the same capabilities as the Phantom Connect React Native SDK for native Kotlin and Swift apps. Here's how the APIs correspond:
For full React Native SDK documentation, see docs.phantom.com.
Contributing
- Fork the repo
- Create a feature branch
- Make your changes with tests
- Run
./gradlew :phantom-connect:allTests to verify
- Open a pull request
License
MIT