@3land/capacitor-wallet-extension
v0.0.13
Published
Capacitor 7 plugin for connecting Solana wallets through native iOS/Android wallets or external wallet deeplinks.
Maintainers
Readme
@3land/capacitor-wallet-extension
Capacitor 7 plugin for Solana wallet connectivity in a Capacitor app.
It exposes ten public methods:
configureExternalWalletUrls({ appUrl, redirectBaseUrl })getAvailableWallets()connectUsing({ walletType })signMessage({ message })signTransactions({ transactions })getWalletMnemonics()recoverWalletFromMnemonics({ mnemonics })hasWalletBeenBackedUp()retryBackUp()logout()
The plugin supports:
icloud: native wallet generated and stored through iCloud Keychainandroid: native wallet generated locally, stored encrypted with Android Keystore, and recovered from Block Store when availablephantom: external wallet via deeplinksolflare: external wallet via deeplinkbackpack: external wallet via deeplink
No web implementation is included.
Install
npm install @3land/capacitor-wallet-extension
npx cap synciOS Setup
External wallet flows need your app to have a custom URL scheme so the wallet can redirect back into your Capacitor app.
Installed-wallet detection also needs LSApplicationQueriesSchemes, because getAvailableWallets() now checks the device for wallet apps and only returns the ones that are actually available, plus icloud.
Add a URL type in ios/App/App/Info.plist if you do not already have one:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.yourcompany.yourapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.yourcompany.yourapp</string>
</array>
</dict>
</array>The plugin uses the first registered URL scheme it finds and builds its redirect links from that.
Add wallet query schemes too:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>phantom</string>
<string>solflare</string>
<string>backpack</string>
</array>These query schemes are only used for installed-app detection.
The actual connect and signing requests still use the wallets' documented https://.../ul/v1/... deeplink endpoints.
If you want the wallet callback to come back through an https://... Universal Link instead of the default custom scheme, call configureExternalWalletUrls(...) before connectUsing(...) and configure the host app's Associated Domains for that HTTPS host.
Example Associated Domains entry:
applinks:gib.memeAndroid Setup
The Android implementation ships its own package-visibility queries for Phantom, Solflare, and Backpack, plus a redirect activity that listens on:
${applicationId}://wallet-extension/...So most Capacitor apps do not need any extra Android manifest changes just to use wallet discovery or deeplink callbacks.
If you call configureExternalWalletUrls(...) with an https://... redirectBaseUrl, Android also needs an App Links intent filter in your app so that HTTPS callback host opens your Capacitor activity. The plugin can build and validate the HTTPS callback URL, but Android still needs your app manifest and Digital Asset Links setup to claim that host.
Example host-app intent filter:
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="gib.meme" />
</intent-filter>If your redirectBaseUrl includes a path prefix such as https://gib.meme/wallet-extension, add the matching android:pathPrefix.
The native android wallet uses two storage layers:
- Android Keystore-backed encrypted local storage for day-to-day use
- Google Block Store as the recovery channel when the app is reinstalled or restored and the local keystore copy is gone
For silent recovery after uninstall/reinstall, the device still needs:
- Google Play services
- Android Backup enabled for the user/device restore flow
If Block Store is unavailable, the wallet still works locally on that device, but cross-reinstall recovery is not guaranteed. If backup is temporarily unavailable when the wallet is first created, the plugin now retries the Block Store sync on later wallet loads.
To verify the Android backup flow during development:
- Confirm
Settings > Google > Backupis enabled on the device. - Create the
androidwallet, fully close the app, reopen it once so any retry can run, then uninstall and reinstall. - Watch Logcat for the
AndroidWalletStoretag. A successful backup logsBacked up the Android wallet to Block Store.and a successful restore logsRestored the Android wallet from Block Store.
The Android-only backup helper method exposes one signal:
hasWalletBeenBackedUp()reports whether this plugin has successfully stored the current Android wallet into Block Store on this device. It does not guarantee that a later cloud sync has already completed.retryBackUp()forces a fresh Block Store write for the current Android wallet and resolves immediately with whether that specific write succeeded.
Vue Usage
import { WalletExtension, type WalletType } from '@3land/capacitor-wallet-extension';
await WalletExtension.configureExternalWalletUrls({
appUrl: 'https://gib.meme/app',
redirectBaseUrl: 'https://gib.meme',
});
const { wallets } = await WalletExtension.getAvailableWallets();
const walletType: WalletType = wallets[0] ?? 'android';
const { publicKey } = await WalletExtension.connectUsing({ walletType });
const { signature } = await WalletExtension.signMessage({
message: '3vQB7B6MrGQZaxCuFg4oh',
});
const { transactions } = await WalletExtension.signTransactions({
transactions: ['2M9n7m7yJtmY8Y9m6aXvP7hL9Xy7xG4vYvL1Y9EwPq...'],
});
const { mnemonics } = await WalletExtension.getWalletMnemonics();
const recovered = await WalletExtension.recoverWalletFromMnemonics({
mnemonics,
});
await WalletExtension.logout();API
configureExternalWalletUrls({ appUrl, redirectBaseUrl })
Overrides the URLs used for external wallet app_url and redirect_link parameters.
appUrlis used exactly as provided.redirectBaseUrlis used as the base for/connect,/sign-message, and/sign-transactions.- Call this before
connectUsing(...)when you want external wallets to return through an HTTPS Universal Link or App Link instead of the default custom scheme. - On iOS, configure Associated Domains for that HTTPS host.
- On Android, add an App Links intent filter for that HTTPS host in the host app and publish the matching
assetlinks.json.
Example:
await WalletExtension.configureExternalWalletUrls({
appUrl: 'https://gib.meme/app',
redirectBaseUrl: 'https://gib.meme',
});getAvailableWallets()
Returns:
{
wallets: ['android', 'phantom'];
}On iOS, this method always returns icloud.
On Android, this method always returns android.
External wallets are only returned when their apps are actually installed and queryable on the device.
If LSApplicationQueriesSchemes is missing from Info.plist, iOS can report those wallets as unavailable.
connectUsing({ walletType })
Connects the requested wallet type.
- If the same wallet type is already cached, the plugin returns the cached public key.
- If
walletType === 'icloud', the plugin creates the wallet on first use and stores it in iCloud Keychain. - If
walletType === 'android', the plugin creates the wallet on first use, stores it encrypted locally, and restores it from Block Store when local state is missing and a backup exists. - If
walletTypeis external, the plugin opens the matching wallet app using its deeplink flow. - If
walletTypeis external and the wallet app is not installed, the call rejects.
Returns:
{
publicKey: string;
walletType: 'icloud' | 'android' | 'phantom' | 'solflare' | 'backpack';
cached: boolean;
}signMessage({ message })
Signs a message and returns a base58 signature string.
messagemust be a base58-encoded byte payload.- The currently connected wallet is used.
Returns:
{
signature: string;
walletType: 'icloud' | 'android' | 'phantom' | 'solflare' | 'backpack';
}signTransactions({ transactions })
Signs one or more base58-encoded serialized Solana transactions.
- For
icloudandandroid, the plugin signs each serialized transaction locally and returns the updated serialized transactions. - For external wallets, the plugin uses the wallet's
signAllTransactionsdeeplink flow so all transactions are signed in one wallet prompt.
Returns:
{
transactions: string[];
walletType: 'icloud' | 'android' | 'phantom' | 'solflare' | 'backpack';
}getWalletMnemonics()
Exports the platform-native wallet as a 24-word BIP39 recovery phrase.
- On iOS, this reads the
icloudwallet stored in iCloud Keychain. - On Android, this reads the native
androidwallet stored with Android Keystore and backed up through Block Store. - If the native wallet does not exist yet, the plugin creates it first and then returns its mnemonics.
- External wallets such as Phantom, Solflare, and Backpack are not exported by this method.
Returns:
{
mnemonics: string[];
}recoverWalletFromMnemonics({ mnemonics })
Recovers and overwrites the platform-native wallet from a previously exported 24-word BIP39 recovery phrase.
mnemonicscan be either a space-delimited string or an array of words.- On iOS, the recovered wallet overwrites the existing iCloud Keychain record.
- On Android, the recovered wallet overwrites the encrypted local record and then attempts to sync Block Store again.
- If the recovered native wallet is currently connected, the cached native session is updated to the recovered public key automatically.
Returns:
trueNotes:
falsemeans the phrase was invalid or the recovery flow could not finish successfully.- On Android,
falsecan also mean the local encrypted wallet was replaced but Block Store still failed to resync afterward. - This method only applies to the native
icloudorandroidwallet for the current platform.
logout()
Clears the remembered connected wallet session from memory and local cache.
It does not delete the native icloud or android wallet itself.
hasWalletBeenBackedUp()
Android only.
Returns:
{
backedUp: true,
}Notes:
truemeans the plugin successfully stored the wallet in Block Store.falsemeans the wallet either does not exist yet or the last Block Store write has not succeeded yet.
retryBackUp()
Android only.
Returns:
trueNotes:
truemeans the manual Block Store retry succeeded during that call.falsemeans there was no local Android wallet to back up, or the manual Block Store write failed.
