Build custom deposit UIs with direct access to payment rails.
The Deposit API provides programmatic access to initiate USDC deposits to Solana addresses from various wallet providers. This API powers the prebuilt ZSDepositView under the hood, giving you complete control over your deposit UI and flow while we handle the complexities of wallet connections and transaction submission.All deposits are USDC on Solana - the system supports USDC deposits from multiple wallet providers including Phantom, Metamask, and Apple Pay.Use this API when you need:
Full control over the deposit UI and user experience
Custom form validation or business logic before initiating deposits
Integration with existing navigation flows and state management
The Deposit API offers both modern async/await and callback-based interfaces. The API is payment method agnostic—the same signature and behavior work consistently across Apple Pay, Phantom, and Metamask.
All async functions and callbacks run on the main actor by default, making it safe to directly update UI elements. Amounts use the USDC type for type safety.
import ZSKitZSDeposit.deposit( paymentMethod: .phantom, to: SolanaAddress("7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"), amount: USDC(10)) { result in switch result { case .success(let transaction): print("Transaction signature: \(transaction.signature)") showSuccessScreen(transaction) case .failure(let error): print("Deposit failed: \(error)") showErrorScreen(error) }}
All three payment methods use the same API and deposit USDC to the same Solana address:
Copy
let destination = SolanaAddress("7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU")// Phantom - transfer USDC from user's Phantom walletlet tx1 = try await ZSDeposit.deposit( paymentMethod: .phantom, to: destination, amount: USDC(10))// Metamask - USDC deposit from Metamasklet tx2 = try await ZSDeposit.deposit( paymentMethod: .metamask, to: destination, amount: USDC(25))// Apple Pay - USDC deposit via Apple Paylet tx3 = try await ZSDeposit.deposit( paymentMethod: .applePay, to: destination, amount: USDC(50))
All payment methods require the same Solana address type. There is no per-method address validation—if you have a valid SolanaAddress, it works with all payment methods.
enum DepositEvent { case submitted(signature: TransactionSignature) case confirmed(signature: TransactionSignature) case failed(error: ZSDepositError)}
Event
When it occurs
Use case
.submitted
Transaction submitted to Solana
Show loading UI with “Confirming…” message
.confirmed
Transaction confirmed on Solana
Update UI to show success (async function will return shortly)
Type-safe wrapper for Solana addresses. Validates base58 encoding at initialization.
Copy
// Create from string - throws if invalidlet address = try SolanaAddress("7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU")// Or use failable initializerif let address = SolanaAddress("7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU") { // Valid address}// Get string representationprint(address.base58String)
struct Transaction { let signature: TransactionSignature let status: TransactionStatus let amount: USDC let destinationAddress: SolanaAddress let timestamp: Date let paymentMethod: PaymentMethod}enum TransactionStatus { case pending case confirmed case failed}
Property
Type
Description
signature
TransactionSignature
Solana transaction signature. Use to look up transaction on explorers
status
TransactionStatus
Always .confirmed when returned from successful deposit
enum ZSDepositError: Error { case walletNotInstalled(PaymentMethod) case userCancelled case insufficientFunds(required: USDC, available: USDC?) case networkError(underlying: Error, isRetryable: Bool) case invalidAddress case transactionFailed(reason: FailureReason, signature: TransactionSignature?) case duplicateRequest(existingSignature: TransactionSignature)}
All errors include metadata for programmatic handling:
Copy
do { let transaction = try await ZSDeposit.deposit(...)} catch let error as ZSDepositError { switch error { case .walletNotInstalled(let method): showInstallPrompt(for: method) case .userCancelled: // User intentionally cancelled - just reset UI resetButton() case .insufficientFunds(required: let required, available: let available): showAlert(""" Insufficient funds. Required: \(required.formatted) Available: \(available?.formatted ?? "Unknown") """) case .networkError(let underlying, isRetryable: let canRetry): if canRetry { showRetryAlert(error: underlying) } else { showPermanentError(error: underlying) } case .invalidAddress: showAlert("Invalid Solana address") case .transactionFailed(reason: let reason, signature: let sig): showTransactionError(reason: reason, signature: sig) case .duplicateRequest(existingSignature: let sig): // Request was duplicate - return existing transaction showExistingTransaction(signature: sig) }}
enum FailureReason { case insufficientLamports case accountNotFound case invalidAccountData case programError(code: UInt32) case timeout case unknown(String)}
Before showing payment options, check which methods are available:
Copy
let capabilities = ZSDeposit.capabilities()// Show only available methods in UIlet availableMethods = capabilities.availableMethods// e.g., [.applePay, .phantom]// Check why a method is unavailableif let reason = capabilities.unavailableReasons[.metamask] { switch reason { case .notInstalled: // Show "Install Metamask" prompt case .unsupportedPlatform: // Don't show this option at all case .configurationError: // Contact support }}
struct DepositCapabilities { let availableMethods: [PaymentMethod] let unavailableReasons: [PaymentMethod: UnavailableReason]}enum UnavailableReason { case notInstalled case unsupportedPlatform case configurationError}
All payment methods use Solana addresses. Validate before creating deposits:
Copy
// Validation helperfunc validateAddress(_ input: String) -> Result<SolanaAddress, ValidationError> { do { let address = try SolanaAddress(input) return .success(address) } catch { return .failure(.invalidFormat) }}// Usage in UIfunc handleDepositTapped() { guard case .success(let address) = validateAddress(addressInput.text ?? "") else { showAlert("Invalid Solana address format") return } // All payment methods accept this address Task { let tx = try await ZSDeposit.deposit( paymentMethod: selectedMethod, to: address, amount: selectedAmount ) }}
Unlike some multi-chain systems, there are no per-payment-method address rules. If you have a valid SolanaAddress, it works with all payment methods (Phantom, Metamask, and Apple Pay).
Modern Swift code is dramatically simpler with async/await. Reserve callbacks only for compatibility with older iOS versions or when you need fine-grained control over threading.
Always validate addresses as Solana
There are no per-payment-method address rules. If you have a SolanaAddress, it works everywhere. This eliminates a whole class of bugs.
Use idempotency keys for money movement
Deposits are money movement. Double-taps, retries, and network issues happen. Always use idempotency keys in production.
Check capabilities before showing UI
Use ZSDeposit.capabilities() to determine which payment methods are available before rendering your UI. This prevents showing options that won’t work.
Handle errors with structured types
Use the structured error types (ZSDepositError) to provide specific, helpful error messages. Don’t just show error.localizedDescription.
Track deposit events for analytics
Use the onEvent callback to track deposit lifecycle events (submitted, confirmed, failed) for monitoring and debugging.
Prefer the prebuilt UI? Check out ZeroSettle Deposit View for a ready-made deposit screen that uses this API internally.