Documentation Index Fetch the complete documentation index at: https://mintlify.com/sovranBitcoin/sovran/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Sovran supports deep linking through custom URL schemes to enable seamless token imports from other apps, websites, and QR codes. The app registers two URL schemes: cashu:// and sovran://.
Deep links are automatically processed when the app is opened from a URL, even if the app wasn’t running.
Supported Schemes
Sovran registers the following URL schemes:
// From app.json:7
"scheme" : [ "sovran" , "cashu" ]
cashu://
Standard Cashu protocol scheme for ecash tokens:
cashu://cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6IjAwOWExZjI5MzI1M...
sovran://
Sovran-specific scheme for app-specific deep links:
sovran://cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6IjAwOWExZjI5MzI1M...
Implementation
Hook Setup
The deep link handler is implemented as a React hook:
// From hooks/useDeeplink.ts:1-47
import { useEffect } from 'react' ;
import * as Linking from 'expo-linking' ;
import { useMintStore } from '../stores/mintStore' ;
import { useNostrKeysContext } from 'providers/NostrKeysProvider' ;
import { deeplinkFailedPopup } from '@/helper/popup' ;
import { useProcessPaymentString } from './coco/useProcessPaymentString' ;
export const useDeeplink = () => {
const { keys } = useNostrKeysContext ();
const selectedMints = useMintStore (( state ) => state . selectedMints );
const selectedMint = keys ?. pubkey ? selectedMints [ keys . pubkey ] : undefined ;
const url = Linking . useURL ();
const { processPaymentString } = useProcessPaymentString ({
unit: 'sat' ,
selectedMint ,
isFocused: true ,
});
useEffect (() => {
if ( ! url || ! keys ?. pubkey ) return ;
( async () => {
const parsed = Linking . parse ( url );
const { scheme , hostname } = parsed ;
// Only process cashu:// or sovran:// schemes
const isOurScheme = scheme === 'cashu' || scheme === 'sovran' ;
if ( ! isOurScheme ) return ;
// Skip router-handled URLs (e.g., sovran://camera)
const isRouterHandled = hostname === 'camera' ;
if ( isRouterHandled ) return ;
// Process valid deep link
const isValidHost = hostname !== null && hostname !== 'expo-development-client' ;
if ( isValidHost ) {
try {
await processPaymentString ({ data: hostname , type: 'deeplink' });
} catch ( error ) {
deeplinkFailedPopup ({
text: error instanceof Error ? error . message : undefined
});
}
}
})();
}, [ url , keys ?. pubkey , selectedMint , processPaymentString ]);
};
URL Structure
Deep links follow this structure:
scheme://token_data
├── scheme: "cashu" | "sovran"
└── hostname: base64-encoded token string
Processing Flow
URL Detection
Expo’s Linking.useURL() hook detects when app is opened from URL
Scheme Validation
Check if scheme matches cashu:// or sovran://
Route Exclusion
Skip router-handled URLs like sovran://camera
Token Extraction
Extract token data from URL hostname
Processing
Pass to processPaymentString for validation and import
Error Handling
Show user-friendly popup on failure
URL Parsing
Expo Linking parses URLs into structured data:
const parsed = Linking . parse ( 'cashu://cashuAeyJ0b2tlbiI...' );
// Result:
{
scheme : 'cashu' ,
hostname : 'cashuAeyJ0b2tlbiI...' ,
path : null ,
queryParams : {}
}
Integration with Payment Processing
Deep links use the same payment string processor as QR codes and manual input:
interface ProcessPaymentStringParams {
data : string ; // Token string
type : 'deeplink' | 'qr' | 'manual' ;
}
// From hooks/coco/useProcessPaymentString
const { processPaymentString } = useProcessPaymentString ({
unit: 'sat' ,
selectedMint ,
isFocused: true ,
});
await processPaymentString ({
data: hostname , // Token data from URL
type: 'deeplink' // Source type
});
iOS
URL schemes are registered in Info.plist via app.json:
// From app.json:7
"scheme" : [ "sovran" , "cashu" ]
This configures:
CFBundleURLTypes in Info.plist
App can be opened from Safari, Messages, etc.
Universal links support (optional)
Android
Intent filters are automatically configured:
<!-- Generated from app.json -->
< intent-filter >
< action android:name = "android.intent.action.VIEW" />
< category android:name = "android.intent.category.DEFAULT" />
< category android:name = "android.intent.category.BROWSABLE" />
< data android:scheme = "cashu" />
< data android:scheme = "sovran" />
</ intent-filter >
Error Handling
Errors during deep link processing show a user-friendly popup:
// From helper/popup.ts
export function deeplinkFailedPopup ({ text } : { text ?: string }) {
showPopup ({
title: 'Link Failed' ,
message: text || 'Unable to process this link. Please try again.' ,
buttons: [{ text: 'OK' , style: 'default' }]
});
}
Common error scenarios:
Invalid token format
Unsupported token version
Network error contacting mint
User wallet not initialized
Testing Deep Links
iOS Simulator
xcrun simctl openurl booted "cashu://cashuAeyJ0b2tlbiI..."
Android Emulator
adb shell am start -W -a android.intent.action.VIEW -d "cashu://cashuAeyJ0b2tlbiI..."
Physical Device
Create test QR codes or share links via Messages/WhatsApp.
Router-Specific Links
Some URLs are handled by the router instead of payment processing:
// Example: Camera deep link
sovran : //camera
// Check in hook:
const isRouterHandled = hostname === 'camera' ;
if ( isRouterHandled ) return ; // Let router handle it
Router-handled URLs:
sovran://camera - Open camera screen
Future: sovran://settings, sovran://backup, etc.
Security Considerations
Deep links can be triggered by any app or website. Always validate token data before processing.
Validation Steps
Scheme Check : Only process cashu:// and sovran://
Format Validation : Ensure token matches expected format
User Confirmation : Show preview before importing large amounts
Mint Verification : Check mint is trusted before accepting tokens
// Example validation in processPaymentString
if ( ! isValidCashuToken ( data )) {
throw new Error ( 'Invalid token format' );
}
if ( amount > LARGE_AMOUNT_THRESHOLD ) {
const confirmed = await confirmImport ( amount );
if ( ! confirmed ) return ;
}
Best Practices
Immediate Feedback : Show processing indicator when link opens app
Clear Errors : Provide specific error messages for different failure modes
Confirmation : Confirm import before adding tokens to wallet
State Restoration : Handle links even when app is backgrounded
Validation First : Validate token format before attempting import
Network Handling : Gracefully handle offline/network errors
User Guidance : Suggest actions when link processing fails
Logging : Log errors for debugging without exposing sensitive data
Input Validation : Never trust deep link data without validation
Rate Limiting : Prevent abuse by limiting processing frequency
User Awareness : Show source/amount before importing
Safe Defaults : Default to safest option when uncertain
URL Generation
To create deep links for sharing tokens:
// Example: Generate shareable link
function generateDeepLink ( token : string ) : string {
// cashu:// is the standard protocol
return `cashu:// ${ token } ` ;
}
// Example usage
const token = await wallet . send ({ amount: 1000 , mint });
const deepLink = generateDeepLink ( token );
// Share via native share sheet
await Share . share ({
message: deepLink ,
title: 'Receive Bitcoin' ,
});
Advanced Features
Query Parameters
Deep links can include query parameters for additional context:
sovran://token?amount=1000&memo=Coffee
const parsed = Linking . parse ( url );
const { amount , memo } = parsed . queryParams ;
Universal Links (iOS)
For better UX, configure universal links (HTTPS URLs that open app):
// app.json - iOS associated domains
"ios" : {
"associatedDomains" : [ "applinks:sovran.money" ]
}
This allows URLs like https://sovran.money/receive/token123 to open the app.
Code Reference
Source Files
hooks/useDeeplink.ts:1-47 - Main deep link handler
app.json:7 - URL scheme registration
hooks/coco/useProcessPaymentString - Payment string processing
helper/popup.ts - Error popup utilities
Key Functions
useDeeplink() - Hook for handling deep links
Linking.useURL() - React hook for URL detection
Linking.parse(url) - Parse URL into components
processPaymentString({ data, type }) - Process token data
deeplinkFailedPopup({ text }) - Show error to user