Documentation Index
Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
Use this file to discover all available pages before exploring further.
Overview
This guide covers how wallets are created and managed with the Dynamic Swift SDK. Wallets are automatically created for users after authentication when embedded wallets are enabled in your Dynamic dashboard.
Prerequisites
Automatic Wallet Creation
When embedded wallets are enabled, wallets are automatically created for users after they authenticate. You don’t need to manually create wallets - the SDK handles this for you.
Listening for Wallet Creation
Use the userWalletsChanges publisher to know when wallets are ready:
import DynamicSDKSwift
import Combine
@MainActor
class WalletManager: ObservableObject {
@Published var wallets: [BaseWallet] = []
@Published var isCreatingWallets = false
private let sdk = DynamicSDK.instance()
private var cancellables = Set<AnyCancellable>()
func startListening() {
// Get current wallets
wallets = sdk.wallets.userWallets
// If user is authenticated but no wallets yet, they're being created
if sdk.auth.authenticatedUser != nil && wallets.isEmpty {
isCreatingWallets = true
}
// Listen for wallet updates
sdk.wallets.userWalletsChanges
.receive(on: DispatchQueue.main)
.sink { [weak self] newWallets in
guard let self else { return }
self.wallets = newWallets
if !newWallets.isEmpty {
self.isCreatingWallets = false
}
}
.store(in: &cancellables)
}
}
Displaying Wallets
import SwiftUI
import DynamicSDKSwift
struct WalletsView: View {
@StateObject private var walletManager = WalletManager()
var body: some View {
VStack {
if walletManager.isCreatingWallets {
HStack {
ProgressView()
Text("Creating wallets...")
}
} else if walletManager.wallets.isEmpty {
Text("No wallets available")
} else {
ForEach(walletManager.wallets, id: \.address) { wallet in
WalletCard(wallet: wallet)
}
}
}
.onAppear {
walletManager.startListening()
}
}
}
struct WalletCard: View {
let wallet: BaseWallet
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text(wallet.walletName ?? "Wallet")
.font(.headline)
Spacer()
Text(wallet.chain.uppercased())
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.blue.opacity(0.2))
.cornerRadius(4)
}
Text(wallet.address)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(1)
.truncationMode(.middle)
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(12)
}
}
Wallet Types
The SDK supports multiple wallet types:
EVM Wallets
For Ethereum and EVM-compatible chains (Polygon, Base, Arbitrum, etc.):
let evmWallets = sdk.wallets.userWallets.filter {
$0.chain.uppercased() == "EVM"
}
Solana Wallets
For Solana blockchain:
let solanaWallets = sdk.wallets.userWallets.filter {
$0.chain.uppercased() == "SOL"
}
Wallet Properties
Each BaseWallet has the following properties:
let wallet: BaseWallet
// Wallet address
let address = wallet.address // "0x..." for EVM, base58 for Solana
// Chain type
let chain = wallet.chain // "EVM" or "SOL"
// Wallet name (optional)
let name = wallet.walletName // e.g., "turnkey"
// Wallet ID (for API operations)
let id = wallet.id // Used for setPrimary, etc.
Setting Primary Wallet
You can set a wallet as the user’s primary wallet:
import DynamicSDKSwift
let sdk = DynamicSDK.instance()
func setPrimaryWallet(wallet: BaseWallet) async {
guard let walletId = wallet.id else {
print("Wallet has no ID")
return
}
do {
try await sdk.wallets.setPrimary(walletId: walletId)
print("Primary wallet set successfully")
} catch {
print("Failed to set primary wallet: \(error)")
}
}
Multi-Chain Support
When you have both EVM and Solana enabled in your dashboard, users will automatically get wallets for both chains:
import SwiftUI
import DynamicSDKSwift
struct MultiChainWalletsView: View {
let wallets: [BaseWallet]
var evmWallets: [BaseWallet] {
wallets.filter { $0.chain.uppercased() == "EVM" }
}
var solanaWallets: [BaseWallet] {
wallets.filter { $0.chain.uppercased() == "SOL" }
}
var body: some View {
List {
if !evmWallets.isEmpty {
Section("EVM Wallets") {
ForEach(evmWallets, id: \.address) { wallet in
WalletRow(wallet: wallet)
}
}
}
if !solanaWallets.isEmpty {
Section("Solana Wallets") {
ForEach(solanaWallets, id: \.address) { wallet in
WalletRow(wallet: wallet)
}
}
}
}
}
}
struct WalletRow: View {
let wallet: BaseWallet
var body: some View {
VStack(alignment: .leading) {
Text(wallet.walletName ?? "Wallet")
.font(.headline)
Text(wallet.address)
.font(.caption)
.foregroundColor(.secondary)
}
}
}
Complete Example with Timeout Handling
Handle cases where wallet creation might take time or fail:
@MainActor
class WalletManager: ObservableObject {
@Published var wallets: [BaseWallet] = []
@Published var isCreatingWallets = false
@Published var creationTimedOut = false
private let sdk = DynamicSDK.instance()
private var cancellables = Set<AnyCancellable>()
private var timeoutTask: Task<Void, Never>?
func startListening() {
wallets = sdk.wallets.userWallets
if sdk.auth.authenticatedUser != nil && wallets.isEmpty {
isCreatingWallets = true
// Set a timeout for wallet creation
timeoutTask = Task {
try? await Task.sleep(nanoseconds: 15_000_000_000) // 15 seconds
if wallets.isEmpty {
isCreatingWallets = false
creationTimedOut = true
}
}
}
sdk.wallets.userWalletsChanges
.receive(on: DispatchQueue.main)
.sink { [weak self] newWallets in
guard let self else { return }
self.wallets = newWallets
if !newWallets.isEmpty {
self.isCreatingWallets = false
self.creationTimedOut = false
self.timeoutTask?.cancel()
}
}
.store(in: &cancellables)
}
}
Next Steps
After wallets are created, you can: