diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Core/SPV/SPVClient.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Core/SPV/SPVClient.swift index a2691a40843..9b25803bfa4 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Core/SPV/SPVClient.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Core/SPV/SPVClient.swift @@ -31,7 +31,7 @@ class SPVClient: @unchecked Sendable { return false }() - init(network: Network = DashSDKNetwork(rawValue: 1), dataDir: String?, startHeight: UInt32, eventHandlers: SPVEventHandlers? = nil) throws { + init(network: DashSDKNetwork = DashSDKNetwork(rawValue: 1), dataDir: String?, startHeight: UInt32, eventHandlers: SPVEventHandlers? = nil) throws { if swiftLoggingEnabled { let level = (ProcessInfo.processInfo.environment["SPV_LOG"] ?? "off") print("[SPV][Log] Initialized SPV logging level=\(level)") diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/DataContractParser.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/DataContractParser.swift deleted file mode 100644 index 96a53c2da2a..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/DataContractParser.swift +++ /dev/null @@ -1,606 +0,0 @@ -import Foundation -import SwiftData - -public struct DataContractParser { - - // MARK: - Parse Data Contract - public static func parseDataContract(contractData: [String: Any], contractId: Data, modelContext: ModelContext) throws { - print("🔵 Parsing data contract with ID: \(contractId.toBase58String())") - - // Parse tokens if present - if let tokens = contractData["tokens"] as? [String: Any] { - print("📦 Found \(tokens.count) tokens in contract") - try parseTokens(tokens: tokens, contractId: contractId, modelContext: modelContext) - } - - // Parse document types - if let documents = contractData["documents"] as? [String: Any] { - print("📄 Found \(documents.count) document types in contract") - try parseDocumentTypes(documentTypes: documents, contractId: contractId, modelContext: modelContext) - } else if let documentSchemas = contractData["documentSchemas"] as? [String: Any] { - // Some contracts use "documentSchemas" instead - print("📄 Found \(documentSchemas.count) document schemas in contract") - try parseDocumentTypes(documentTypes: documentSchemas, contractId: contractId, modelContext: modelContext) - } - - // Update contract metadata - if let existingContract = try? modelContext.fetch( - FetchDescriptor( - predicate: #Predicate { $0.id == contractId } - ) - ).first { - if let version = contractData["version"] as? Int { - existingContract.version = version - } - if let ownerIdString = contractData["ownerId"] as? String, - let ownerIdData = Data.identifier(fromBase58: ownerIdString) { - existingContract.ownerId = ownerIdData - } - - // Contract configuration - if let canBeDeleted = contractData["canBeDeleted"] as? Bool { - existingContract.canBeDeleted = canBeDeleted - } - if let readonly = contractData["readonly"] as? Bool { - existingContract.readonly = readonly - } - if let keepsHistory = contractData["keepsHistory"] as? Bool { - existingContract.keepsHistory = keepsHistory - } - if let schemaDefs = contractData["schemaDefs"] as? Int { - existingContract.schemaDefs = schemaDefs - } - - // Document defaults - if let documentsKeepHistoryContractDefault = contractData["documentsKeepHistoryContractDefault"] as? Bool { - existingContract.documentsKeepHistoryContractDefault = documentsKeepHistoryContractDefault - } - if let documentsMutableContractDefault = contractData["documentsMutableContractDefault"] as? Bool { - existingContract.documentsMutableContractDefault = documentsMutableContractDefault - } - if let documentsCanBeDeletedContractDefault = contractData["documentsCanBeDeletedContractDefault"] as? Bool { - existingContract.documentsCanBeDeletedContractDefault = documentsCanBeDeletedContractDefault - } - } - } - - // MARK: - Parse Tokens - private static func parseTokens(tokens: [String: Any], contractId: Data, modelContext: ModelContext) throws { - // First, get the contract - let descriptor = FetchDescriptor( - predicate: #Predicate { $0.id == contractId } - ) - guard let contract = try modelContext.fetch(descriptor).first else { - print("⚠️ Could not find contract to link tokens") - return - } - - for (positionKey, tokenData) in tokens { - guard let position = Int(positionKey), - let tokenDict = tokenData as? [String: Any] else { - print("⚠️ Skipping invalid token at position: \(positionKey)") - continue - } - - // Extract token name (might be in different places) - let tokenName = extractTokenName(from: tokenDict, position: position) - - // Extract base supply - let baseSupply = extractTokenSupply(from: tokenDict, key: "baseSupply") - print("📊 Token \(position) - Base Supply: \(baseSupply), raw value: \(tokenDict["baseSupply"] ?? "nil")") - - // Create persistent token - let token = PersistentToken( - contractId: contractId, - position: position, - name: tokenName, - baseSupply: baseSupply - ) - - // Parse and set all token properties - parseTokenConfiguration(token: token, from: tokenDict) - - // Link to contract - token.dataContract = contract - - modelContext.insert(token) - print("✅ Created token: \(tokenName) at position \(position)") - } - } - - // MARK: - Parse Document Types - private static func parseDocumentTypes(documentTypes: [String: Any], contractId: Data, modelContext: ModelContext) throws { - // First, get the contract - let descriptor = FetchDescriptor( - predicate: #Predicate { $0.id == contractId } - ) - guard let contract = try modelContext.fetch(descriptor).first else { - print("⚠️ Could not find contract to link document types") - return - } - - for (typeName, typeData) in documentTypes { - guard let typeDict = typeData as? [String: Any] else { - print("⚠️ Skipping invalid document type: \(typeName)") - continue - } - - // Extract schema - make sure we store the whole typeDict as schema - // and only properties as the properties field - let schemaJSON = try JSONSerialization.data(withJSONObject: typeDict, options: []) - - // Extract actual properties for the form - let properties = typeDict["properties"] as? [String: Any] ?? [:] - let propertiesJSON = try JSONSerialization.data(withJSONObject: properties, options: []) - - // Create document type - let docType = PersistentDocumentType( - contractId: contractId, - name: typeName, - schemaJSON: schemaJSON, - propertiesJSON: propertiesJSON - ) - - // Set document behavior - if let keepsHistory = typeDict["documentsKeepHistory"] as? Bool { - docType.documentsKeepHistory = keepsHistory - } - - if let mutable = typeDict["documentsMutable"] as? Bool { - docType.documentsMutable = mutable - } - - // The actual field name is just "canBeDeleted" not "documentsCanBeDeleted" - if let canDelete = typeDict["canBeDeleted"] as? Bool { - docType.documentsCanBeDeleted = canDelete - } - - // The actual field name is "transferable" and it can be an integer (0 = false, non-zero = true) - if let transferable = typeDict["transferable"] { - // Handle both boolean and integer values (0 = false, non-zero = true) - if let boolValue = transferable as? Bool { - docType.documentsTransferable = boolValue - } else if let intValue = transferable as? Int { - docType.documentsTransferable = intValue != 0 - } - } - - // Trade mode - can be integer or boolean - if let tradeMode = typeDict["tradeMode"] { - if let intValue = tradeMode as? Int { - docType.tradeMode = intValue - } else if let boolValue = tradeMode as? Bool { - docType.tradeMode = boolValue ? 1 : 0 - } - } - - // Creation restriction mode - if let creationRestrictionMode = typeDict["creationRestrictionMode"] as? Int { - docType.creationRestrictionMode = creationRestrictionMode - } - - // Identity encryption keys - if let requiresEncryption = typeDict["requiresIdentityEncryptionBoundedKey"] as? Bool { - docType.requiresIdentityEncryptionBoundedKey = requiresEncryption - } - - if let requiresDecryption = typeDict["requiresIdentityDecryptionBoundedKey"] as? Bool { - docType.requiresIdentityDecryptionBoundedKey = requiresDecryption - } - - // Extract required fields - if let required = typeDict["required"] as? [String] { - docType.requiredFieldsJSON = try? JSONSerialization.data(withJSONObject: required, options: []) - } - - // Security level - the field name in contracts is "signatureSecurityLevelRequirement" - if let securityLevel = typeDict["signatureSecurityLevelRequirement"] as? Int { - docType.securityLevel = securityLevel - } else if let securityLevel = typeDict["securityLevelRequirement"] as? Int { - // Fallback to old name for compatibility - docType.securityLevel = securityLevel - } else { - // Default to HIGH (value 2) as per DPP specification - docType.securityLevel = 2 - } - - // Link to contract - docType.dataContract = contract - - modelContext.insert(docType) - print("✅ Created document type: \(typeName)") - - // Parse indices - if let indices = typeDict["indices"] as? [[String: Any]] { - try parseIndices(indices: indices, contractId: contractId, documentTypeName: typeName, documentType: docType, modelContext: modelContext) - } - - // Parse properties into separate entities - if let properties = typeDict["properties"] as? [String: Any] { - try parseProperties(properties: properties, contractId: contractId, documentTypeName: typeName, documentType: docType, requiredFields: typeDict["required"] as? [String] ?? [], modelContext: modelContext) - } - } - } - - // MARK: - Parse Indices - private static func parseIndices(indices: [[String: Any]], contractId: Data, documentTypeName: String, documentType: PersistentDocumentType, modelContext: ModelContext) throws { - for indexData in indices { - guard let name = indexData["name"] as? String else { - print("⚠️ Skipping index without name") - continue - } - - // Extract properties array with sorting - let properties = indexData["properties"] as? [[String: Any]] ?? [] - var propertyNames: [String] = [] - - // Parse property names with their sort order - for prop in properties { - if let propName = prop.keys.first { - // Include sort order if not default "asc" - if let sortOrder = prop[propName] as? String, sortOrder != "asc" { - propertyNames.append("\(propName) (\(sortOrder))") - } else { - propertyNames.append(propName) - } - } - } - - // Create persistent index - let index = PersistentIndex( - contractId: contractId, - documentTypeName: documentTypeName, - name: name, - properties: propertyNames - ) - - // Set index attributes - if let unique = indexData["unique"] as? Bool { - index.unique = unique - } - - if let nullSearchable = indexData["nullSearchable"] as? Bool { - index.nullSearchable = nullSearchable - } - - // Handle contested - can be bool or object - if let contestedBool = indexData["contested"] as? Bool { - index.contested = contestedBool - } else if let contestedDict = indexData["contested"] as? [String: Any] { - index.contested = true - // Store contested details as JSON - if let contestedData = try? JSONSerialization.data(withJSONObject: contestedDict, options: []) { - index.contestedDetailsJSON = contestedData - } - } - - // Link to document type - index.documentType = documentType - - modelContext.insert(index) - print("✅ Created index: \(name) for document type: \(documentTypeName)") - } - } - - // MARK: - Parse Properties - private static func parseProperties(properties: [String: Any], contractId: Data, documentTypeName: String, documentType: PersistentDocumentType, requiredFields: [String], modelContext: ModelContext) throws { - for (propertyName, propertyData) in properties { - guard let propertyDict = propertyData as? [String: Any] else { - print("⚠️ Skipping invalid property: \(propertyName)") - continue - } - - // Extract type - let type = propertyDict["type"] as? String ?? "unknown" - - // Create persistent property - let property = PersistentProperty( - contractId: contractId, - documentTypeName: documentTypeName, - name: propertyName, - type: type - ) - - // Set property attributes - if let format = propertyDict["format"] as? String { - property.format = format - } - - if let contentMediaType = propertyDict["contentMediaType"] as? String { - property.contentMediaType = contentMediaType - } - - if let byteArray = propertyDict["byteArray"] as? Bool { - property.byteArray = byteArray - } - - if let minItems = propertyDict["minItems"] as? Int { - property.minItems = minItems - } - - if let maxItems = propertyDict["maxItems"] as? Int { - property.maxItems = maxItems - } - - if let pattern = propertyDict["pattern"] as? String { - property.pattern = pattern - } - - if let minLength = propertyDict["minLength"] as? Int { - property.minLength = minLength - } - - if let maxLength = propertyDict["maxLength"] as? Int { - property.maxLength = maxLength - } - - if let minValue = propertyDict["minValue"] as? Int { - property.minValue = minValue - } else if let minimum = propertyDict["minimum"] as? Int { - property.minValue = minimum - } - - if let maxValue = propertyDict["maxValue"] as? Int { - property.maxValue = maxValue - } else if let maximum = propertyDict["maximum"] as? Int { - property.maxValue = maximum - } - - if let description = propertyDict["description"] as? String { - property.fieldDescription = description - print(" 📝 Property \(propertyName) has description: \(description)") - } else { - print(" ⚠️ Property \(propertyName) has no description") - } - - if let transient = propertyDict["transient"] as? Bool { - property.transient = transient - } - - // Check if required - property.isRequired = requiredFields.contains(propertyName) - - // Link to document type - property.documentType = documentType - - modelContext.insert(property) - print("✅ Created property: \(propertyName) for document type: \(documentTypeName)") - } - } - - // MARK: - Helper Methods - private static func extractTokenName(from tokenDict: [String: Any], position: Int) -> String { - // Try different possible locations for the name - if let name = tokenDict["name"] as? String { return name } - if let conventions = tokenDict["conventions"] as? [String: Any], - let name = conventions["name"] as? String { return name } - if let description = tokenDict["description"] as? String { return description } - return "Token \(position)" - } - - private static func extractTokenSupply(from tokenDict: [String: Any], key: String) -> String { - // Handle different number formats - if let supplyInt = tokenDict[key] as? Int { - return String(supplyInt) - } - if let supplyDouble = tokenDict[key] as? Double { - return String(format: "%.0f", supplyDouble) - } - if let supplyString = tokenDict[key] as? String { - return supplyString - } - return "0" - } - - private static func parseTokenConfiguration(token: PersistentToken, from tokenDict: [String: Any]) { - // Basic properties - let maxSupplyStr = extractTokenSupply(from: tokenDict, key: "maxSupply") - if maxSupplyStr != "0" { - token.maxSupply = maxSupplyStr - } - - if let decimals = tokenDict["decimals"] as? Int { - token.decimals = decimals - } - - if let description = tokenDict["description"] as? String { - token.tokenDescription = description - } - - // Status flags - if let startAsPaused = tokenDict["startAsPaused"] as? Bool { - token.isPaused = startAsPaused - } - - if let allowTransfer = tokenDict["allowTransferToFrozenBalance"] as? Bool { - token.allowTransferToFrozenBalance = allowTransfer - } - - // Parse conventions/localizations - if let conventions = tokenDict["conventions"] as? [String: Any] { - if let decimals = conventions["decimals"] as? Int { - token.decimals = decimals - } - if let localizations = conventions["localizations"] as? [String: Any] { - var tokenLocalizations: [String: TokenLocalization] = [:] - for (langCode, locData) in localizations { - if let locDict = locData as? [String: Any] { - // Skip format version keys - if langCode == "$formatVersion" { continue } - - tokenLocalizations[langCode] = TokenLocalization( - singularForm: locDict["singular"] as? String ?? locDict["singularForm"] as? String ?? "", - pluralForm: locDict["plural"] as? String ?? locDict["pluralForm"] as? String ?? "", - description: locDict["description"] as? String - ) - } - } - token.localizations = tokenLocalizations - } - } - - // Parse history keeping rules - if let keepsHistory = tokenDict["keepsHistory"] as? [String: Any] { - token.keepsTransferHistory = keepsHistory["keepsTransferHistory"] as? Bool ?? true - token.keepsFreezingHistory = keepsHistory["keepsFreezingHistory"] as? Bool ?? true - token.keepsMintingHistory = keepsHistory["keepsMintingHistory"] as? Bool ?? true - token.keepsBurningHistory = keepsHistory["keepsBurningHistory"] as? Bool ?? true - token.keepsDirectPricingHistory = keepsHistory["keepsDirectPricingHistory"] as? Bool ?? true - token.keepsDirectPurchaseHistory = keepsHistory["keepsDirectPurchaseHistory"] as? Bool ?? true - } else if let keepsHistory = tokenDict["keepsHistory"] as? Bool { - // Simple boolean for all history - token.keepsTransferHistory = keepsHistory - token.keepsFreezingHistory = keepsHistory - token.keepsMintingHistory = keepsHistory - token.keepsBurningHistory = keepsHistory - token.keepsDirectPricingHistory = keepsHistory - token.keepsDirectPurchaseHistory = keepsHistory - } - - // Parse control rules - token.conventionsChangeRules = parseChangeControlRule(tokenDict["conventionsChangeRules"]) - token.maxSupplyChangeRules = parseChangeControlRule(tokenDict["maxSupplyChangeRules"]) - token.manualMintingRules = parseChangeControlRule(tokenDict["manualMintingRules"]) - token.manualBurningRules = parseChangeControlRule(tokenDict["manualBurningRules"]) - token.freezeRules = parseChangeControlRule(tokenDict["freezeRules"]) - token.unfreezeRules = parseChangeControlRule(tokenDict["unfreezeRules"]) - token.destroyFrozenFundsRules = parseChangeControlRule(tokenDict["destroyFrozenFundsRules"]) - token.emergencyActionRules = parseChangeControlRule(tokenDict["emergencyActionRules"]) - - // Parse distribution rules - if let distributionRules = tokenDict["distributionRules"] as? [String: Any] { - // Perpetual distribution - if let perpetual = distributionRules["perpetualDistribution"] as? [String: Any] { - var dist = TokenPerpetualDistribution() - if let distType = perpetual["distributionType"] { - // Convert to JSON string for storage - if let jsonData = try? JSONSerialization.data(withJSONObject: distType, options: []), - let jsonString = String(data: jsonData, encoding: .utf8) { - dist.distributionType = jsonString - } else { - dist.distributionType = "{}" - } - } - if let recipient = perpetual["distributionRecipient"] as? String { - dist.distributionRecipient = recipient - } - // Set enabled flag if it exists (defaults to true in init) - if let enabled = perpetual["enabled"] as? Bool { - dist.enabled = enabled - } else { - dist.enabled = true // Default to enabled if not specified - } - token.perpetualDistribution = dist - } - - // Pre-programmed distribution - if let preProgrammed = distributionRules["preProgrammedDistribution"] as? [String: Any] { - var dist = TokenPreProgrammedDistribution() - if let schedule = preProgrammed["distributionSchedule"] as? [[String: Any]] { - dist.distributionSchedule = schedule.compactMap { eventDict in - guard let amount = eventDict["amount"] as? String else { return nil } - var event = DistributionEvent( - triggerTime: Date(), - amount: amount - ) - if let triggerType = eventDict["triggerType"] as? String { - event.triggerType = triggerType - } - if let time = eventDict["triggerTime"] as? TimeInterval { - event.triggerTime = Date(timeIntervalSince1970: time) - } - if let block = eventDict["triggerBlock"] as? Int64 { - event.triggerBlock = block - } - if let condition = eventDict["triggerCondition"] as? String { - event.triggerCondition = condition - } - if let recipient = eventDict["recipient"] as? String { - event.recipient = recipient - } - if let desc = eventDict["description"] as? String { - event.description = desc - } - return event - } - } - token.preProgrammedDistribution = dist - } - - // New tokens destination - if let destinationId = distributionRules["newTokensDestinationIdentity"] as? String, - let destinationData = Data.identifier(fromBase58: destinationId) { - token.newTokensDestinationIdentity = destinationData - } - - // Minting destination choice - if let allowChoice = distributionRules["mintingAllowChoosingDestination"] as? Bool { - token.mintingAllowChoosingDestination = allowChoice - } - - // Store distribution change rules - var changeRules = TokenDistributionChangeRules() - changeRules.perpetualDistributionRules = parseChangeControlRule(distributionRules["perpetualDistributionRules"]) - changeRules.newTokensDestinationIdentityRules = parseChangeControlRule(distributionRules["newTokensDestinationIdentityRules"]) - changeRules.mintingAllowChoosingDestinationRules = parseChangeControlRule(distributionRules["mintingAllowChoosingDestinationRules"]) - changeRules.changeDirectPurchasePricingRules = parseChangeControlRule(distributionRules["changeDirectPurchasePricingRules"]) - token.distributionChangeRules = changeRules - } - - // Parse marketplace rules - if let marketplaceRules = tokenDict["marketplaceRules"] as? [String: Any] { - if let tradeModeStr = marketplaceRules["tradeMode"] as? String, - let tradeMode = TokenTradeMode(rawValue: tradeModeStr) { - token.tradeMode = tradeMode - } - token.tradeModeChangeRules = parseChangeControlRule(marketplaceRules["tradeModeChangeRules"]) - } - - // Main control group - if let mainControlGroup = tokenDict["mainControlGroup"] as? Int { - token.mainControlGroupPosition = mainControlGroup - } - - if let canModify = tokenDict["mainControlGroupCanBeModified"] as? String { - token.mainControlGroupCanBeModified = canModify - } - } - - private static func parseChangeControlRule(_ ruleData: Any?) -> ChangeControlRules? { - guard let ruleContainer = ruleData as? [String: Any] else { return nil } - - // Handle V0 format where the actual rules are nested under "V0" key - let rule: [String: Any] - if let v0Rules = ruleContainer["V0"] as? [String: Any] { - rule = v0Rules - } else { - // Fall back to direct format if not wrapped in V0 - rule = ruleContainer - } - - var controlRules = ChangeControlRules.mostRestrictive() - - // Handle both snake_case (from JSON) and camelCase - if let authorized = rule["authorized_to_make_change"] as? String ?? rule["authorizedToMakeChange"] as? String { - controlRules.authorizedToMakeChange = authorized - } - - if let admin = rule["admin_action_takers"] as? String ?? rule["adminActionTakers"] as? String { - controlRules.adminActionTakers = admin - } - - if let flag = rule["changing_authorized_action_takers_to_no_one_allowed"] as? Bool ?? rule["changingAuthorizedActionTakersToNoOneAllowed"] as? Bool { - controlRules.changingAuthorizedActionTakersToNoOneAllowed = flag - } - - if let flag = rule["changing_admin_action_takers_to_no_one_allowed"] as? Bool ?? rule["changingAdminActionTakersToNoOneAllowed"] as? Bool { - controlRules.changingAdminActionTakersToNoOneAllowed = flag - } - - if let flag = rule["self_changing_admin_action_takers_allowed"] as? Bool ?? rule["selfChangingAdminActionTakersAllowed"] as? Bool { - controlRules.selfChangingAdminActionTakersAllowed = flag - } - - return controlRules - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/ModelContainerHelper.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/ModelContainerHelper.swift index fb2a68fbbbe..c8c4736711b 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/ModelContainerHelper.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/ModelContainerHelper.swift @@ -15,7 +15,6 @@ public struct ModelContainerHelper { PersistentDataContract.self, PersistentToken.self, PersistentDocumentType.self, - PersistentTokenHistoryEvent.self, PersistentKeyword.self ]) diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift index c99e70a5d64..a8be50720e0 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift @@ -283,33 +283,6 @@ public class CoreWalletManager: ObservableObject { return wallet } - public func decryptSeed(_ encryptedSeed: Data?) -> Data? { - // This method is used internally by other services - // In a real implementation, this would decrypt using the current PIN - // For now, return nil to indicate manual unlock is needed - return nil - } - - public func changeWalletPIN(currentPIN: String, newPIN: String) async throws { - // Retrieve seed with current PIN - let seed = try storage.retrieveSeed(pin: currentPIN) - - // Re-encrypt with new PIN - _ = try storage.storeSeed(seed, pin: newPIN) - } - - public func enableBiometricProtection(pin: String) async throws { - // First verify PIN and get seed - let seed = try storage.retrieveSeed(pin: pin) - - // Enable biometric protection - try storage.enableBiometricProtection(for: seed) - } - - public func unlockWithBiometric() async throws -> Data { - return try storage.retrieveSeedWithBiometric() - } - // MARK: - Account Management /// Build a signed transaction diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/WalletStorage.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/WalletStorage.swift index 2ea9736c423..08bf7eed1e9 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/WalletStorage.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/WalletStorage.swift @@ -84,19 +84,6 @@ public class WalletStorage { return try decryptData(encryptedSeed, with: key) } - public func deleteSeed() throws { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: keychainService, - kSecAttrAccount as String: seedKeychainAccount - ] - - let status = SecItemDelete(query as CFDictionary) - guard status == errSecSuccess || status == errSecItemNotFound else { - throw WalletStorageError.keychainError(status) - } - } - // MARK: - PIN Management private func storePINHash(_ pin: String) throws { @@ -141,58 +128,6 @@ public class WalletStorage { return Data(hash) == storedHash } - // MARK: - Biometric Protection - - public func enableBiometricProtection(for seed: Data) throws { - // Create access control with biometric authentication - var error: Unmanaged? - guard let access = SecAccessControlCreateWithFlags( - nil, - kSecAttrAccessibleWhenUnlockedThisDeviceOnly, - .biometryCurrentSet, - &error - ) else { - throw WalletStorageError.biometricSetupFailed - } - - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: keychainService, - kSecAttrAccount as String: biometricKeychainAccount, - kSecValueData as String: seed, - kSecAttrAccessControl as String: access - ] - - SecItemDelete(query as CFDictionary) - - let status = SecItemAdd(query as CFDictionary, nil) - guard status == errSecSuccess else { - throw WalletStorageError.keychainError(status) - } - } - - public func retrieveSeedWithBiometric() throws -> Data { - let context = LAContext() - context.localizedReason = "Authenticate to access your wallet" - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: keychainService, - kSecAttrAccount as String: biometricKeychainAccount, - kSecReturnData as String: true, - kSecUseAuthenticationContext as String: context - ] - - var result: AnyObject? - let status = SecItemCopyMatching(query as CFDictionary, &result) - - guard status == errSecSuccess, - let seed = result as? Data else { - throw WalletStorageError.biometricAuthenticationFailed - } - - return seed - } - // MARK: - Encryption Helpers private func generateSalt() -> Data { diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPDataContract.swift b/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPDataContract.swift index 4142f8e6873..8cc85cda145 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPDataContract.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPDataContract.swift @@ -89,7 +89,6 @@ public struct DPPDataContract: Identifiable, Codable, Equatable, Sendable { public struct DocumentType: Codable, Equatable, Sendable { public let name: String - public let schema: JsonSchema public let indices: [Index] public let properties: [String: DocumentProperty] public let security: DocumentTypeSecurity @@ -112,7 +111,6 @@ public struct DocumentType: Codable, Equatable, Sendable { public init( name: String, - schema: JsonSchema, indices: [Index], properties: [String: DocumentProperty], security: DocumentTypeSecurity, @@ -125,7 +123,6 @@ public struct DocumentType: Codable, Equatable, Sendable { tradeMode: TradeMode ) { self.name = name - self.schema = schema self.indices = indices self.properties = properties self.security = security @@ -451,95 +448,3 @@ public struct TokenEveryoneRules: Codable, Equatable, Sendable { } } -// MARK: - Json Schema - -public struct JsonSchema: Codable, Equatable, Sendable { - public let type: String - public let properties: [String: JsonSchemaProperty] - public let required: [String] - public let additionalProperties: Bool - - public init(type: String, properties: [String: JsonSchemaProperty], required: [String], additionalProperties: Bool = false) { - self.type = type - self.properties = properties - self.required = required - self.additionalProperties = additionalProperties - } -} - -public indirect enum JsonSchemaPropertyValue: Codable, Equatable, Sendable { - case property(JsonSchemaProperty) -} - -public struct JsonSchemaProperty: Codable, Equatable, Sendable { - public let type: String - public let schemaDescription: String? - public let format: String? - public let pattern: String? - public let minLength: Int? - public let maxLength: Int? - public let minimum: Double? - public let maximum: Double? - public let items: JsonSchemaPropertyValue? - - public init( - type: String, - schemaDescription: String? = nil, - format: String? = nil, - pattern: String? = nil, - minLength: Int? = nil, - maxLength: Int? = nil, - minimum: Double? = nil, - maximum: Double? = nil, - items: JsonSchemaPropertyValue? = nil - ) { - self.type = type - self.schemaDescription = schemaDescription - self.format = format - self.pattern = pattern - self.minLength = minLength - self.maxLength = maxLength - self.minimum = minimum - self.maximum = maximum - self.items = items - } -} - -// MARK: - Factory Methods - -extension DPPDataContract { - /// Create a simple data contract - public static func create( - id: Identifier? = nil, - ownerId: Identifier, - documentTypes: [DocumentName: DocumentType] = [:], - contractDescription: String? = nil - ) -> DPPDataContract { - let contractId = id ?? Data(UUID().uuidString.utf8).prefix(32).paddedToLength(32) - - return DPPDataContract( - id: contractId, - version: 0, - ownerId: ownerId, - documentTypes: documentTypes, - config: DataContractConfig( - canBeDeleted: false, - readOnly: false, - keepsHistory: true, - documentsKeepRevisionLogForPassedTimeMs: nil, - documentsMutableContractDefaultStored: true - ), - schemaDefs: nil, - createdAt: TimestampMillis(Date().timeIntervalSince1970 * 1000), - updatedAt: nil, - createdAtBlockHeight: nil, - updatedAtBlockHeight: nil, - createdAtEpoch: nil, - updatedAtEpoch: nil, - groups: [:], - tokens: [:], - keywords: [], - contractDescription: contractDescription - ) - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPDocument.swift b/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPDocument.swift index ca6e30a406a..4e9b569af52 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPDocument.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPDocument.swift @@ -77,63 +77,6 @@ public struct DPPDocument: Identifiable, Codable, Equatable, Sendable { } } -// MARK: - Extended Document - -/// Extended document that includes data contract and metadata -public struct ExtendedDocument: Identifiable, Codable, Equatable, Sendable { - public let documentTypeName: String - public let dataContractId: Identifier - public let document: DPPDocument - public let dataContract: DPPDataContract - public let metadata: DocumentMetadata? - public let entropy: Bytes32 - public let tokenPaymentInfo: TokenPaymentInfo? - - /// Convenience accessor for document ID - public var id: Identifier { - document.id - } - - /// Get the data contract ID as a base58 string - public var dataContractIdString: String { - dataContractId.toBase58String() - } - - public init( - documentTypeName: String, - dataContractId: Identifier, - document: DPPDocument, - dataContract: DPPDataContract, - metadata: DocumentMetadata?, - entropy: Bytes32, - tokenPaymentInfo: TokenPaymentInfo? - ) { - self.documentTypeName = documentTypeName - self.dataContractId = dataContractId - self.document = document - self.dataContract = dataContract - self.metadata = metadata - self.entropy = entropy - self.tokenPaymentInfo = tokenPaymentInfo - } -} - -// MARK: - Document Metadata - -public struct DocumentMetadata: Codable, Equatable, Sendable { - public let blockHeight: BlockHeight - public let coreBlockHeight: CoreBlockHeight - public let timeMs: TimestampMillis - public let protocolVersion: UInt32 - - public init(blockHeight: BlockHeight, coreBlockHeight: CoreBlockHeight, timeMs: TimestampMillis, protocolVersion: UInt32) { - self.blockHeight = blockHeight - self.coreBlockHeight = coreBlockHeight - self.timeMs = timeMs - self.protocolVersion = protocolVersion - } -} - // MARK: - Token Payment Info public struct TokenPaymentInfo: Codable, Equatable, Sendable { @@ -149,81 +92,3 @@ public struct TokenPaymentInfo: Codable, Equatable, Sendable { self.amount = amount } } - -// MARK: - Document Patch - -/// Represents a partial document update -public struct DocumentPatch: Codable, Equatable, Sendable { - public let id: Identifier - public let properties: [String: PlatformValue] - public let revision: Revision? - public let updatedAt: TimestampMillis? - - /// Get the document ID as a base58 string - public var idString: String { - id.toBase58String() - } - - public init(id: Identifier, properties: [String: PlatformValue], revision: Revision?, updatedAt: TimestampMillis?) { - self.id = id - self.properties = properties - self.revision = revision - self.updatedAt = updatedAt - } -} - -// MARK: - Document Property Names - -public struct DocumentPropertyNames { - public static let featureVersion = "$version" - public static let id = "$id" - public static let dataContractId = "$dataContractId" - public static let revision = "$revision" - public static let ownerId = "$ownerId" - public static let price = "$price" - public static let createdAt = "$createdAt" - public static let updatedAt = "$updatedAt" - public static let transferredAt = "$transferredAt" - public static let createdAtBlockHeight = "$createdAtBlockHeight" - public static let updatedAtBlockHeight = "$updatedAtBlockHeight" - public static let transferredAtBlockHeight = "$transferredAtBlockHeight" - public static let createdAtCoreBlockHeight = "$createdAtCoreBlockHeight" - public static let updatedAtCoreBlockHeight = "$updatedAtCoreBlockHeight" - public static let transferredAtCoreBlockHeight = "$transferredAtCoreBlockHeight" - - public static let identifierFields = [id, ownerId, dataContractId] - public static let timestampFields = [createdAt, updatedAt, transferredAt] - public static let blockHeightFields = [ - createdAtBlockHeight, updatedAtBlockHeight, transferredAtBlockHeight, - createdAtCoreBlockHeight, updatedAtCoreBlockHeight, transferredAtCoreBlockHeight - ] -} - -// MARK: - Document Factory - -extension DPPDocument { - /// Create a new document with auto-generated ID - public static func create( - id: Identifier? = nil, - ownerId: Identifier, - properties: [String: PlatformValue] = [:] - ) -> DPPDocument { - let documentId = id ?? Data(UUID().uuidString.utf8).prefix(32).paddedToLength(32) - - return DPPDocument( - id: documentId, - ownerId: ownerId, - properties: properties, - revision: 0, - createdAt: TimestampMillis(Date().timeIntervalSince1970 * 1000), - updatedAt: nil, - transferredAt: nil, - createdAtBlockHeight: nil, - updatedAtBlockHeight: nil, - transferredAtBlockHeight: nil, - createdAtCoreBlockHeight: nil, - updatedAtCoreBlockHeight: nil, - transferredAtCoreBlockHeight: nil - ) - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPIdentity.swift b/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPIdentity.swift index f1f31f93f50..66f85f4280e 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPIdentity.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPIdentity.swift @@ -33,60 +33,3 @@ public struct DPPIdentity: Identifiable, Codable, Equatable, Sendable { } } -// MARK: - Partial Identity - -/// Represents a partially loaded identity (some data may not be fetched) -public struct PartialIdentity: Identifiable, Sendable { - public let id: Identifier - public let loadedPublicKeys: [KeyID: IdentityPublicKey] - public let balance: Credits? - public let revision: Revision? - public let notFoundPublicKeys: Set - - /// Get the identity ID as a base58 string - public var idString: String { - id.toBase58String() - } - - public init(id: Identifier, loadedPublicKeys: [KeyID: IdentityPublicKey], balance: Credits?, revision: Revision?, notFoundPublicKeys: Set) { - self.id = id - self.loadedPublicKeys = loadedPublicKeys - self.balance = balance - self.revision = revision - self.notFoundPublicKeys = notFoundPublicKeys - } -} - -// MARK: - Identity Factory - -extension DPPIdentity { - /// Create a new identity with initial keys - public static func create( - id: Identifier, - publicKeys: [IdentityPublicKey] = [], - balance: Credits = 0 - ) -> DPPIdentity { - let keysDict = Dictionary(uniqueKeysWithValues: publicKeys.map { ($0.id, $0) }) - return DPPIdentity( - id: id, - publicKeys: keysDict, - balance: balance, - revision: 0 - ) - } - - /// Create an identity from raw data - public static func create( - idData: Data, - publicKeys: [IdentityPublicKey] = [], - balance: Credits = 0 - ) -> DPPIdentity { - let keysDict = Dictionary(uniqueKeysWithValues: publicKeys.map { ($0.id, $0) }) - return DPPIdentity( - id: idData, - publicKeys: keysDict, - balance: balance, - revision: 0 - ) - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPStateTransition.swift b/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPStateTransition.swift deleted file mode 100644 index af6496b9180..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/DPP/DPPStateTransition.swift +++ /dev/null @@ -1,467 +0,0 @@ -import Foundation - -// MARK: - State Transition Models based on DPP - -/// Base protocol for all state transitions -public protocol StateTransition: Codable, Sendable { - var type: StateTransitionType { get } - var signature: BinaryData? { get } - var signaturePublicKeyId: KeyID? { get } -} - -// MARK: - State Transition Type - -public enum StateTransitionType: String, Codable, Sendable { - // Identity transitions - case identityCreate - case identityUpdate - case identityTopUp - case identityCreditWithdrawal - case identityCreditTransfer - - // Data Contract transitions - case dataContractCreate - case dataContractUpdate - - // Document transitions - case documentsBatch - - // Token transitions - case tokenTransfer - case tokenMint - case tokenBurn - case tokenFreeze - case tokenUnfreeze - - public var name: String { - switch self { - case .identityCreate: return "Identity Create" - case .identityUpdate: return "Identity Update" - case .identityTopUp: return "Identity Top Up" - case .identityCreditWithdrawal: return "Identity Credit Withdrawal" - case .identityCreditTransfer: return "Identity Credit Transfer" - case .dataContractCreate: return "Data Contract Create" - case .dataContractUpdate: return "Data Contract Update" - case .documentsBatch: return "Documents Batch" - case .tokenTransfer: return "Token Transfer" - case .tokenMint: return "Token Mint" - case .tokenBurn: return "Token Burn" - case .tokenFreeze: return "Token Freeze" - case .tokenUnfreeze: return "Token Unfreeze" - } - } -} - -// MARK: - Identity State Transitions - -public struct IdentityCreateTransition: StateTransition { - public var type: StateTransitionType { .identityCreate } - public let identityId: Identifier - public let publicKeys: [IdentityPublicKey] - public let balance: Credits - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(identityId: Identifier, publicKeys: [IdentityPublicKey], balance: Credits, signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.identityId = identityId - self.publicKeys = publicKeys - self.balance = balance - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -public struct IdentityUpdateTransition: StateTransition { - public var type: StateTransitionType { .identityUpdate } - public let identityId: Identifier - public let revision: Revision - public let addPublicKeys: [IdentityPublicKey]? - public let disablePublicKeys: [KeyID]? - public let publicKeysDisabledAt: TimestampMillis? - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(identityId: Identifier, revision: Revision, addPublicKeys: [IdentityPublicKey]? = nil, disablePublicKeys: [KeyID]? = nil, publicKeysDisabledAt: TimestampMillis? = nil, signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.identityId = identityId - self.revision = revision - self.addPublicKeys = addPublicKeys - self.disablePublicKeys = disablePublicKeys - self.publicKeysDisabledAt = publicKeysDisabledAt - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -public struct IdentityTopUpTransition: StateTransition { - public var type: StateTransitionType { .identityTopUp } - public let identityId: Identifier - public let amount: Credits - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(identityId: Identifier, amount: Credits, signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.identityId = identityId - self.amount = amount - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -public struct IdentityCreditWithdrawalTransition: StateTransition { - public var type: StateTransitionType { .identityCreditWithdrawal } - public let identityId: Identifier - public let amount: Credits - public let coreFeePerByte: UInt32 - public let pooling: Pooling - public let outputScript: BinaryData - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(identityId: Identifier, amount: Credits, coreFeePerByte: UInt32, pooling: Pooling, outputScript: BinaryData, signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.identityId = identityId - self.amount = amount - self.coreFeePerByte = coreFeePerByte - self.pooling = pooling - self.outputScript = outputScript - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -public struct IdentityCreditTransferTransition: StateTransition { - public var type: StateTransitionType { .identityCreditTransfer } - public let identityId: Identifier - public let recipientId: Identifier - public let amount: Credits - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(identityId: Identifier, recipientId: Identifier, amount: Credits, signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.identityId = identityId - self.recipientId = recipientId - self.amount = amount - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -// MARK: - Data Contract State Transitions - -public struct DataContractCreateTransition: StateTransition { - public var type: StateTransitionType { .dataContractCreate } - public let dataContract: DPPDataContract - public let entropy: Bytes32 - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(dataContract: DPPDataContract, entropy: Bytes32, signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.dataContract = dataContract - self.entropy = entropy - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -public struct DataContractUpdateTransition: StateTransition { - public var type: StateTransitionType { .dataContractUpdate } - public let dataContract: DPPDataContract - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(dataContract: DPPDataContract, signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.dataContract = dataContract - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -// MARK: - Document State Transitions - -public struct DocumentsBatchTransition: StateTransition { - public var type: StateTransitionType { .documentsBatch } - public let ownerId: Identifier - public let contractId: Identifier - public let documentTransitions: [DocumentTransition] - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(ownerId: Identifier, contractId: Identifier, documentTransitions: [DocumentTransition], signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.ownerId = ownerId - self.contractId = contractId - self.documentTransitions = documentTransitions - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -public enum DocumentTransition: Codable, Sendable { - case create(DocumentCreateTransition) - case replace(DocumentReplaceTransition) - case delete(DocumentDeleteTransition) - case transfer(DocumentTransferTransition) - case purchase(DocumentPurchaseTransition) - case updatePrice(DocumentUpdatePriceTransition) -} - -public struct DocumentCreateTransition: Codable, Sendable { - public let id: Identifier - public let dataContractId: Identifier - public let ownerId: Identifier - public let documentType: String - public let data: [String: PlatformValue] - public let entropy: Bytes32 - - public init(id: Identifier, dataContractId: Identifier, ownerId: Identifier, documentType: String, data: [String: PlatformValue], entropy: Bytes32) { - self.id = id - self.dataContractId = dataContractId - self.ownerId = ownerId - self.documentType = documentType - self.data = data - self.entropy = entropy - } -} - -public struct DocumentReplaceTransition: Codable, Sendable { - public let id: Identifier - public let dataContractId: Identifier - public let ownerId: Identifier - public let documentType: String - public let revision: Revision - public let data: [String: PlatformValue] - - public init(id: Identifier, dataContractId: Identifier, ownerId: Identifier, documentType: String, revision: Revision, data: [String: PlatformValue]) { - self.id = id - self.dataContractId = dataContractId - self.ownerId = ownerId - self.documentType = documentType - self.revision = revision - self.data = data - } -} - -public struct DocumentDeleteTransition: Codable, Sendable { - public let id: Identifier - public let dataContractId: Identifier - public let ownerId: Identifier - public let documentType: String - - public init(id: Identifier, dataContractId: Identifier, ownerId: Identifier, documentType: String) { - self.id = id - self.dataContractId = dataContractId - self.ownerId = ownerId - self.documentType = documentType - } -} - -public struct DocumentTransferTransition: Codable, Sendable { - public let id: Identifier - public let dataContractId: Identifier - public let ownerId: Identifier - public let recipientOwnerId: Identifier - public let documentType: String - public let revision: Revision - - public init(id: Identifier, dataContractId: Identifier, ownerId: Identifier, recipientOwnerId: Identifier, documentType: String, revision: Revision) { - self.id = id - self.dataContractId = dataContractId - self.ownerId = ownerId - self.recipientOwnerId = recipientOwnerId - self.documentType = documentType - self.revision = revision - } -} - -public struct DocumentPurchaseTransition: Codable, Sendable { - public let id: Identifier - public let dataContractId: Identifier - public let ownerId: Identifier - public let documentType: String - public let price: Credits - - public init(id: Identifier, dataContractId: Identifier, ownerId: Identifier, documentType: String, price: Credits) { - self.id = id - self.dataContractId = dataContractId - self.ownerId = ownerId - self.documentType = documentType - self.price = price - } -} - -public struct DocumentUpdatePriceTransition: Codable, Sendable { - public let id: Identifier - public let dataContractId: Identifier - public let ownerId: Identifier - public let documentType: String - public let price: Credits - - public init(id: Identifier, dataContractId: Identifier, ownerId: Identifier, documentType: String, price: Credits) { - self.id = id - self.dataContractId = dataContractId - self.ownerId = ownerId - self.documentType = documentType - self.price = price - } -} - -// MARK: - Token State Transitions - -public struct TokenTransferTransition: StateTransition { - public var type: StateTransitionType { .tokenTransfer } - public let tokenId: Identifier - public let senderId: Identifier - public let recipientId: Identifier - public let amount: UInt64 - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(tokenId: Identifier, senderId: Identifier, recipientId: Identifier, amount: UInt64, signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.tokenId = tokenId - self.senderId = senderId - self.recipientId = recipientId - self.amount = amount - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -public struct TokenMintTransition: StateTransition { - public var type: StateTransitionType { .tokenMint } - public let tokenId: Identifier - public let ownerId: Identifier - public let recipientId: Identifier? - public let amount: UInt64 - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(tokenId: Identifier, ownerId: Identifier, recipientId: Identifier? = nil, amount: UInt64, signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.tokenId = tokenId - self.ownerId = ownerId - self.recipientId = recipientId - self.amount = amount - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -public struct TokenBurnTransition: StateTransition { - public var type: StateTransitionType { .tokenBurn } - public let tokenId: Identifier - public let ownerId: Identifier - public let amount: UInt64 - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(tokenId: Identifier, ownerId: Identifier, amount: UInt64, signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.tokenId = tokenId - self.ownerId = ownerId - self.amount = amount - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -public struct TokenFreezeTransition: StateTransition { - public var type: StateTransitionType { .tokenFreeze } - public let tokenId: Identifier - public let ownerId: Identifier - public let frozenOwnerId: Identifier - public let amount: UInt64 - public let reason: String? - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(tokenId: Identifier, ownerId: Identifier, frozenOwnerId: Identifier, amount: UInt64, reason: String? = nil, signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.tokenId = tokenId - self.ownerId = ownerId - self.frozenOwnerId = frozenOwnerId - self.amount = amount - self.reason = reason - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -public struct TokenUnfreezeTransition: StateTransition { - public var type: StateTransitionType { .tokenUnfreeze } - public let tokenId: Identifier - public let ownerId: Identifier - public let unfrozenOwnerId: Identifier - public let amount: UInt64 - public let signature: BinaryData? - public let signaturePublicKeyId: KeyID? - - public init(tokenId: Identifier, ownerId: Identifier, unfrozenOwnerId: Identifier, amount: UInt64, signature: BinaryData? = nil, signaturePublicKeyId: KeyID? = nil) { - self.tokenId = tokenId - self.ownerId = ownerId - self.unfrozenOwnerId = unfrozenOwnerId - self.amount = amount - self.signature = signature - self.signaturePublicKeyId = signaturePublicKeyId - } -} - -// MARK: - Supporting Types - -public enum Pooling: UInt8, Codable, Sendable { - case never = 0 - case ifAvailable = 1 - case always = 2 -} - -// MARK: - State Transition Result - -public struct StateTransitionResult: Codable, Sendable { - public let fee: Credits - public let stateTransitionHash: Identifier - public let blockHeight: BlockHeight - public let blockTime: TimestampMillis - public let error: StateTransitionError? - - public init(fee: Credits, stateTransitionHash: Identifier, blockHeight: BlockHeight, blockTime: TimestampMillis, error: StateTransitionError? = nil) { - self.fee = fee - self.stateTransitionHash = stateTransitionHash - self.blockHeight = blockHeight - self.blockTime = blockTime - self.error = error - } -} - -public struct StateTransitionError: Codable, Error, Sendable { - public let code: UInt32 - public let message: String - public let data: [String: PlatformValue]? - - public init(code: UInt32, message: String, data: [String: PlatformValue]? = nil) { - self.code = code - self.message = message - self.data = data - } -} - -// MARK: - Broadcast State Transition - -public struct BroadcastStateTransitionRequest: Sendable { - public let stateTransition: any StateTransition - public let skipValidation: Bool - public let dryRun: Bool - - public init(stateTransition: any StateTransition, skipValidation: Bool = false, dryRun: Bool = false) { - self.stateTransition = stateTransition - self.skipValidation = skipValidation - self.dryRun = dryRun - } -} - -// MARK: - Wait for State Transition Result - -public struct WaitForStateTransitionResultRequest: Sendable { - public let stateTransitionHash: Identifier - public let prove: Bool - public let timeout: TimeInterval - - public init(stateTransitionHash: Identifier, prove: Bool = false, timeout: TimeInterval = 30) { - self.stateTransitionHash = stateTransitionHash - self.prove = prove - self.timeout = timeout - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/FFI/PlatformQueryExtensions.swift b/packages/swift-sdk/Sources/SwiftDashSDK/FFI/PlatformQueryExtensions.swift index 469b967f292..26933315419 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/FFI/PlatformQueryExtensions.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/FFI/PlatformQueryExtensions.swift @@ -4,12 +4,6 @@ import DashSDKFFI // MARK: - Platform Query Extensions for SDK @MainActor extension SDK { - // Helper to pass non-Sendable pointers across @Sendable closures when safe - private final class SendablePtr: @unchecked Sendable { - let ptr: UnsafeMutablePointer - init(_ p: UnsafeMutablePointer) { self.ptr = p } - } - // MARK: - Helper Functions /// Process DashSDKResult and extract JSON diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/FFI/Signer.swift b/packages/swift-sdk/Sources/SwiftDashSDK/FFI/Signer.swift index 89e55518fca..96c60da9d1d 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/FFI/Signer.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/FFI/Signer.swift @@ -17,56 +17,3 @@ public protocol Signer: Sendable { /// - Returns: true if the signer has the corresponding private key func canSign(identityPublicKey: Data) -> Bool } - -// MARK: - Test Signer - -/// Test signer implementation for development and testing -/// In production apps, use a secure signer that integrates with iOS Keychain -public final class TestSigner: Signer, @unchecked Sendable { - private var privateKeys: [String: Data] = [:] - - public init() { - // Initialize with some test private keys for demo purposes - // In a real app, these would be securely stored and retrieved - privateKeys["11111111111111111111111111111111"] = Data(repeating: 0x01, count: 32) - privateKeys["22222222222222222222222222222222"] = Data(repeating: 0x02, count: 32) - privateKeys["33333333333333333333333333333333"] = Data(repeating: 0x03, count: 32) - } - - public func sign(identityPublicKey: Data, data: Data) -> Data? { - // In a real implementation, this would: - // 1. Find the identity by its public key - // 2. Retrieve the corresponding private key from secure storage - // 3. Sign the data using the private key - // 4. Return the signature - - // For demo purposes, we'll create a mock signature - // based on the public key and data - var signature = Data() - signature.append(contentsOf: "SIGNATURE:".utf8) - signature.append(identityPublicKey.prefix(32)) - signature.append(data.prefix(32)) - - // Ensure signature is at least 64 bytes (typical for ECDSA) - while signature.count < 64 { - signature.append(0) - } - - return signature - } - - public func canSign(identityPublicKey: Data) -> Bool { - // In a real implementation, check if we have the private key - // corresponding to this public key - // For demo purposes, return true for known test identities - return true - } - - public func addPrivateKey(_ key: Data, forIdentity identityId: String) { - privateKeys[identityId] = key - } - - public func removePrivateKey(forIdentity identityId: String) { - privateKeys.removeValue(forKey: identityId) - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/FFI/StateTransitionExtensions.swift b/packages/swift-sdk/Sources/SwiftDashSDK/FFI/StateTransitionExtensions.swift index 1da4057ee21..8fa1dd81276 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/FFI/StateTransitionExtensions.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/FFI/StateTransitionExtensions.swift @@ -1321,17 +1321,6 @@ extension SDK { // MARK: - Token State Transitions - /// Transfer tokens between identities - public func tokenTransfer( - tokenId: String, - fromIdentityId: String, - toIdentityId: String, - amount: UInt64 - ) async throws -> (senderBalance: UInt64, receiverBalance: UInt64) { - // TODO: Implement when FFI binding is available - throw SDKError.notImplemented("Token transfer not yet implemented") - } - /// Mint new tokens public func tokenMint( contractId: String, @@ -2710,31 +2699,6 @@ extension SDK { ) } - /// Top up identity with instant lock (convenience method with DPPIdentity) - public func topUpIdentity( - _ identity: DPPIdentity, - instantLock: Data, - transaction: Data, - outputIndex: UInt32, - privateKey: Data - ) async throws -> UInt64 { - // Convert DPPIdentity to handle - let identityHandle = try identityToHandle(identity) - defer { - // Clean up the handle when done - dash_sdk_identity_destroy(idMut(identityHandle)) - } - - // Call the lower-level method - return try await identityTopUp( - identity: identityHandle, - instantLock: instantLock, - transaction: transaction, - outputIndex: outputIndex, - privateKey: privateKey - ) - } - /// Withdraw credits from identity (convenience method with DPPIdentity) public func withdrawFromIdentity( _ identity: DPPIdentity, diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Helpers/TestKeyGenerator.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Helpers/TestKeyGenerator.swift deleted file mode 100644 index 3e569e669ef..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Helpers/TestKeyGenerator.swift +++ /dev/null @@ -1,46 +0,0 @@ -import Foundation -import CryptoKit - -/// Test key generator for demo purposes only -/// DO NOT USE IN PRODUCTION - This generates deterministic keys which are insecure -struct TestKeyGenerator { - - /// Generate a deterministic private key from identity ID (FOR DEMO ONLY) - static func generateTestPrivateKey(identityId: Data, keyIndex: UInt32, purpose: UInt8) -> Data { - // Create deterministic seed from identity ID, key index, and purpose - var seedData = Data() - seedData.append(identityId) - seedData.append(contentsOf: withUnsafeBytes(of: keyIndex) { Data($0) }) - seedData.append(purpose) - - // Use SHA256 to generate a 32-byte private key - let hash = SHA256.hash(data: seedData) - return Data(hash) - } - - /// Generate test private keys for an identity - static func generateTestPrivateKeys(identityId: Data) -> [String: Data] { - var keys: [String: Data] = [:] - - // Generate keys for different purposes - // Key 0: Master key (not used in state transitions) - keys["0"] = generateTestPrivateKey(identityId: identityId, keyIndex: 0, purpose: 0) - - // Key 1: Authentication key (HIGH security) - keys["1"] = generateTestPrivateKey(identityId: identityId, keyIndex: 1, purpose: 0) - - // Key 2: Transfer key (CRITICAL security, purpose 3 = TRANSFER) - keys["2"] = generateTestPrivateKey(identityId: identityId, keyIndex: 2, purpose: 3) - - // Key 3: Another transfer key (some identities might have transfer key at index 3) - keys["3"] = generateTestPrivateKey(identityId: identityId, keyIndex: 3, purpose: 3) - - return keys - } - - /// Get private key for a specific key ID - static func getPrivateKey(identityId: Data, keyId: UInt32) -> Data? { - let keys = generateTestPrivateKeys(identityId: identityId) - return keys[String(keyId)] - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Identity.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Identity.swift index ca968f7b59c..6c6e0adca49 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Identity.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Identity.swift @@ -3,24 +3,6 @@ import DashSDKFFI /// Swift wrapper for Dash Platform Identity public class Identity { - public let id: String - public let balance: UInt64 - public let revision: UInt64 - - public init(id: String, balance: UInt64, revision: UInt64) { - self.id = id - self.balance = balance - self.revision = revision - } - /// Create an Identity from a C handle - public init?(handle: UnsafeMutablePointer) { - // In a real implementation, this would extract data from the C handle - // For now, create a placeholder - self.id = "placeholder" - self.balance = 0 - self.revision = 0 - } - - /// Get the balance (already accessible as property) + public init?(handle: UnsafeMutablePointer) {} } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/AccountCollection.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/AccountCollection.swift deleted file mode 100644 index e71fc2c527d..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/AccountCollection.swift +++ /dev/null @@ -1,54 +0,0 @@ -import Foundation -import DashSDKFFI - -/// Swift wrapper for a collection of accounts -public class AccountCollection { - private let handle: UnsafeMutablePointer - private weak var wallet: Wallet? - - internal init(handle: UnsafeMutablePointer, wallet: Wallet) { - self.handle = handle - self.wallet = wallet - } - - deinit { - account_collection_free(handle) - } - - // MARK: - Provider Accounts (BLS) - - /// Get the provider operator keys account (BLS) - public func getProviderOperatorKeys() -> BLSAccount? { - guard let rawPointer = account_collection_get_provider_operator_keys(handle) else { - return nil - } - let accountHandle = rawPointer.assumingMemoryBound(to: FFIBLSAccount.self) - return BLSAccount(handle: accountHandle, wallet: wallet) - } - - // MARK: - Provider Accounts (EdDSA) - - /// Get the provider platform keys account (EdDSA) - public func getProviderPlatformKeys() -> EdDSAAccount? { - guard let rawPointer = account_collection_get_provider_platform_keys(handle) else { - return nil - } - let accountHandle = rawPointer.assumingMemoryBound(to: FFIEdDSAAccount.self) - return EdDSAAccount(handle: accountHandle, wallet: wallet) - } - - // MARK: - Summary - - /// Get a summary of all accounts in this collection - public func getSummary() -> AccountCollectionSummary? { - guard let summaryPtr = account_collection_summary_data(handle) else { - return nil - } - - defer { - account_collection_summary_free(summaryPtr) - } - - return AccountCollectionSummary(ffiSummary: summaryPtr.pointee) - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Address.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Address.swift index 73a4101850f..7af34006c22 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Address.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Address.swift @@ -3,15 +3,6 @@ import DashSDKFFI /// Address utilities public class Address { - - /// Address type enumeration - public enum AddressType: UInt8 { - case p2pkh = 0 - case p2sh = 1 - case other = 2 - case unknown = 255 - } - /// Validate an address /// - Parameters: /// - address: The address to validate @@ -32,35 +23,4 @@ public class Address { return isValid } - - /// Get the type of an address - /// - Parameters: - /// - address: The address to check - /// - network: The network type - /// - Returns: The address type - public static func getType(of address: String, network: KeyWalletNetwork = .mainnet) -> AddressType { - var error = FFIError() - - let typeRaw = address.withCString { addressCStr in - address_get_type(addressCStr, network.ffiValue, &error) - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - // Map the raw value to our enum - switch typeRaw { - case 0: - return .p2pkh - case 1: - return .p2sh - case 2: - return .other - default: - return .unknown - } - } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/BIP38.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/BIP38.swift deleted file mode 100644 index beb0030e52b..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/BIP38.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -// BIP38 functionality is not available in the current FFI -// The bip38_encrypt_private_key and bip38_decrypt_private_key functions -// are not present in the unified header -// This file is kept as a placeholder to avoid Xcode build errors diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/BLSAccount.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/BLSAccount.swift deleted file mode 100644 index f5d4713f7eb..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/BLSAccount.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation -import DashSDKFFI - -/// Swift wrapper for a BLS account (used for provider keys) -public class BLSAccount { - internal let handle: UnsafeMutablePointer - private weak var wallet: Wallet? - - internal init(handle: UnsafeMutablePointer, wallet: Wallet?) { - self.handle = handle - self.wallet = wallet - } - - deinit { - bls_account_free(handle) - } - - // BLS account specific functionality can be added here - // This class manages the lifecycle of BLS provider key accounts -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/EdDSAAccount.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/EdDSAAccount.swift deleted file mode 100644 index e612a7cfc4c..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/EdDSAAccount.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation -import DashSDKFFI - -/// Swift wrapper for an EdDSA account (used for platform P2P keys) -public class EdDSAAccount { - internal let handle: UnsafeMutablePointer - private weak var wallet: Wallet? - - internal init(handle: UnsafeMutablePointer, wallet: Wallet?) { - self.handle = handle - self.wallet = wallet - } - - deinit { - eddsa_account_free(handle) - } - - // EdDSA account specific functionality can be added here - // This class manages the lifecycle of EdDSA platform P2P key accounts -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyDerivation.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyDerivation.swift deleted file mode 100644 index f39f2e5518d..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyDerivation.swift +++ /dev/null @@ -1,359 +0,0 @@ -import Foundation -import DashSDKFFI - -/// Key derivation utilities -public class KeyDerivation { - - /// Create a new master extended private key from seed - /// - Parameters: - /// - seed: The seed bytes - /// - network: The network type - /// - Returns: Extended private key handle - public static func createMasterKey(seed: Data, network: KeyWalletNetwork = .mainnet) throws -> ExtendedPrivateKey { - var error = FFIError() - - let xprivPtr = seed.withUnsafeBytes { seedBytes in - let seedPtr = seedBytes.bindMemory(to: UInt8.self).baseAddress - return derivation_new_master_key(seedPtr, seed.count, network.ffiValue, &error) - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let handle = xprivPtr else { - throw KeyWalletError(ffiError: error) - } - - return ExtendedPrivateKey(handle: handle) - } - - /// Get BIP44 account path - /// - Parameters: - /// - network: The network type - /// - accountIndex: The account index - /// - Returns: The derivation path string - public static func getBIP44AccountPath(network: KeyWalletNetwork = .mainnet, - accountIndex: UInt32) throws -> String { - var error = FFIError() - let maxPathLen = 256 - let pathBuffer = UnsafeMutablePointer.allocate(capacity: maxPathLen) - defer { - pathBuffer.deallocate() - } - - let success = derivation_bip44_account_path( - network.ffiValue, accountIndex, pathBuffer, maxPathLen, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - - return String(cString: pathBuffer) - } - - /// Get BIP44 payment path - /// - Parameters: - /// - network: The network type - /// - accountIndex: The account index - /// - isChange: Whether this is a change address - /// - addressIndex: The address index - /// - Returns: The derivation path string - public static func getBIP44PaymentPath(network: KeyWalletNetwork = .mainnet, - accountIndex: UInt32, - isChange: Bool, - addressIndex: UInt32) throws -> String { - var error = FFIError() - let maxPathLen = 256 - let pathBuffer = UnsafeMutablePointer.allocate(capacity: maxPathLen) - defer { - pathBuffer.deallocate() - } - - let success = derivation_bip44_payment_path( - network.ffiValue, accountIndex, isChange, addressIndex, - pathBuffer, maxPathLen, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - - return String(cString: pathBuffer) - } - - /// Get CoinJoin path - /// - Parameters: - /// - network: The network type - /// - accountIndex: The account index - /// - Returns: The derivation path string - public static func getCoinJoinPath(network: KeyWalletNetwork = .mainnet, - accountIndex: UInt32) throws -> String { - var error = FFIError() - let maxPathLen = 256 - let pathBuffer = UnsafeMutablePointer.allocate(capacity: maxPathLen) - defer { - pathBuffer.deallocate() - } - - let success = derivation_coinjoin_path( - network.ffiValue, accountIndex, pathBuffer, maxPathLen, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - - return String(cString: pathBuffer) - } - - /// Get identity registration path - /// - Parameters: - /// - network: The network type - /// - identityIndex: The identity index - /// - Returns: The derivation path string - public static func getIdentityRegistrationPath(network: KeyWalletNetwork = .mainnet, - identityIndex: UInt32) throws -> String { - var error = FFIError() - let maxPathLen = 256 - let pathBuffer = UnsafeMutablePointer.allocate(capacity: maxPathLen) - defer { - pathBuffer.deallocate() - } - - let success = derivation_identity_registration_path( - network.ffiValue, identityIndex, pathBuffer, maxPathLen, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - - return String(cString: pathBuffer) - } - - /// Get identity top-up path - /// - Parameters: - /// - network: The network type - /// - identityIndex: The identity index - /// - topupIndex: The top-up index - /// - Returns: The derivation path string - public static func getIdentityTopUpPath(network: KeyWalletNetwork = .mainnet, - identityIndex: UInt32, - topupIndex: UInt32) throws -> String { - var error = FFIError() - let maxPathLen = 256 - let pathBuffer = UnsafeMutablePointer.allocate(capacity: maxPathLen) - defer { - pathBuffer.deallocate() - } - - let success = derivation_identity_topup_path( - network.ffiValue, identityIndex, topupIndex, - pathBuffer, maxPathLen, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - - return String(cString: pathBuffer) - } - - /// Get identity authentication path - /// - Parameters: - /// - network: The network type - /// - identityIndex: The identity index - /// - keyIndex: The key index - /// - Returns: The derivation path string - public static func getIdentityAuthenticationPath(network: KeyWalletNetwork = .mainnet, - identityIndex: UInt32, - keyIndex: UInt32) throws -> String { - var error = FFIError() - let maxPathLen = 256 - let pathBuffer = UnsafeMutablePointer.allocate(capacity: maxPathLen) - defer { - pathBuffer.deallocate() - } - - let success = derivation_identity_authentication_path( - network.ffiValue, identityIndex, keyIndex, - pathBuffer, maxPathLen, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - - return String(cString: pathBuffer) - } - - /// Parse a derivation path string to indices - /// - Parameter path: The derivation path string - /// - Returns: Tuple of (indices, hardened flags) - public static func parsePath(_ path: String) throws -> (indices: [UInt32], hardened: [Bool]) { - var error = FFIError() - var indicesPtr: UnsafeMutablePointer? - var hardenedPtr: UnsafeMutablePointer? - var count: size_t = 0 - - let success = path.withCString { pathCStr in - derivation_path_parse(pathCStr, &indicesPtr, &hardenedPtr, &count, &error) - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - if let indices = indicesPtr, let hardened = hardenedPtr { - derivation_path_free(indices, hardened, count) - } - } - - guard success, let indices = indicesPtr, let hardened = hardenedPtr else { - throw KeyWalletError(ffiError: error) - } - - // Copy the data before freeing - var indicesArray: [UInt32] = [] - var hardenedArray: [Bool] = [] - - for i in 0.. - - internal init(handle: UnsafeMutablePointer) { - self.handle = handle - } - - deinit { - derivation_xpriv_free(handle) - } - - /// Convert to extended public key - public func toPublicKey() throws -> ExtendedPublicKey { - var error = FFIError() - guard let xpubHandle = derivation_xpriv_to_xpub(handle, &error) else { - defer { - if error.message != nil { - error_message_free(error.message) - } - } - throw KeyWalletError(ffiError: error) - } - - return ExtendedPublicKey(handle: xpubHandle) - } - - /// Get string representation - public func toString() throws -> String { - var error = FFIError() - guard let strPtr = derivation_xpriv_to_string(handle, &error) else { - defer { - if error.message != nil { - error_message_free(error.message) - } - } - throw KeyWalletError(ffiError: error) - } - - let str = String(cString: strPtr) - derivation_string_free(strPtr) - return str - } -} - -/// Extended public key handle -public class ExtendedPublicKey { - private let handle: UnsafeMutablePointer - - internal init(handle: UnsafeMutablePointer) { - self.handle = handle - } - - deinit { - derivation_xpub_free(handle) - } - - /// Get string representation - public func toString() throws -> String { - var error = FFIError() - guard let strPtr = derivation_xpub_to_string(handle, &error) else { - defer { - if error.message != nil { - error_message_free(error.message) - } - } - throw KeyWalletError(ffiError: error) - } - - let str = String(cString: strPtr) - derivation_string_free(strPtr) - return str - } - - /// Get fingerprint (4 bytes) - public func getFingerprint() throws -> Data { - var error = FFIError() - var fingerprint = Data(count: 4) - - let success = fingerprint.withUnsafeMutableBytes { bytes in - let ptr = bytes.bindMemory(to: UInt8.self).baseAddress - return derivation_xpub_fingerprint(handle, ptr, &error) - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - - return fingerprint - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyManager.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyManager.swift index 341bee03d91..5d983a4acae 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyManager.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyManager.swift @@ -110,25 +110,6 @@ public final class KeyManager: Sendable { }) } - /// Find a key by purpose for an identity - /// - Parameters: - /// - identity: The identity to find a key for - /// - purpose: The key purpose to find - /// - Returns: A key with the specified purpose if found, nil otherwise - /// - Note: This only returns the public key. Use `getPrivateKey(for:keyIndex:from:)` to check if private key is available. - public func getKeyByPurpose(for identity: DPPIdentity, purpose: KeyPurpose) -> IdentityPublicKey? { - // Prefer critical key, then any key with the purpose - if let criticalKey = identity.publicKeys.values.first(where: { - $0.purpose == purpose && $0.securityLevel == .critical && !$0.isDisabled - }) { - return criticalKey - } - - return identity.publicKeys.values.first(where: { - $0.purpose == purpose && !$0.isDisabled - }) - } - /// Find a key that meets specific requirements /// - Parameters: /// - identity: The identity to find a key for @@ -189,17 +170,6 @@ public final class KeyManager: Sendable { return privateKeyData } - /// Check if a private key is available for a key - /// - Parameters: - /// - identity: The identity - /// - keyIndex: The key index to check - /// - Returns: True if private key is available in keychain - /// - Note: This method must be called from a MainActor context - @MainActor - public func hasPrivateKey(for identity: DPPIdentity, keyIndex: KeyID) -> Bool { - return keychainManager.hasPrivateKey(identityId: identity.id, keyIndex: Int32(keyIndex)) - } - /// Find a key with available private key that meets requirements /// - Parameters: /// - identity: The identity to find a key for @@ -299,23 +269,6 @@ public final class KeyManager: Sendable { return (transferKey, signer) } - /// Create an authentication signer for an identity (convenience method) - /// - Parameter identity: The identity to create an authentication signer for - /// - Returns: A tuple containing the authentication key and signer handle - /// - Throws: `KeyManagerError.noSuitableKey` if no authentication key with private key is found - /// - Throws: `KeyManagerError.signerCreationFailed` if signer creation fails - /// - Note: The returned signer must be destroyed with `destroySigner(_:)` when done - /// - Note: This method must be called from a MainActor context - @MainActor - public func createAuthenticationSigner(for identity: DPPIdentity) throws -> (key: IdentityPublicKey, signer: OpaquePointer) { - guard let authKey = getAuthenticationKey(for: identity) else { - throw KeyManagerError.noSuitableKey("No authentication key found for identity") - } - - let signer = try createSigner(for: identity, keyIndex: authKey.id) - return (authKey, signer) - } - /// Create a signer for a key that meets specific requirements /// - Parameters: /// - identity: The identity to create a signer for @@ -355,71 +308,11 @@ public final class KeyManager: Sendable { let signerPtr = UnsafeMutablePointer(signer) dash_sdk_signer_destroy(signerPtr) } - - // MARK: - Key Validation - - /// Validate that a private key matches a public key - /// - Parameters: - /// - privateKeyData: The private key data - /// - publicKey: The public key to validate against - /// - isTestnet: Whether this is for testnet (default: true) - /// - Returns: True if the private key matches the public key - public func validatePrivateKey( - _ privateKeyData: Data, - matches publicKey: IdentityPublicKey, - isTestnet: Bool = true - ) -> Bool { - let privateKeyHex = privateKeyData.toHexString() - let publicKeyHex = publicKey.data.toHexString() - - return KeyValidation.validatePrivateKeyForPublicKey( - privateKeyHex: privateKeyHex, - publicKeyHex: publicKeyHex, - keyType: publicKey.keyType, - isTestnet: isTestnet - ) - } - - /// Validate private key format and length - /// - Parameter privateKeyData: The private key data to validate - /// - Returns: True if the private key has valid format (32 bytes) - public func validatePrivateKeyFormat(_ privateKeyData: Data) -> Bool { - return privateKeyData.count == 32 - } } // MARK: - Convenience Extensions extension KeyManager { - /// Find a key suitable for document operations (requires AUTHENTICATION purpose) - /// - Parameters: - /// - identity: The identity to find a key for - /// - minimumSecurityLevel: The minimum security level required (default: .high) - /// - Returns: A key suitable for document operations if found, nil otherwise - public func findDocumentSigningKey( - for identity: DPPIdentity, - minimumSecurityLevel: SecurityLevel = .high - ) -> IdentityPublicKey? { - return findKey( - for: identity, - purpose: .authentication, - minimumSecurityLevel: minimumSecurityLevel, - preferCritical: true - ) - } - - /// Find a key suitable for contract operations (requires CRITICAL + AUTHENTICATION) - /// - Parameter identity: The identity to find a key for - /// - Returns: A key suitable for contract operations if found, nil otherwise - public func findContractSigningKey(for identity: DPPIdentity) -> IdentityPublicKey? { - return findKey( - for: identity, - purpose: .authentication, - minimumSecurityLevel: .critical, - preferCritical: true - ) - } - /// Create a signer for document operations /// - Parameters: /// - identity: The identity to create a signer for diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyWallet.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyWallet.swift deleted file mode 100644 index 8c67c97e519..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyWallet.swift +++ /dev/null @@ -1,59 +0,0 @@ -import Foundation - -/// Main module for Dash Key Wallet functionality -/// -/// The KeyWallet module provides comprehensive wallet management capabilities for Dash, -/// including HD key derivation, address generation, transaction management, and provider keys. -/// -/// ## Key Features: -/// - Hierarchical Deterministic (HD) wallet support (BIP32/BIP44) -/// - Multiple account types (standard, CoinJoin, identity, provider) -/// - Address pool management with gap limits -/// - Transaction building and signing -/// - Provider key generation for masternodes -/// - BIP38 encryption/decryption -/// - Multi-wallet management -/// -/// ## Usage Example: -/// ```swift -/// // Initialize the library -/// KeyWallet.initialize() -/// -/// // Generate a new wallet -/// let mnemonic = try Mnemonic.generate() -/// let wallet = try Wallet(mnemonic: mnemonic, network: .testnet) -/// -/// // Get a receive address -/// let managed = try ManagedWallet(wallet: wallet) -/// let address = try managed.getNextReceiveAddress(wallet: wallet) -/// -/// // Check wallet balance -/// let balance = try wallet.getBalance() -/// print("Confirmed: \(balance.confirmed), Unconfirmed: \(balance.unconfirmed)") -/// ``` -public class KeyWallet { - - /// Initialize the key wallet library - /// Call this once at application startup - public static func initialize() { - _ = Wallet.initialize() - } - - /// Get the library version - public static var version: String { - return Wallet.version - } - - private init() {} -} - -// Re-export all public types for convenience -public typealias KeyWalletWallet = Wallet -public typealias KeyWalletAccount = Account -public typealias KeyWalletManagedWallet = ManagedWallet -public typealias KeyWalletManager = WalletManager -public typealias KeyWalletMnemonic = Mnemonic -public typealias KeyWalletTransaction = Transaction -public typealias KeyWalletAddress = Address -// public typealias KeyWalletBIP38 = BIP38 // BIP38 functions not available in current FFI -public typealias KeyWalletDerivation = KeyDerivation diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyWalletTypes.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyWalletTypes.swift index c6daff80fcb..69da8f01352 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyWalletTypes.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyWalletTypes.swift @@ -222,21 +222,6 @@ public struct AddressPoolInfo { } } -/// Transaction check result -public struct TransactionCheckResult { - public let isRelevant: Bool - public let totalReceived: UInt64 - public let totalSent: UInt64 - public let affectedAccountsCount: UInt32 - - init(ffiResult: FFITransactionCheckResult) { - self.isRelevant = ffiResult.is_relevant - self.totalReceived = ffiResult.total_received - self.totalSent = ffiResult.total_sent - self.affectedAccountsCount = ffiResult.affected_accounts_count - } -} - /// UTXO information public struct UTXO: Identifiable, Equatable, Sendable { public let txid: Data @@ -308,60 +293,6 @@ public struct UTXO: Identifiable, Equatable, Sendable { // MARK: - Account Collection Types -/// Summary of accounts in a collection -public struct AccountCollectionSummary { - public let bip44Indices: [UInt32] - public let bip32Indices: [UInt32] - public let coinJoinIndices: [UInt32] - public let identityTopUpIndices: [UInt32] - public let hasIdentityRegistration: Bool - public let hasIdentityInvitation: Bool - public let hasIdentityTopUpNotBound: Bool - public let hasProviderVotingKeys: Bool - public let hasProviderOwnerKeys: Bool - public let hasProviderOperatorKeys: Bool - public let hasProviderPlatformKeys: Bool - - init(ffiSummary: FFIAccountCollectionSummary) { - // Convert BIP44 indices - if ffiSummary.bip44_count > 0, let indices = ffiSummary.bip44_indices { - self.bip44Indices = Array(UnsafeBufferPointer(start: indices, count: ffiSummary.bip44_count)) - } else { - self.bip44Indices = [] - } - - // Convert BIP32 indices - if ffiSummary.bip32_count > 0, let indices = ffiSummary.bip32_indices { - self.bip32Indices = Array(UnsafeBufferPointer(start: indices, count: ffiSummary.bip32_count)) - } else { - self.bip32Indices = [] - } - - // Convert CoinJoin indices - if ffiSummary.coinjoin_count > 0, let indices = ffiSummary.coinjoin_indices { - self.coinJoinIndices = Array(UnsafeBufferPointer(start: indices, count: ffiSummary.coinjoin_count)) - } else { - self.coinJoinIndices = [] - } - - // Convert identity top-up indices - if ffiSummary.identity_topup_count > 0, let indices = ffiSummary.identity_topup_indices { - self.identityTopUpIndices = Array(UnsafeBufferPointer(start: indices, count: ffiSummary.identity_topup_count)) - } else { - self.identityTopUpIndices = [] - } - - // Copy boolean flags - self.hasIdentityRegistration = ffiSummary.has_identity_registration - self.hasIdentityInvitation = ffiSummary.has_identity_invitation - self.hasIdentityTopUpNotBound = ffiSummary.has_identity_topup_not_bound - self.hasProviderVotingKeys = ffiSummary.has_provider_voting_keys - self.hasProviderOwnerKeys = ffiSummary.has_provider_owner_keys - self.hasProviderOperatorKeys = ffiSummary.has_provider_operator_keys - self.hasProviderPlatformKeys = ffiSummary.has_provider_platform_keys - } -} - /// Summary of managed accounts in a collection public struct ManagedAccountCollectionSummary { public let bip44Indices: [UInt32] diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccountCollection.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccountCollection.swift index cfe33bc67d6..b83ce5ef6dd 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccountCollection.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccountCollection.swift @@ -118,11 +118,6 @@ public class ManagedAccountCollection { return ManagedAccount(handle: accountHandle, manager: manager) } - /// Check if identity registration account exists - public var hasIdentityRegistration: Bool { - return managed_account_collection_has_identity_registration(handle) - } - /// Get an identity top-up account by registration index /// - Parameter registrationIndex: The registration index /// - Returns: The managed account if it exists @@ -201,11 +196,6 @@ public class ManagedAccountCollection { return ManagedAccount(handle: accountHandle, manager: manager) } - /// Check if provider owner keys account exists - public var hasProviderOwnerKeys: Bool { - return managed_account_collection_has_provider_owner_keys(handle) - } - /// Get the provider operator keys account public func getProviderOperatorKeysAccount() -> ManagedAccount? { guard let rawPointer = managed_account_collection_get_provider_operator_keys(handle) else { @@ -215,11 +205,6 @@ public class ManagedAccountCollection { return ManagedAccount(handle: accountHandle, manager: manager) } - /// Check if provider operator keys account exists - public var hasProviderOperatorKeys: Bool { - return managed_account_collection_has_provider_operator_keys(handle) - } - /// Get the provider platform keys account public func getProviderPlatformKeysAccount() -> ManagedAccount? { guard let rawPointer = managed_account_collection_get_provider_platform_keys(handle) else { @@ -259,41 +244,4 @@ public class ManagedAccountCollection { } return ManagedPlatformAccount(handle: accountHandle) } - - /// Get all platform payment account keys from this collection. - /// - Returns: Array of account key identifiers (account index + key class). - public func getPlatformPaymentKeys() -> [PlatformPaymentAccountKey] { - var keysPtr: UnsafeMutablePointer? - var count: Int = 0 - - let success = managed_account_collection_get_platform_payment_keys(handle, &keysPtr, &count) - - guard success, let ptr = keysPtr, count > 0 else { - return [] - } - - defer { - managed_account_collection_free_platform_payment_keys(ptr, count) - } - - return (0.. ManagedAccountCollectionSummary? { - guard let summaryPtr = managed_account_collection_summary_data(handle) else { - return nil - } - - defer { - managed_account_collection_summary_free(summaryPtr) - } - - return ManagedAccountCollectionSummary(ffiSummary: summaryPtr.pointee) - } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedPlatformAccount.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedPlatformAccount.swift index cd30e9dbed6..0b7cf898c51 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedPlatformAccount.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedPlatformAccount.swift @@ -11,21 +11,6 @@ import Foundation import DashSDKFFI -// MARK: - Platform Payment Account Key - -/// Identifies a platform payment account by its account index and key class. -public struct PlatformPaymentAccountKey: Sendable, Equatable { - /// Account index (hardened). - public let account: UInt32 - /// Key class (hardened). - public let keyClass: UInt32 - - public init(account: UInt32, keyClass: UInt32) { - self.account = account - self.keyClass = keyClass - } -} - // MARK: - Managed Platform Account /// Swift wrapper for an FFI-managed platform payment account. @@ -43,49 +28,6 @@ public class ManagedPlatformAccount { managed_platform_account_free(handle) } - // MARK: - Properties - - /// The network this account belongs to. - public var network: KeyWalletNetwork { - let ffiNetwork = managed_platform_account_get_network(handle) - return KeyWalletNetwork(ffiNetwork: ffiNetwork) - } - - /// The account index (hardened). - public var accountIndex: UInt32 { - managed_platform_account_get_account_index(handle) - } - - /// The key class (hardened). - public var keyClass: UInt32 { - managed_platform_account_get_key_class(handle) - } - - /// Total balance in credits (1000 credits = 1 duff). - public var creditBalance: UInt64 { - managed_platform_account_get_credit_balance(handle) - } - - /// Total balance in duffs (credit_balance / 1000). - public var duffBalance: UInt64 { - managed_platform_account_get_duff_balance(handle) - } - - /// Number of addresses that have been funded (have non-zero balance). - public var fundedAddressCount: UInt32 { - managed_platform_account_get_funded_address_count(handle) - } - - /// Total number of derived addresses in this account. - public var totalAddressCount: UInt32 { - managed_platform_account_get_total_address_count(handle) - } - - /// Whether this is a watch-only account. - public var isWatchOnly: Bool { - managed_platform_account_get_is_watch_only(handle) - } - // MARK: - Address Pool /// Get the address pool for this platform account. diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedWallet.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedWallet.swift deleted file mode 100644 index 0dfeda0de5e..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedWallet.swift +++ /dev/null @@ -1,370 +0,0 @@ -import Foundation -import DashSDKFFI - -/// Swift wrapper for managed wallet with address pool management and transaction checking -public class ManagedWallet { - private let handle: UnsafeMutablePointer - - /// Create a managed wallet wrapper from a regular wallet - /// - Parameter wallet: The wallet to manage - public init(wallet: Wallet) throws { - var error = FFIError() - guard let managedPointer = wallet_create_managed_wallet(wallet.ffiHandle, &error) else { - defer { - if error.message != nil { - error_message_free(error.message) - } - } - throw KeyWalletError(ffiError: error) - } - - self.handle = managedPointer - } - - deinit { - ffi_managed_wallet_free(handle) - } - - // MARK: - Address Generation - - /// Get the next unused receive address for a BIP44 account - /// - Parameters: - /// - wallet: The wallet for key derivation - /// - accountIndex: The account index - /// - Returns: The next receive address - public func getNextReceiveAddress(wallet: Wallet, accountIndex: UInt32 = 0) throws -> String { - var error = FFIError() - - guard let infoHandle = getInfoHandle() else { - throw KeyWalletError.invalidState("Failed to get managed wallet info") - } - - let addressPtr = managed_wallet_get_next_bip44_receive_address( - infoHandle, wallet.ffiHandle, accountIndex, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let ptr = addressPtr else { - throw KeyWalletError(ffiError: error) - } - - let address = String(cString: ptr) - address_free(ptr) - - return address - } - - /// Get the next unused change address for a BIP44 account - /// - Parameters: - /// - wallet: The wallet for key derivation - /// - accountIndex: The account index - /// - Returns: The next change address - public func getNextChangeAddress(wallet: Wallet, accountIndex: UInt32 = 0) throws -> String { - var error = FFIError() - - guard let infoHandle = getInfoHandle() else { - throw KeyWalletError.invalidState("Failed to get managed wallet info") - } - - let addressPtr = managed_wallet_get_next_bip44_change_address( - infoHandle, wallet.ffiHandle, accountIndex, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let ptr = addressPtr else { - throw KeyWalletError(ffiError: error) - } - - let address = String(cString: ptr) - address_free(ptr) - - return address - } - - /// Get a range of external (receive) addresses - /// - Parameters: - /// - wallet: The wallet for key derivation - /// - accountIndex: The account index - /// - startIndex: Starting index (inclusive) - /// - endIndex: Ending index (exclusive) - /// - Returns: Array of addresses - public func getExternalAddressRange(wallet: Wallet, accountIndex: UInt32 = 0, - startIndex: UInt32, endIndex: UInt32) throws -> [String] { - guard endIndex > startIndex else { - throw KeyWalletError.invalidInput("End index must be greater than start index") - } - - var error = FFIError() - var addressesPtr: UnsafeMutablePointer?>? - var count: size_t = 0 - - guard let infoHandle = getInfoHandle() else { - throw KeyWalletError.invalidState("Failed to get managed wallet info") - } - - let success = managed_wallet_get_bip_44_external_address_range( - infoHandle, wallet.ffiHandle, accountIndex, - startIndex, endIndex, &addressesPtr, &count, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - if let ptr = addressesPtr { - address_array_free(ptr, count) - } - } - - guard success, let ptr = addressesPtr else { - throw KeyWalletError(ffiError: error) - } - - var addresses: [String] = [] - for i in 0.. [String] { - guard endIndex > startIndex else { - throw KeyWalletError.invalidInput("End index must be greater than start index") - } - - var error = FFIError() - var addressesPtr: UnsafeMutablePointer?>? - var count: size_t = 0 - - guard let infoHandle = getInfoHandle() else { - throw KeyWalletError.invalidState("Failed to get managed wallet info") - } - - let success = managed_wallet_get_bip_44_internal_address_range( - infoHandle, wallet.ffiHandle, accountIndex, - startIndex, endIndex, &addressesPtr, &count, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - if let ptr = addressesPtr { - address_array_free(ptr, count) - } - } - - guard success, let ptr = addressesPtr else { - throw KeyWalletError(ffiError: error) - } - - var addresses: [String] = [] - for i in 0.. AddressPoolInfo { - var error = FFIError() - var ffiInfo = FFIAddressPoolInfo() - - let success = managed_wallet_get_address_pool_info( - handle, accountType.ffiValue, accountIndex, - poolType.ffiValue, &ffiInfo, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - - return AddressPoolInfo(ffiInfo: ffiInfo) - } - - /// Set the gap limit for an address pool - /// - Parameters: - /// - accountType: The account type - /// - accountIndex: The account index - /// - poolType: The address pool type - /// - gapLimit: The new gap limit - public func setGapLimit(accountType: AccountType, accountIndex: UInt32 = 0, - poolType: AddressPoolType, gapLimit: UInt32) throws { - var error = FFIError() - - let success = managed_wallet_set_gap_limit( - handle, accountType.ffiValue, accountIndex, - poolType.ffiValue, gapLimit, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - } - - /// Generate addresses up to a specific index - /// - Parameters: - /// - wallet: The wallet for key derivation - /// - accountType: The account type - /// - accountIndex: The account index - /// - poolType: The address pool type - /// - targetIndex: The target index to generate up to - public func generateAddressesToIndex(wallet: Wallet, accountType: AccountType, - accountIndex: UInt32 = 0, - poolType: AddressPoolType, - targetIndex: UInt32) throws { - var error = FFIError() - - let success = managed_wallet_generate_addresses_to_index( - handle, wallet.ffiHandle, accountType.ffiValue, - accountIndex, poolType.ffiValue, targetIndex, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - } - - /// Mark an address as used - /// - Parameter address: The address to mark as used - public func markAddressUsed(_ address: String) throws { - var error = FFIError() - - let success = address.withCString { addressCStr in - managed_wallet_mark_address_used(handle, addressCStr, &error) - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - } - - // MARK: - Balance and UTXOs - - /// Get the wallet balance from managed wallet info - public func getBalance() throws -> Balance { - guard let infoHandle = getInfoHandle() else { - throw KeyWalletError.invalidState("Failed to get managed wallet info") - } - - var error = FFIError() - var confirmed: UInt64 = 0 - var unconfirmed: UInt64 = 0 - var immature: UInt64 = 0 - var locked: UInt64 = 0 - var total: UInt64 = 0 - - let success = managed_wallet_get_balance( - infoHandle, &confirmed, &unconfirmed, &immature, &locked, &total, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - - let ffiBalance = FFIBalance( - confirmed: confirmed, - unconfirmed: unconfirmed, - immature: immature, - locked: locked, - total: total - ) - - return Balance(ffiBalance: ffiBalance) - } - - /// Get all UTXOs from the managed wallet - public func getUTXOs() throws -> [UTXO] { - guard let infoHandle = getInfoHandle() else { - throw KeyWalletError.invalidState("Failed to get managed wallet info") - } - - var error = FFIError() - var utxosPtr: UnsafeMutablePointer? - var count: size_t = 0 - - let success = managed_wallet_get_utxos( - infoHandle, &utxosPtr, &count, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - if let ptr = utxosPtr { - utxo_array_free(ptr, count) - } - } - - guard success, let ptr = utxosPtr else { - throw KeyWalletError(ffiError: error) - } - - var utxos: [UTXO] = [] - for i in 0.. UnsafeMutablePointer? { - // The handle is an FFIManagedWalletInfo* (opaque C handle) - return handle - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Mnemonic.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Mnemonic.swift index cce08214acf..b06959b90e7 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Mnemonic.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Mnemonic.swift @@ -96,28 +96,4 @@ public class Mnemonic { return seed } - - /// Get word count from a mnemonic phrase - /// - Parameter mnemonic: The mnemonic phrase - /// - Returns: The number of words - public static func wordCount(of mnemonic: String) throws -> UInt32 { - var error = FFIError() - - let count = mnemonic.withCString { mnemonicCStr in - mnemonic_word_count(mnemonicCStr, &error) - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - // Check if there was an error - if error.code != FFIErrorCode(rawValue: 0) { - throw KeyWalletError(ffiError: error) - } - - return count - } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Transaction.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Transaction.swift index 21f5c0f9fcf..db7851a7510 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Transaction.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Transaction.swift @@ -22,31 +22,4 @@ public class Transaction { return FFITxOutput(address: cString, amount: amount) } } - - /// Classify a transaction for routing - /// - Parameter transactionData: The transaction bytes - /// - Returns: A string describing the transaction type - public static func classify(_ transactionData: Data) throws -> String { - var error = FFIError() - - let classificationPtr = transactionData.withUnsafeBytes { txBytes in - let txPtr = txBytes.bindMemory(to: UInt8.self).baseAddress - return transaction_classify(txPtr, transactionData.count, &error) - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let ptr = classificationPtr else { - throw KeyWalletError(ffiError: error) - } - - let classification = String(cString: ptr) - string_free(ptr) - - return classification - } } \ No newline at end of file diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Wallet.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Wallet.swift index c8a9e79c78f..b7627ecf4fc 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Wallet.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Wallet.swift @@ -6,258 +6,8 @@ public class Wallet { internal let handle: UnsafeMutablePointer private let ownsHandle: Bool - // MARK: - Static Methods - - /// Initialize the key wallet library (call once at app startup) - public static func initialize() -> Bool { - return key_wallet_ffi_initialize() - } - - /// Get library version - public static var version: String { - guard let versionPtr = key_wallet_ffi_version() else { - return "Unknown" - } - return String(cString: versionPtr) - } - - // MARK: - Initialization - - /// Create a wallet from a mnemonic phrase - /// - Parameters: - /// - mnemonic: The mnemonic phrase - /// - passphrase: Optional BIP39 passphrase - /// - network: The network type - /// - accountOptions: Account creation options - public init(mnemonic: String, passphrase: String? = nil, - network: KeyWalletNetwork = .mainnet, - accountOptions: AccountCreationOption = .default) throws { - - var error = FFIError() - let walletPtr: UnsafeMutablePointer? - - if case .specificAccounts = accountOptions { - // Use the with_options variant for specific accounts - var options = accountOptions.toFFIOptions() - - // Note: For production, we'd need to properly manage the memory for the arrays - // This is a simplified version - walletPtr = mnemonic.withCString { mnemonicCStr in - if let passphrase = passphrase { - return passphrase.withCString { passphraseCStr in - wallet_create_from_mnemonic_with_options( - mnemonicCStr, - passphraseCStr, - network.ffiValue, - &options, - &error - ) - } - } else { - return wallet_create_from_mnemonic_with_options( - mnemonicCStr, - nil, - network.ffiValue, - &options, - &error - ) - } - } - } else { - // Use simpler variant for default options - walletPtr = mnemonic.withCString { mnemonicCStr in - if let passphrase = passphrase { - return passphrase.withCString { passphraseCStr in - wallet_create_from_mnemonic( - mnemonicCStr, - passphraseCStr, - network.ffiValue, - &error - ) - } - } else { - return wallet_create_from_mnemonic( - mnemonicCStr, - nil, - network.ffiValue, - &error - ) - } - } - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let handle = walletPtr else { - throw KeyWalletError(ffiError: error) - } - - self.handle = handle - self.ownsHandle = true - } - - /// Create a wallet from seed bytes - /// - Parameters: - /// - seed: The seed bytes (typically 64 bytes) - /// - network: The network type - /// - accountOptions: Account creation options - public init(seed: Data, network: KeyWalletNetwork = .mainnet, - accountOptions: AccountCreationOption = .default) throws { - self.ownsHandle = true - - var error = FFIError() - let walletPtr: UnsafeMutablePointer? = seed.withUnsafeBytes { seedBytes in - let seedPtr = seedBytes.bindMemory(to: UInt8.self).baseAddress - - if case .specificAccounts = accountOptions { - var options = accountOptions.toFFIOptions() - return wallet_create_from_seed_with_options( - seedPtr, - seed.count, - network.ffiValue, - &options, - &error - ) - } else { - return wallet_create_from_seed( - seedPtr, - seed.count, - network.ffiValue, - &error - ) - } - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let handle = walletPtr else { - throw KeyWalletError(ffiError: error) - } - - self.handle = handle - } - - /// Create a watch-only wallet from extended public key - /// - Parameters: - /// - xpub: The extended public key string - /// - network: The network type - public init(xpub: String, network: KeyWalletNetwork = .mainnet) throws { - // Create an empty wallet first (no accounts) - var error = FFIError() - var options = AccountCreationOption.noAccounts.toFFIOptions() - - // Create a random wallet with no accounts - let walletPtr = wallet_create_random_with_options(network.ffiValue, &options, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let handle = walletPtr else { - throw KeyWalletError(ffiError: error) - } - - self.handle = handle - self.ownsHandle = true - - // Now add the watch-only account with the provided xpub - do { - _ = try addAccount(type: .standardBIP44, index: 0, xpub: xpub) - } catch { - // Clean up the wallet if adding account failed - wallet_free(handle) - throw error - } - } - - /// Create a new random wallet - /// - Parameters: - /// - network: The network type - /// - accountOptions: Account creation options - public static func createRandom(network: KeyWalletNetwork = .mainnet, - accountOptions: AccountCreationOption = .default) throws -> Wallet { - var error = FFIError() - let walletPtr: UnsafeMutablePointer? - - if case .specificAccounts = accountOptions { - var options = accountOptions.toFFIOptions() - walletPtr = wallet_create_random_with_options(network.ffiValue, &options, &error) - } else { - walletPtr = wallet_create_random(network.ffiValue, &error) - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let ptr = walletPtr else { - throw KeyWalletError(ffiError: error) - } - - // Create a wrapper that takes ownership - let wallet = Wallet(handle: ptr, network: network) - return wallet - } - - /// Private initializer for internal use (takes ownership) - private init(handle: UnsafeMutablePointer, network: KeyWalletNetwork) { - self.handle = handle - self.ownsHandle = true - } - // MARK: - Wallet Properties - /// Get the wallet ID (32-byte hash) - public var id: Data { - get throws { - var id = Data(count: 32) - var error = FFIError() - - let success = id.withUnsafeMutableBytes { idBytes in - let idPtr = idBytes.bindMemory(to: UInt8.self).baseAddress - return wallet_get_id(handle, idPtr, &error) - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - - return id - } - } - - /// Check if wallet has a mnemonic - public var hasMnemonic: Bool { - var error = FFIError() - let result = wallet_has_mnemonic(handle, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - return result - } - /// Check if wallet is watch-only public var isWatchOnly: Bool { var error = FFIError() @@ -301,32 +51,6 @@ public class Wallet { return Account(handle: accountHandle, wallet: self) } - /// Get an identity top-up account with a specific registration index - /// - Parameter registrationIndex: The identity registration index - /// - Returns: An account handle - public func getTopUpAccount(registrationIndex: UInt32) throws -> Account { - let result = wallet_get_top_up_account_with_registration_index( - handle, registrationIndex) - - defer { - if result.error_message != nil { - var mutableResult = result - account_result_free_error(&mutableResult) - } - } - - guard let accountHandle = result.account else { - var error = FFIError() - error.code = FFIErrorCode(rawValue: UInt32(result.error_code)) - if let msg = result.error_message { - error.message = msg - } - throw KeyWalletError(ffiError: error) - } - - return Account(handle: accountHandle, wallet: self) - } - /// Add an account to the wallet /// - Parameters: /// - type: The account type @@ -393,148 +117,9 @@ public class Wallet { } } - /// Get the number of accounts in the wallet - public var accountCount: UInt32 { - var error = FFIError() - let count = wallet_get_account_count(handle, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - return count - } - - // MARK: - Key Derivation - - /// Get the extended public key for an account - /// - Parameter accountIndex: The account index - /// - Returns: The extended public key string - public func getAccountXpub(accountIndex: UInt32) throws -> String { - var error = FFIError() - let xpubPtr = wallet_get_account_xpub(handle, accountIndex, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let ptr = xpubPtr else { - throw KeyWalletError(ffiError: error) - } - - let xpub = String(cString: ptr) - string_free(ptr) - - return xpub - } - - /// Get the extended private key for an account (only for non-watch-only wallets) - /// - Parameter accountIndex: The account index - /// - Returns: The extended private key string - public func getAccountXpriv(accountIndex: UInt32) throws -> String { - guard !isWatchOnly else { - throw KeyWalletError.invalidState("Cannot get private key from watch-only wallet") - } - - var error = FFIError() - let xprivPtr = wallet_get_account_xpriv(handle, accountIndex, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let ptr = xprivPtr else { - throw KeyWalletError(ffiError: error) - } - - let xpriv = String(cString: ptr) - string_free(ptr) - - return xpriv - } - - /// Derive a private key at a specific path - /// - Parameter derivationPath: The BIP32 derivation path - /// - Returns: The private key in WIF format - public func derivePrivateKey(path: String) throws -> String { - guard !isWatchOnly else { - throw KeyWalletError.invalidState("Cannot derive private key from watch-only wallet") - } - - var error = FFIError() - let wifPtr = path.withCString { pathCStr in - wallet_derive_private_key_as_wif(handle, pathCStr, &error) - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let ptr = wifPtr else { - throw KeyWalletError(ffiError: error) - } - - let wif = String(cString: ptr) - string_free(ptr) - - return wif - } - - /// Derive a public key at a specific path - /// - Parameter derivationPath: The BIP32 derivation path - /// - Returns: The public key as hex string - public func derivePublicKey(path: String) throws -> String { - var error = FFIError() - let hexPtr = path.withCString { pathCStr in - wallet_derive_public_key_as_hex(handle, pathCStr, &error) - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let ptr = hexPtr else { - throw KeyWalletError(ffiError: error) - } - - let hex = String(cString: ptr) - string_free(ptr) - - return hex - } - // MARK: - Internal /// Get the raw FFI handle (for internal use) - // MARK: - Account Collection - - /// Get a collection of all accounts in this wallet - /// - Parameter network: The network type - /// - Returns: The account collection - public func getAccountCollection() throws -> AccountCollection { - var error = FFIError() - - guard let collectionHandle = wallet_get_account_collection(handle, &error) else { - defer { - if error.message != nil { - error_message_free(error.message) - } - } - throw KeyWalletError(ffiError: error) - } - - return AccountCollection(handle: collectionHandle, wallet: self) - } internal var ffiHandle: UnsafeMutablePointer { handle } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift index 40f186ed22e..2f137363b60 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift @@ -7,24 +7,6 @@ public class WalletManager { internal let network: KeyWalletNetwork private let ownsHandle: Bool - /// Create a new standalone wallet manager - /// Note: Consider using SPVClient.getWalletManager() instead if you have an SPV client - public init(network: KeyWalletNetwork = .mainnet,) throws { - var error = FFIError() - guard let managerHandle = wallet_manager_create(network.ffiValue, &error) else { - defer { - if error.message != nil { - error_message_free(error.message) - } - } - throw KeyWalletError(ffiError: error) - } - - self.handle = managerHandle - self.network = network - self.ownsHandle = true - } - /// Create a wallet manager wrapper from an existing handle (does not own the handle) /// - Parameter handle: The FFI wallet manager handle internal init(handle: UnsafeMutablePointer) throws { @@ -55,93 +37,6 @@ public class WalletManager { // MARK: - Wallet Management - /// Add a wallet from mnemonic - /// - Parameters: - /// - mnemonic: The mnemonic phrase - /// - passphrase: Optional BIP39 passphrase - /// - accountOptions: Account creation options - /// - Returns: The wallet ID - @discardableResult - public func addWallet(mnemonic: String, passphrase: String? = nil, - accountOptions: AccountCreationOption = .default) throws -> Data { - var error = FFIError() - - let success = mnemonic.withCString { mnemonicCStr in - if case .specificAccounts = accountOptions { - var options = accountOptions.toFFIOptions() - - if let passphrase = passphrase { - return passphrase.withCString { passphraseCStr in - wallet_manager_add_wallet_from_mnemonic_with_options( - handle, mnemonicCStr, passphraseCStr, - &options, &error) - } - } else { - return wallet_manager_add_wallet_from_mnemonic_with_options( - handle, mnemonicCStr, nil, - &options, &error) - } - } else { - if let passphrase = passphrase { - return passphrase.withCString { passphraseCStr in - wallet_manager_add_wallet_from_mnemonic( - handle, mnemonicCStr, passphraseCStr, - &error) - } - } else { - return wallet_manager_add_wallet_from_mnemonic( - handle, mnemonicCStr, nil, - &error) - } - } - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - - // Get the wallet IDs to return the newly added wallet ID - return try getWalletIds().last ?? Data() - } - - /// Get all wallet IDs - /// - Returns: Array of wallet IDs (32-byte Data objects) - public func getWalletIds() throws -> [Data] { - var error = FFIError() - var walletIdsPtr: UnsafeMutablePointer? - var count: size_t = 0 - - let success = wallet_manager_get_wallet_ids(handle, &walletIdsPtr, &count, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - if let ptr = walletIdsPtr { - wallet_manager_free_wallet_ids(ptr, count) - } - } - - guard success, let ptr = walletIdsPtr else { - throw KeyWalletError(ffiError: error) - } - - var walletIds: [Data] = [] - for i in 0.. String { - guard walletId.count == 32 else { - throw KeyWalletError.invalidInput("Wallet ID must be exactly 32 bytes") - } - - var error = FFIError() - - // First get the managed wallet info - guard let managedInfo = walletId.withUnsafeBytes({ idBytes in - let idPtr = idBytes.bindMemory(to: UInt8.self).baseAddress - return wallet_manager_get_managed_wallet_info(handle, idPtr, &error) - }) else { - defer { - if error.message != nil { - error_message_free(error.message) - } - } - throw KeyWalletError(ffiError: error) - } - - defer { - managed_wallet_info_free(managedInfo) - } - - // Get the wallet - guard let wallet = walletId.withUnsafeBytes({ idBytes in - let idPtr = idBytes.bindMemory(to: UInt8.self).baseAddress - return wallet_manager_get_wallet(handle, idPtr, &error) - }) else { - defer { - if error.message != nil { - error_message_free(error.message) - } - } - throw KeyWalletError(ffiError: error) - } - - // Now get the change address - let addressPtr = managed_wallet_get_next_bip44_change_address( - managedInfo, wallet, accountIndex, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard let ptr = addressPtr else { - throw KeyWalletError(ffiError: error) - } - - let address = String(cString: ptr) - address_free(ptr) - - return address - } - - // MARK: - Balance - /// Get wallet balance - /// - Parameter walletId: The wallet ID - /// - Returns: Tuple of (confirmed, unconfirmed) balance - public func getWalletBalance(walletId: Data) throws -> (confirmed: UInt64, unconfirmed: UInt64) { - guard walletId.count == 32 else { - throw KeyWalletError.invalidInput("Wallet ID must be exactly 32 bytes") - } - - var error = FFIError() - var confirmed: UInt64 = 0 - var unconfirmed: UInt64 = 0 - - let success = walletId.withUnsafeBytes { idBytes in - let idPtr = idBytes.bindMemory(to: UInt8.self).baseAddress - return wallet_manager_get_wallet_balance( - handle, idPtr, &confirmed, &unconfirmed, &error) - } - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - guard success else { - throw KeyWalletError(ffiError: error) - } - - return (confirmed: confirmed, unconfirmed: unconfirmed) - } - /// Build a signed transaction /// - Parameters: /// - accIndex: The account index to use @@ -409,30 +189,6 @@ public class WalletManager { return (txData, fee) } - // MARK: - Block Height Management - - /// Get the current block height for a network - /// - Parameter network: The network type - /// - Returns: The current block height - public func currentHeight() throws -> UInt32 { - var error = FFIError() - - let height = wallet_manager_current_height(handle, &error) - - defer { - if error.message != nil { - error_message_free(error.message) - } - } - - // Check if there was an error - if error.code != FFIErrorCode(rawValue: 0) { - throw KeyWalletError(ffiError: error) - } - - return height - } - // MARK: - Managed Accounts /// Get a managed account from a wallet @@ -465,36 +221,6 @@ public class WalletManager { return ManagedAccount(handle: accountHandle, manager: self) } - /// Get a managed top-up account with a specific registration index - /// - Parameters: - /// - walletId: The wallet ID - /// - registrationIndex: The registration index - /// - Returns: The managed account - public func getManagedTopUpAccount(walletId: Data, registrationIndex: UInt32) throws -> ManagedAccount { - guard walletId.count == 32 else { - throw KeyWalletError.invalidInput("Wallet ID must be exactly 32 bytes") - } - - var result = walletId.withUnsafeBytes { idBytes in - let idPtr = idBytes.bindMemory(to: UInt8.self).baseAddress - return managed_wallet_get_top_up_account_with_registration_index( - handle, idPtr, registrationIndex) - } - - defer { - if result.error_message != nil { - managed_core_account_result_free_error(&result) - } - } - - guard let accountHandle = result.account else { - let errorMessage = result.error_message != nil ? String(cString: result.error_message!) : "Unknown error" - throw KeyWalletError.walletError(errorMessage) - } - - return ManagedAccount(handle: accountHandle, manager: self) - } - /// Get a collection of all managed accounts for a wallet /// - Parameters: /// - walletId: The wallet ID diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Models/IdentityModel.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Models/IdentityModel.swift index 9ff17c91ec2..9cb7806dbd5 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Models/IdentityModel.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Models/IdentityModel.swift @@ -72,58 +72,6 @@ public struct IdentityModel: Identifiable, Equatable, Hashable { self.init(id: idData, balance: balance, isLocal: isLocal, alias: alias, type: type, privateKeys: privateKeys, votingPrivateKey: votingPrivateKey, ownerPrivateKey: ownerPrivateKey, payoutPrivateKey: payoutPrivateKey, dpnsName: dpnsName, mainDpnsName: mainDpnsName, dpnsNames: dpnsNames, contestedDpnsNames: contestedDpnsNames, contestedDpnsInfo: contestedDpnsInfo, publicKeys: publicKeys, walletId: walletId, network: network) } - public init?(from identity: SwiftDashSDK.Identity) { - guard let idData = Data(hexString: identity.id), idData.count == 32 else { return nil } - self.id = idData - self._base58String = idData.toBase58String() - self.balance = identity.balance - self.isLocal = false - self.alias = nil - self.type = .user - self.privateKeys = [] - self.votingPrivateKey = nil - self.ownerPrivateKey = nil - self.payoutPrivateKey = nil - self.dpnsName = nil - self.mainDpnsName = nil - self.dpnsNames = [] - self.contestedDpnsNames = [] - self.contestedDpnsInfo = [:] - self.publicKeys = [] - self.walletId = nil - self.network = "testnet" - } - - /// Create from DPP Identity - public init(from dppIdentity: DPPIdentity, alias: String? = nil, type: IdentityType = .user, privateKeys: [Data] = [], dpnsName: String? = nil, mainDpnsName: String? = nil, dpnsNames: [String] = [], contestedDpnsNames: [String] = [], contestedDpnsInfo: [String: Any] = [:], walletId: Data? = nil, network: String = "testnet") { - self.id = dppIdentity.id // DPPIdentity already uses Data for id - self._base58String = dppIdentity.id.toBase58String() - self.balance = dppIdentity.balance - self.isLocal = false - self.alias = alias - self.type = type - self.privateKeys = privateKeys - self.dpnsName = dpnsName - self.mainDpnsName = mainDpnsName - self.dpnsNames = dpnsNames - self.contestedDpnsNames = contestedDpnsNames - self.contestedDpnsInfo = contestedDpnsInfo - self.publicKeys = Array(dppIdentity.publicKeys.values) - self.walletId = walletId - self.network = network - - // Extract specific keys for masternodes - if type == .masternode || type == .evonode { - self.votingPrivateKey = nil // Would be set separately - self.ownerPrivateKey = nil // Would be set separately - self.payoutPrivateKey = nil // Would be set separately - } else { - self.votingPrivateKey = nil - self.ownerPrivateKey = nil - self.payoutPrivateKey = nil - } - } - public var formattedBalance: String { let dashAmount = Double(balance) / 100_000_000_000 // 1 DASH = 100B credits return String(format: "%.8f DASH", dashAmount) diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Models/Network.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Models/Network.swift index c6cf90d964f..86437af4c1c 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Models/Network.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Models/Network.swift @@ -41,22 +41,4 @@ public enum AppNetwork: String, CaseIterable, Codable, Sendable { return DashSDKNetwork(rawValue: 3) } } - - public static var defaultNetwork: AppNetwork { - return .testnet - } - - // Convert to KeyWalletNetwork for wallet operations - public func toKeyWalletNetwork() -> KeyWalletNetwork { - switch self { - case .mainnet: - return .mainnet - case .testnet: - return .testnet - case .regtest: - return .regtest - case .devnet: - return .devnet - } - } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Models/TokenAction.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Models/TokenAction.swift deleted file mode 100644 index 6bc2fef385c..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Models/TokenAction.swift +++ /dev/null @@ -1,52 +0,0 @@ -import Foundation - -public enum TokenAction: String, CaseIterable, Identifiable, Sendable { - public var id: String { self.rawValue } - case transfer = "Transfer" - case mint = "Mint" - case burn = "Burn" - case claim = "Claim" - case freeze = "Freeze" - case unfreeze = "Unfreeze" - case destroyFrozenFunds = "Destroy Frozen Funds" - case directPurchase = "Direct Purchase" - - public var systemImage: String { - switch self { - case .transfer: return "arrow.left.arrow.right" - case .mint: return "plus.circle" - case .burn: return "flame" - case .claim: return "gift" - case .freeze: return "snowflake" - case .unfreeze: return "sun.max" - case .destroyFrozenFunds: return "trash" - case .directPurchase: return "cart" - } - } - - public var isEnabled: Bool { - // All actions are now enabled - return true - } - - public var description: String { - switch self { - case .transfer: - return "Transfer tokens to another identity" - case .mint: - return "Create new tokens (requires permission)" - case .burn: - return "Permanently destroy tokens" - case .claim: - return "Claim tokens from distribution" - case .freeze: - return "Temporarily lock tokens" - case .unfreeze: - return "Unlock frozen tokens" - case .destroyFrozenFunds: - return "Destroy frozen tokens" - case .directPurchase: - return "Purchase tokens directly" - } - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift deleted file mode 100644 index eabfb6fe86b..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift +++ /dev/null @@ -1,86 +0,0 @@ -import Foundation -import SwiftData - -/// Factory for creating SwiftData model containers for Dash Platform persistence -public enum DashModelContainer { - /// All persistent model types for the Dash SDK - public static var modelTypes: [any PersistentModel.Type] { - [ - PersistentIdentity.self, - PersistentDocument.self, - PersistentDataContract.self, - PersistentPublicKey.self, - PersistentTokenBalance.self, - PersistentKeyword.self, - PersistentToken.self, - PersistentDocumentType.self, - PersistentIndex.self, - PersistentProperty.self, - PersistentTokenHistoryEvent.self - ] - } - - /// Create the schema for all Dash Platform models - public static var schema: Schema { - Schema(modelTypes) - } - - /// Create a persistent model container for storing data - /// - Parameters: - /// - cloudKit: Whether to enable CloudKit sync (default: disabled) - /// - groupContainer: App group container configuration - /// - Returns: A configured ModelContainer - public static func create( - cloudKit: Bool = false, - groupContainer: ModelConfiguration.GroupContainer = .automatic - ) throws -> ModelContainer { - let modelConfiguration = ModelConfiguration( - schema: schema, - isStoredInMemoryOnly: false, - allowsSave: true, - groupContainer: groupContainer, - cloudKitDatabase: cloudKit ? .automatic : .none - ) - - return try ModelContainer( - for: schema, - configurations: [modelConfiguration] - ) - } - - /// Create an in-memory model container for testing - /// - Returns: A configured in-memory ModelContainer - public static func createInMemory() throws -> ModelContainer { - let modelConfiguration = ModelConfiguration( - schema: schema, - isStoredInMemoryOnly: true - ) - - return try ModelContainer( - for: schema, - configurations: [modelConfiguration] - ) - } -} - -/// SwiftData migration plan for Dash Platform model updates -public enum DashMigrationPlan: SchemaMigrationPlan { - public static var schemas: [any VersionedSchema.Type] { - [DashSchemaV1.self] - } - - public static var stages: [MigrationStage] { - [] - } -} - -/// Version 1 of the Dash Platform schema -public enum DashSchemaV1: VersionedSchema { - public static var versionIdentifier: Schema.Version { - Schema.Version(1, 0, 0) - } - - public static var models: [any PersistentModel.Type] { - DashModelContainer.modelTypes - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentToken.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentToken.swift index 36923137bcd..b528bc0df4a 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentToken.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentToken.swift @@ -67,9 +67,6 @@ public final class PersistentToken { @Relationship(deleteRule: .cascade) public var balances: [PersistentTokenBalance]? - @Relationship(deleteRule: .cascade) - public var historyEvents: [PersistentTokenHistoryEvent]? - public init(contractId: Data, position: Int, name: String, baseSupply: String, decimals: Int = 8) { // Create unique ID by combining contract ID and position var idData = contractId @@ -236,39 +233,6 @@ extension PersistentToken { } } -// MARK: - Control Rules Methods -extension PersistentToken { - public func getChangeControlRules(for type: ChangeControlRuleType) -> ChangeControlRules? { - switch type { - case .conventions: return conventionsChangeRules - case .maxSupply: return maxSupplyChangeRules - case .manualMinting: return manualMintingRules - case .manualBurning: return manualBurningRules - case .freeze: return freezeRules - case .unfreeze: return unfreezeRules - case .destroyFrozenFunds: return destroyFrozenFundsRules - case .emergencyAction: return emergencyActionRules - case .tradeMode: return tradeModeChangeRules - } - } - - public func setChangeControlRules(_ rules: ChangeControlRules, for type: ChangeControlRuleType) { - switch type { - case .conventions: conventionsChangeRules = rules - case .maxSupply: maxSupplyChangeRules = rules - case .manualMinting: manualMintingRules = rules - case .manualBurning: manualBurningRules = rules - case .freeze: freezeRules = rules - case .unfreeze: unfreezeRules = rules - case .destroyFrozenFunds: destroyFrozenFundsRules = rules - case .emergencyAction: emergencyActionRules = rules - case .tradeMode: tradeModeChangeRules = rules - } - - lastUpdatedAt = Date() - } -} - // MARK: - Query Helpers extension PersistentToken { public static func mintableTokensPredicate() -> Predicate { @@ -306,41 +270,4 @@ extension PersistentToken { token.contractId == contractId } } - - public static func tokensWithControlRulePredicate(rule: ControlRuleType) -> Predicate { - switch rule { - case .manualMinting: - return #Predicate { token in - token.manualMintingRules != nil - } - case .manualBurning: - return #Predicate { token in - token.manualBurningRules != nil - } - case .freeze: - return #Predicate { token in - token.freezeRules != nil - } - case .unfreeze: - return #Predicate { token in - token.unfreezeRules != nil - } - case .destroyFrozenFunds: - return #Predicate { token in - token.destroyFrozenFundsRules != nil - } - case .emergencyAction: - return #Predicate { token in - token.emergencyActionRules != nil - } - case .conventions: - return #Predicate { token in - token.conventionsChangeRules != nil - } - case .maxSupply: - return #Predicate { token in - token.maxSupplyChangeRules != nil - } - } - } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentTokenHistoryEvent.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentTokenHistoryEvent.swift deleted file mode 100644 index 6bc0b12a54d..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentTokenHistoryEvent.swift +++ /dev/null @@ -1,113 +0,0 @@ -import Foundation -import SwiftData - -/// SwiftData model for persisting token history events -@Model -public final class PersistentTokenHistoryEvent { - @Attribute(.unique) public var id: UUID - - // Event details - public var eventType: String - public var transactionId: Data? - public var blockHeight: Int64? - public var coreBlockHeight: Int64? - - // Participants - public var fromIdentity: Data? - public var toIdentity: Data? - public var performedByIdentity: Data - - // Amounts - public var amount: String? - public var balanceBefore: String? - public var balanceAfter: String? - - // Additional data stored as JSON - public var additionalDataJSON: Data? - - // Description - public var eventDescription: String? - - // Timestamps - public var createdAt: Date - public var eventTimestamp: Date - - // Relationship to token - @Relationship(inverse: \PersistentToken.historyEvents) - public var token: PersistentToken? - - public init( - eventType: TokenEventType, - performedByIdentity: Data, - eventTimestamp: Date = Date() - ) { - self.id = UUID() - self.eventType = eventType.rawValue - self.performedByIdentity = performedByIdentity - self.eventTimestamp = eventTimestamp - self.createdAt = Date() - } - - // MARK: - Computed Properties - public var eventTypeEnum: TokenEventType { - TokenEventType(rawValue: eventType) ?? .unknown - } - - public var fromIdentityBase58: String? { - fromIdentity?.toBase58String() - } - - public var toIdentityBase58: String? { - toIdentity?.toBase58String() - } - - public var performedByIdentityBase58: String { - performedByIdentity.toBase58String() - } - - public var displayTitle: String { - switch eventTypeEnum { - case .mint: - return "Minted \(formattedAmount)" - case .burn: - return "Burned \(formattedAmount)" - case .transfer: - return "Transfer \(formattedAmount)" - case .freeze: - return "Frozen \(formattedAmount)" - case .unfreeze: - return "Unfrozen \(formattedAmount)" - case .destroyFrozenFunds: - return "Destroyed Frozen Funds \(formattedAmount)" - case .configUpdate: - return "Configuration Updated" - case .emergencyAction: - return "Emergency Action" - case .perpetualDistribution: - return "Perpetual Distribution \(formattedAmount)" - case .preProgrammedRelease: - return "Pre-programmed Release \(formattedAmount)" - case .directPricing: - return "Direct Pricing Updated" - case .directPurchase: - return "Direct Purchase \(formattedAmount)" - case .unknown: - return "Unknown Event" - } - } - - private var formattedAmount: String { - guard let amount = amount else { return "" } - return amount - } - - // MARK: - Additional Data Methods - public func setAdditionalData(_ data: [String: Any]) { - additionalDataJSON = try? JSONSerialization.data(withJSONObject: data) - } - - public func getAdditionalData() -> [String: Any]? { - guard let data = additionalDataJSON else { return nil } - return try? JSONSerialization.jsonObject(with: data) as? [String: Any] - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Types/TokenTypes.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Types/TokenTypes.swift index 91443d2083e..2711a202db8 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Types/TokenTypes.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Types/TokenTypes.swift @@ -170,81 +170,6 @@ public enum TokenTradeMode: String, CaseIterable, Codable, Sendable { } } -// MARK: - Control Rule Types - -/// Types of control rules that can be configured on tokens -public enum ControlRuleType: Sendable { - case conventions - case maxSupply - case manualMinting - case manualBurning - case freeze - case unfreeze - case destroyFrozenFunds - case emergencyAction -} - -/// Types of change control rules for token configuration -public enum ChangeControlRuleType: Sendable { - case conventions - case maxSupply - case manualMinting - case manualBurning - case freeze - case unfreeze - case destroyFrozenFunds - case emergencyAction - case tradeMode -} - -// MARK: - Token Event Types - -/// Types of token history events -public enum TokenEventType: String, CaseIterable, Sendable { - case mint = "Mint" - case burn = "Burn" - case transfer = "Transfer" - case freeze = "Freeze" - case unfreeze = "Unfreeze" - case destroyFrozenFunds = "DestroyFrozenFunds" - case configUpdate = "ConfigUpdate" - case emergencyAction = "EmergencyAction" - case perpetualDistribution = "PerpetualDistribution" - case preProgrammedRelease = "PreProgrammedRelease" - case directPricing = "DirectPricing" - case directPurchase = "DirectPurchase" - case unknown = "Unknown" - - /// Whether this event type always requires a history entry - public var requiresHistory: Bool { - switch self { - case .configUpdate, .destroyFrozenFunds, .emergencyAction, .preProgrammedRelease: - return true - default: - return false - } - } - - /// SF Symbol icon for this event type - public var icon: String { - switch self { - case .mint: return "plus.circle.fill" - case .burn: return "flame.fill" - case .transfer: return "arrow.right.circle.fill" - case .freeze: return "snowflake" - case .unfreeze: return "sun.max.fill" - case .destroyFrozenFunds: return "trash.fill" - case .configUpdate: return "gearshape.fill" - case .emergencyAction: return "exclamationmark.triangle.fill" - case .perpetualDistribution: return "clock.arrow.circlepath" - case .preProgrammedRelease: return "calendar.badge.clock" - case .directPricing: return "tag.fill" - case .directPurchase: return "cart.fill" - case .unknown: return "questionmark.circle.fill" - } - } -} - // MARK: - Identity Type /// Types of identities on the Dash Platform diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/SDK.swift b/packages/swift-sdk/Sources/SwiftDashSDK/SDK.swift index 5f0c88ace12..9599ee9c556 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/SDK.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/SDK.swift @@ -1,6 +1,8 @@ import Foundation import DashSDKFFI +@_exported import DashSDKFFI + // MARK: - Data Extensions extension Data { /// Convert Data to Base58 string @@ -52,7 +54,7 @@ public final class SDK: @unchecked Sendable { public private(set) var handle: UnsafeMutablePointer? /// The network this SDK instance is connected to - public private(set) var network: Network = DashSDKNetwork(rawValue: 1) // Default to testnet + public private(set) var network: DashSDKNetwork = DashSDKNetwork(rawValue: 1) // Default to testnet /// Identities operations public lazy var identities = Identities(sdk: self) @@ -153,7 +155,7 @@ public final class SDK: @unchecked Sendable { /// This uses a trusted context provider that fetches quorum keys and /// data contracts from trusted HTTP endpoints instead of requiring proof verification. /// This is suitable for mobile applications where proof verification would be resource-intensive. - public init(network: Network) throws { + public init(network: DashSDKNetwork) throws { var config = DashSDKConfig() config.network = network config.dapi_addresses = nil diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift index 0008370f940..202b8aad60e 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift @@ -10,27 +10,6 @@ public enum SpecialKeyType: String, Sendable { case payout = "payout" } -/// Errors that can occur during keychain operations -public enum KeychainError: LocalizedError, Sendable { - case storeFailed(OSStatus) - case retrieveFailed(OSStatus) - case deleteFailed(OSStatus) - case invalidData - - public var errorDescription: String? { - switch self { - case .storeFailed(let status): - return "Failed to store key in keychain: \(status)" - case .retrieveFailed(let status): - return "Failed to retrieve key from keychain: \(status)" - case .deleteFailed(let status): - return "Failed to delete key from keychain: \(status)" - case .invalidData: - return "Invalid key data" - } - } -} - // MARK: - KeychainManager /// Manages secure storage of private keys in the iOS Keychain. @@ -71,15 +50,6 @@ public final class KeychainManager: Sendable { self.accessGroup = nil } - /// Initialize with custom service name and optional access group - /// - Parameters: - /// - serviceName: The service name for keychain entries (e.g., "com.myapp.keys") - /// - accessGroup: Optional access group for sharing keys between apps - public init(serviceName: String, accessGroup: String? = nil) { - self.serviceName = serviceName - self.accessGroup = accessGroup - } - // MARK: - Private Key Storage /// Store a private key in the keychain @@ -185,51 +155,6 @@ public final class KeychainManager: Sendable { return status == errSecSuccess || status == errSecItemNotFound } - /// Delete all private keys for an identity - /// - Parameter identityId: The identity ID (32 bytes) - /// - Returns: true if deletion completed (even if no keys existed) - @discardableResult - public func deleteAllPrivateKeys(for identityId: Data) -> Bool { - var query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: serviceName, - kSecMatchLimit as String: kSecMatchLimitAll - ] - - if let accessGroup = accessGroup { - query[kSecAttrAccessGroup as String] = accessGroup - } - - // First, find all keys for this identity - var result: AnyObject? - let searchStatus = SecItemCopyMatching(query as CFDictionary, &result) - - let identityHex = identityId.map { String(format: "%02x", $0) }.joined() - - if searchStatus == errSecSuccess, - let items = result as? [[String: Any]] { - // Filter items for this identity and delete them - for item in items { - if let account = item[kSecAttrAccount as String] as? String, - account.hasPrefix("privkey_\(identityHex)_") { - var deleteQuery: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: serviceName, - kSecAttrAccount as String: account - ] - - if let accessGroup = accessGroup { - deleteQuery[kSecAttrAccessGroup as String] = accessGroup - } - - SecItemDelete(deleteQuery as CFDictionary) - } - } - } - - return true - } - // MARK: - Special Keys (Voting, Owner, Payout) /// Store a special key (voting, owner, or payout) in the keychain @@ -244,27 +169,6 @@ public final class KeychainManager: Sendable { return storeKeyData(keyData, identifier: keyIdentifier) } - /// Retrieve a special key from the keychain - /// - Parameters: - /// - identityId: The identity ID (32 bytes) - /// - keyType: The type of special key - /// - Returns: The key data, or nil if not found - public func retrieveSpecialKey(identityId: Data, keyType: SpecialKeyType) -> Data? { - let keyIdentifier = generateSpecialKeyIdentifier(identityId: identityId, keyType: keyType) - return retrieveKeyData(identifier: keyIdentifier) - } - - /// Delete a special key from the keychain - /// - Parameters: - /// - identityId: The identity ID (32 bytes) - /// - keyType: The type of special key - /// - Returns: true if deletion succeeded or key didn't exist - @discardableResult - public func deleteSpecialKey(identityId: Data, keyType: SpecialKeyType) -> Bool { - let keyIdentifier = generateSpecialKeyIdentifier(identityId: identityId, keyType: keyType) - return deleteKeyData(identifier: keyIdentifier) - } - // MARK: - Key Existence Check /// Check if a private key exists in the keychain @@ -290,29 +194,6 @@ public final class KeychainManager: Sendable { return status == errSecSuccess } - /// Check if a special key exists in the keychain - /// - Parameters: - /// - identityId: The identity ID (32 bytes) - /// - keyType: The type of special key - /// - Returns: true if the key exists - public func hasSpecialKey(identityId: Data, keyType: SpecialKeyType) -> Bool { - let keyIdentifier = generateSpecialKeyIdentifier(identityId: identityId, keyType: keyType) - - var query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: serviceName, - kSecAttrAccount as String: keyIdentifier, - kSecMatchLimit as String: kSecMatchLimitOne - ] - - if let accessGroup = accessGroup { - query[kSecAttrAccessGroup as String] = accessGroup - } - - let status = SecItemCopyMatching(query as CFDictionary, nil) - return status == errSecSuccess - } - // MARK: - Generic Key Storage /// Store arbitrary data in the keychain with a custom identifier @@ -341,47 +222,6 @@ public final class KeychainManager: Sendable { return status == errSecSuccess ? identifier : nil } - /// Retrieve data from the keychain by identifier - /// - Parameter identifier: The identifier for the stored data - /// - Returns: The stored data, or nil if not found - public func retrieveKeyData(identifier: String) -> Data? { - var query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: serviceName, - kSecAttrAccount as String: identifier, - kSecReturnData as String: true, - kSecMatchLimit as String: kSecMatchLimitOne - ] - - if let accessGroup = accessGroup { - query[kSecAttrAccessGroup as String] = accessGroup - } - - var result: AnyObject? - let status = SecItemCopyMatching(query as CFDictionary, &result) - - return status == errSecSuccess ? result as? Data : nil - } - - /// Delete data from the keychain by identifier - /// - Parameter identifier: The identifier for the stored data - /// - Returns: true if deletion succeeded or data didn't exist - @discardableResult - public func deleteKeyData(identifier: String) -> Bool { - var query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: serviceName, - kSecAttrAccount as String: identifier - ] - - if let accessGroup = accessGroup { - query[kSecAttrAccessGroup as String] = accessGroup - } - - let status = SecItemDelete(query as CFDictionary) - return status == errSecSuccess || status == errSecItemNotFound - } - // MARK: - Private Helpers private func generateKeyIdentifier(identityId: Data, keyIndex: Int32) -> String { diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Services/DataManager.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Services/DataManager.swift index 95cb7b91f98..58ff199a246 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Services/DataManager.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Services/DataManager.swift @@ -84,16 +84,6 @@ public final class DataManager: ObservableObject { return persistentIdentities.map { $0.toIdentityModel() } } - /// Fetch local identities only - public func fetchLocalIdentities() throws -> [IdentityModel] { - let descriptor = FetchDescriptor( - predicate: PersistentIdentity.localIdentitiesPredicate(network: currentNetwork.rawValue), - sortBy: [SortDescriptor(\.createdAt, order: .reverse)] - ) - let persistentIdentities = try modelContext.fetch(descriptor) - return persistentIdentities.map { $0.toIdentityModel() } - } - /// Delete an identity public func deleteIdentity(withId identityId: Data) throws { let predicate = PersistentIdentity.predicate(identityId: identityId) @@ -140,28 +130,6 @@ public final class DataManager: ObservableObject { return persistentDocuments.map { $0.toDocumentModel() } } - /// Fetch documents owned by an identity - public func fetchDocuments(ownerId: Data) throws -> [DocumentModel] { - let predicate = PersistentDocument.predicate(ownerId: ownerId) - let descriptor = FetchDescriptor( - predicate: predicate, - sortBy: [SortDescriptor(\.createdAt, order: .reverse)] - ) - let persistentDocuments = try modelContext.fetch(descriptor) - return persistentDocuments.map { $0.toDocumentModel() } - } - - /// Delete a document - public func deleteDocument(withId documentId: String) throws { - let predicate = PersistentDocument.predicate(documentId: documentId) - let descriptor = FetchDescriptor(predicate: predicate) - - if let document = try modelContext.fetch(descriptor).first { - document.markAsDeleted() - try modelContext.save() - } - } - // MARK: - Contract Operations /// Save or update a contract @@ -199,111 +167,8 @@ public final class DataManager: ObservableObject { return persistentContracts.map { $0.toContractModel() } } - /// Fetch contracts with tokens - public func fetchContractsWithTokens() throws -> [ContractModel] { - let descriptor = FetchDescriptor( - predicate: PersistentDataContract.contractsWithTokensPredicate(network: currentNetwork.rawValue), - sortBy: [SortDescriptor(\.createdAt, order: .reverse)] - ) - let persistentContracts = try modelContext.fetch(descriptor) - return persistentContracts.map { $0.toContractModel() } - } - - // MARK: - Token Balance Operations - - /// Save or update a token balance - public func saveTokenBalance(tokenId: String, identityId: Data, balance: UInt64, frozen: Bool = false, tokenInfo: (name: String, symbol: String, decimals: Int32)? = nil) throws { - let predicate = PersistentTokenBalance.predicate(tokenId: tokenId, identityId: identityId) - let descriptor = FetchDescriptor(predicate: predicate) - - if let existingBalance = try modelContext.fetch(descriptor).first { - // Update existing balance - existingBalance.updateBalance(Int64(balance)) - if frozen != existingBalance.frozen { - if frozen { - existingBalance.freeze() - } else { - existingBalance.unfreeze() - } - } - if let info = tokenInfo { - existingBalance.updateTokenInfo(name: info.name, symbol: info.symbol, decimals: info.decimals) - } - } else { - // Create new balance - let persistentBalance = PersistentTokenBalance( - tokenId: tokenId, - identityId: identityId, - balance: Int64(balance), - frozen: frozen, - tokenName: tokenInfo?.name, - tokenSymbol: tokenInfo?.symbol, - tokenDecimals: tokenInfo?.decimals - ) - modelContext.insert(persistentBalance) - } - - try modelContext.save() - } - - /// Fetch token balances for an identity - public func fetchTokenBalances(identityId: Data) throws -> [(tokenId: String, balance: UInt64, frozen: Bool)] { - let predicate = PersistentTokenBalance.predicate(identityId: identityId) - let descriptor = FetchDescriptor( - predicate: predicate, - sortBy: [SortDescriptor(\.balance, order: .reverse)] - ) - let persistentBalances = try modelContext.fetch(descriptor) - return persistentBalances.map { $0.toTokenBalance() } - } - - // MARK: - Sync Operations - - /// Mark an identity as synced - func markIdentityAsSynced(identityId: Data) throws { - let predicate = PersistentIdentity.predicate(identityId: identityId) - let descriptor = FetchDescriptor(predicate: predicate) - - if let identity = try modelContext.fetch(descriptor).first { - identity.markAsSynced() - try modelContext.save() - } - } - - /// Get identities that need syncing - public func fetchIdentitiesNeedingSync(olderThan hours: Int = 1) throws -> [IdentityModel] { - let date = Date().addingTimeInterval(-Double(hours) * 3600) - let predicate = PersistentIdentity.needsSyncPredicate(olderThan: date) - let descriptor = FetchDescriptor( - predicate: predicate, - sortBy: [SortDescriptor(\.lastSyncedAt)] - ) - let persistentIdentities = try modelContext.fetch(descriptor) - return persistentIdentities.map { $0.toIdentityModel() } - } - // MARK: - Utility Operations - /// Clear all data (for testing or reset) - func clearAllData() throws { - // Delete all identities - try modelContext.delete(model: PersistentIdentity.self) - - // Delete all documents - try modelContext.delete(model: PersistentDocument.self) - - // Delete all contracts - try modelContext.delete(model: PersistentDataContract.self) - - // Delete all public keys - try modelContext.delete(model: PersistentPublicKey.self) - - // Delete all token balances - try modelContext.delete(model: PersistentTokenBalance.self) - - try modelContext.save() - } - /// Get statistics about stored data public func getDataStatistics() throws -> (identities: Int, documents: Int, contracts: Int, tokenBalances: Int) { let identityCount = try modelContext.fetchCount(FetchDescriptor()) diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Shared/UnifiedStateManager.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Shared/UnifiedStateManager.swift deleted file mode 100644 index 38163bfaf14..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Shared/UnifiedStateManager.swift +++ /dev/null @@ -1,162 +0,0 @@ -import Foundation -import SwiftUI - -@MainActor -public class UnifiedStateManager: ObservableObject { - @Published public var isInitialized = false - @Published public var isCoreSynced = false - @Published public var isPlatformSynced = false - - // Core wallet state - @Published public var coreBalance = Balance() - - // Platform state - @Published public var platformIdentities: [DPPIdentity] = [] - @Published public var platformDocuments: [DPPDocument] = [] - - // Cross-layer state - @Published public var assetLocks: [AssetLock] = [] - @Published public var pendingTransfers: [CrossLayerTransfer] = [] - - // SDKs (using Any for now - will be replaced with real types) - private var coreSDK: Any? - private var platformWrapper: Any? - - public init(coreSDK: Any? = nil, platformWrapper: Any? = nil) { - self.coreSDK = coreSDK - self.platformWrapper = platformWrapper - } - - public func updateCoreSDK(_ sdk: Any) async { - coreSDK = sdk - isCoreSynced = true - } - - public func updatePlatformWrapper(_ wrapper: Any) async { - platformWrapper = wrapper - isPlatformSynced = true - } - - // MARK: - Core Operations - - public func refreshCoreBalance() async { - // Mock implementation - coreBalance = Balance( - confirmed: 100_000_000, // 1 DASH - unconfirmed: 0 - ) - } - - public func sendCoreTransaction(to address: String, amount: UInt64) async throws -> String { - // Mock implementation - return UUID().uuidString - } - - // MARK: - Platform Operations - - public func createIdentity(withCredits credits: UInt64) async throws -> DPPIdentity { - // Mock implementation - let idData = Data(UUID().uuidString.utf8).prefix(32) - let paddedData = idData + Data(repeating: 0, count: max(0, 32 - idData.count)) - let identity = DPPIdentity( - id: paddedData, - publicKeys: [:], - balance: credits, - revision: 0 - ) - platformIdentities.append(identity) - return identity - } - - public func createDocument(type: String, data: [String: Any]) async throws -> DPPDocument { - // Mock implementation - let idData = Data(UUID().uuidString.utf8).prefix(32) - let paddedIdData = idData + Data(repeating: 0, count: max(0, 32 - idData.count)) - - let ownerData = Data(UUID().uuidString.utf8).prefix(32) - let paddedOwnerData = ownerData + Data(repeating: 0, count: max(0, 32 - ownerData.count)) - - let document = DPPDocument( - id: paddedIdData, - ownerId: paddedOwnerData, - properties: [:], - revision: 0, - createdAt: nil, - updatedAt: nil, - transferredAt: nil, - createdAtBlockHeight: nil, - updatedAtBlockHeight: nil, - transferredAtBlockHeight: nil, - createdAtCoreBlockHeight: nil, - updatedAtCoreBlockHeight: nil, - transferredAtCoreBlockHeight: nil - ) - platformDocuments.append(document) - return document - } - - // MARK: - Cross-Layer Operations - - public func createAssetLock(amount: UInt64) async throws -> AssetLock { - // Mock implementation - let assetLock = AssetLock( - txid: UUID().uuidString, - amount: amount, - status: .pending - ) - assetLocks.append(assetLock) - return assetLock - } - - public func transferToPlatform(amount: UInt64) async throws { - // Create asset lock - let assetLock = try await createAssetLock(amount: amount) - - // Create pending transfer - let transfer = CrossLayerTransfer( - id: UUID().uuidString, - amount: amount, - direction: .coreToPlatform, - status: .pending, - assetLockTxid: assetLock.txid - ) - pendingTransfers.append(transfer) - } -} - -// MARK: - Supporting Types - -public struct AssetLock: Identifiable { - public let id = UUID() - public let txid: String - public let amount: UInt64 - public let status: AssetLockStatus - public let createdAt = Date() -} - -public enum AssetLockStatus { - case pending - case confirmed - case failed -} - -public struct CrossLayerTransfer: Identifiable { - public let id: String - public let amount: UInt64 - public let direction: TransferDirection - public let status: TransferStatus - public let assetLockTxid: String? - public let createdAt = Date() -} - -public enum TransferDirection { - case coreToPlatform - case platformToCore -} - -public enum TransferStatus { - case pending - case processing - case completed - case failed -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/SwiftDashSDK.swift b/packages/swift-sdk/Sources/SwiftDashSDK/SwiftDashSDK.swift deleted file mode 100644 index 4933dfb2961..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/SwiftDashSDK.swift +++ /dev/null @@ -1,7 +0,0 @@ -// Re-export all C types so they're available to clients -@_exported import DashSDKFFI - -// Type aliases for easier access -public typealias Network = DashSDKNetwork -public typealias ErrorCode = DashSDKErrorCode -public typealias SDKConfig = DashSDKConfig diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Utils/ErrorHandling.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Utils/ErrorHandling.swift index 7c17fd2d9e3..9dbce30e636 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Utils/ErrorHandling.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Utils/ErrorHandling.swift @@ -55,7 +55,6 @@ public struct UserFacingError: Error, LocalizedError, Sendable, Equatable { public let category: ErrorCategory public let recoverySuggestion: String? public let underlyingError: String? - public let isRetryable: Bool public init( title: String, @@ -63,14 +62,12 @@ public struct UserFacingError: Error, LocalizedError, Sendable, Equatable { category: ErrorCategory = .unknown, recoverySuggestion: String? = nil, underlyingError: String? = nil, - isRetryable: Bool = false ) { self.title = title self.message = message self.category = category self.recoverySuggestion = recoverySuggestion self.underlyingError = underlyingError - self.isRetryable = isRetryable } public var errorDescription: String? { @@ -116,12 +113,6 @@ public enum ErrorFormatter { return error.localizedDescription } - /// Format an error with its category prefix - public static func formatWithCategory(_ error: Error, category: ErrorCategory) -> String { - let message = formatForDisplay(error) - return "[\(category.rawValue)] \(message)" - } - /// Extract a user-friendly message from any error public static func userFriendlyMessage(from error: Error) -> String { // Handle specific SDK error types @@ -168,30 +159,12 @@ public enum ErrorFormatter { .map { "\($0.offset + 1). \($0.element)" } .joined(separator: "\n") } - - /// Format an error for logging (includes more technical details) - public static func formatForLogging(_ error: Error, context: String? = nil) -> String { - var result = "" - if let ctx = context { - result += "[\(ctx)] " - } - result += String(describing: type(of: error)) - result += ": " - result += error.localizedDescription - - if let underlying = (error as NSError).userInfo[NSUnderlyingErrorKey] as? Error { - result += " (underlying: \(underlying.localizedDescription))" - } - - return result - } } // MARK: - Error Recovery /// Provides recovery suggestions for common error types. public enum ErrorRecovery { - /// Get recovery suggestion for an error category public static func suggestion(for category: ErrorCategory) -> String { switch category { @@ -223,44 +196,6 @@ public enum ErrorRecovery { return "An unexpected error occurred. Please try again." } } - - /// Get recovery suggestion based on error message content - public static func suggestionFromMessage(_ message: String) -> String? { - let lowercased = message.lowercased() - - if lowercased.contains("network") || lowercased.contains("connection") { - return suggestion(for: .network) - } - if lowercased.contains("timeout") || lowercased.contains("timed out") { - return suggestion(for: .timeout) - } - if lowercased.contains("invalid") || lowercased.contains("validation") { - return suggestion(for: .validation) - } - if lowercased.contains("not found") || lowercased.contains("notfound") { - return suggestion(for: .notFound) - } - if lowercased.contains("unauthorized") || lowercased.contains("authentication") { - return suggestion(for: .authentication) - } - if lowercased.contains("permission") || lowercased.contains("forbidden") { - return suggestion(for: .authorization) - } - - return nil - } - - /// Determine if an error is retryable based on its category - public static func isRetryable(category: ErrorCategory) -> Bool { - switch category { - case .network, .timeout, .system: - return true - case .validation, .userInput, .authentication, .authorization, - .notFound, .serialization, .cryptography, .storage, - .configuration, .unknown: - return false - } - } } // MARK: - Error Categorizer @@ -326,7 +261,6 @@ public enum ErrorCategorizer { let category = categorize(error) let message = ErrorFormatter.userFriendlyMessage(from: error) let suggestion = ErrorRecovery.suggestion(for: category) - let isRetryable = ErrorRecovery.isRetryable(category: category) return UserFacingError( title: category.rawValue, @@ -334,205 +268,6 @@ public enum ErrorCategorizer { category: category, recoverySuggestion: suggestion, underlyingError: String(describing: error), - isRetryable: isRetryable ) } } - -// MARK: - Error Builder - -/// Fluent builder for creating UserFacingError instances. -public final class ErrorBuilder: @unchecked Sendable { - private var title: String = "Error" - private var message: String = "" - private var category: ErrorCategory = .unknown - private var recoverySuggestion: String? - private var underlyingError: String? - private var isRetryable: Bool = false - - public init() {} - - @discardableResult - public func withTitle(_ title: String) -> ErrorBuilder { - self.title = title - return self - } - - @discardableResult - public func withMessage(_ message: String) -> ErrorBuilder { - self.message = message - return self - } - - @discardableResult - public func withCategory(_ category: ErrorCategory) -> ErrorBuilder { - self.category = category - return self - } - - @discardableResult - public func withRecoverySuggestion(_ suggestion: String) -> ErrorBuilder { - self.recoverySuggestion = suggestion - return self - } - - @discardableResult - public func withUnderlyingError(_ error: Error) -> ErrorBuilder { - self.underlyingError = String(describing: error) - return self - } - - @discardableResult - public func retryable(_ isRetryable: Bool = true) -> ErrorBuilder { - self.isRetryable = isRetryable - return self - } - - public func build() -> UserFacingError { - UserFacingError( - title: title, - message: message, - category: category, - recoverySuggestion: recoverySuggestion ?? ErrorRecovery.suggestion(for: category), - underlyingError: underlyingError, - isRetryable: isRetryable - ) - } - - // MARK: - Convenience Factory Methods - - public static func validation(_ message: String) -> UserFacingError { - ErrorBuilder() - .withTitle("Validation Error") - .withMessage(message) - .withCategory(.validation) - .build() - } - - public static func network(_ message: String) -> UserFacingError { - ErrorBuilder() - .withTitle("Network Error") - .withMessage(message) - .withCategory(.network) - .retryable() - .build() - } - - public static func notFound(_ message: String) -> UserFacingError { - ErrorBuilder() - .withTitle("Not Found") - .withMessage(message) - .withCategory(.notFound) - .build() - } - - public static func timeout(_ message: String = "The request took too long") -> UserFacingError { - ErrorBuilder() - .withTitle("Timeout") - .withMessage(message) - .withCategory(.timeout) - .retryable() - .build() - } - - public static func authentication(_ message: String) -> UserFacingError { - ErrorBuilder() - .withTitle("Authentication Error") - .withMessage(message) - .withCategory(.authentication) - .build() - } - - public static func userInput(_ message: String) -> UserFacingError { - ErrorBuilder() - .withTitle("Input Error") - .withMessage(message) - .withCategory(.userInput) - .build() - } -} - -// MARK: - Result Extensions - -public extension Result where Failure == Error { - /// Convert a Result to a UserFacingError result - func mapToUserFacingError() -> Result { - mapError { ErrorCategorizer.toUserFacingError($0) } - } - - /// Get the error message if this is a failure - var errorMessage: String? { - if case .failure(let error) = self { - return ErrorFormatter.formatForDisplay(error) - } - return nil - } -} - -// MARK: - Error Aggregator - -/// Aggregates multiple errors into a single error representation. -public struct ErrorAggregator: Sendable { - private var errors: [UserFacingError] = [] - - public init() {} - - public mutating func add(_ error: Error) { - errors.append(ErrorCategorizer.toUserFacingError(error)) - } - - public mutating func add(_ message: String, category: ErrorCategory = .unknown) { - errors.append(UserFacingError( - title: category.rawValue, - message: message, - category: category - )) - } - - public mutating func addValidation(_ message: String) { - add(message, category: .validation) - } - - public var hasErrors: Bool { - !errors.isEmpty - } - - public var count: Int { - errors.count - } - - public var allErrors: [UserFacingError] { - errors - } - - public var messages: [String] { - errors.map { $0.message } - } - - public var combinedMessage: String { - ErrorFormatter.formatValidationErrors(messages) - } - - /// Get the most severe error (by category priority) - public var primaryError: UserFacingError? { - // Priority: system > network > authentication > validation > unknown - let priority: [ErrorCategory] = [ - .system, .cryptography, .storage, - .network, .timeout, - .authentication, .authorization, - .serialization, .configuration, - .validation, .userInput, .notFound, - .unknown - ] - - return errors.min { e1, e2 in - let p1 = priority.firstIndex(of: e1.category) ?? priority.count - let p2 = priority.firstIndex(of: e2.category) ?? priority.count - return p1 < p2 - } - } - - public mutating func clear() { - errors.removeAll() - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Utils/PrivateKeyUtils.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Utils/PrivateKeyUtils.swift index 76ce3cc8fa8..c9e0c1a17bc 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Utils/PrivateKeyUtils.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Utils/PrivateKeyUtils.swift @@ -206,59 +206,6 @@ public enum KeyValidator { return .invalid("Private key does not match any public key") } - - /// Validates a private key string against an identity's public keys. - /// - Parameters: - /// - privateKeyInput: The private key string (hex or WIF). - /// - publicKeys: List of public keys to match against. - /// - isTestnet: Whether to use testnet parameters. - /// - Returns: ValidationResult with the matched key or error. - public static func validatePrivateKeyInput( - _ privateKeyInput: String, - against publicKeys: [IdentityPublicKey], - isTestnet: Bool = true - ) -> ValidationResult { - let parseResult = PrivateKeyParser.parse(privateKeyInput) - - guard let privateKey = parseResult.data else { - return .invalid(parseResult.error ?? "Failed to parse private key") - } - - return validatePrivateKey(privateKey, against: publicKeys, isTestnet: isTestnet) - } -} - -// MARK: - Key Size Validator - -/// Validates key sizes for different key types. -public enum KeySizeValidator { - - /// Expected private key size for a given key type. - /// - Parameter keyType: The type of key. - /// - Returns: Expected size in bytes. - public static func expectedPrivateKeySize(for keyType: KeyType) -> Int { - switch keyType { - case .ecdsaSecp256k1: - return 32 // 256 bits - case .bls12_381: - return 32 // 256 bits - case .ecdsaHash160: - return 32 // 256 bits for the actual key - case .bip13ScriptHash: - return 32 // 256 bits - case .eddsa25519Hash160: - return 32 // 256 bits - } - } - - /// Validates that a private key has the correct size for its type. - /// - Parameters: - /// - privateKey: The private key data. - /// - keyType: The type of key. - /// - Returns: True if the size is correct. - public static func isValidSize(_ privateKey: Data, for keyType: KeyType) -> Bool { - privateKey.count == expectedPrivateKeySize(for: keyType) - } } // MARK: - Key Formatter @@ -281,29 +228,4 @@ public enum KeyFormatter { public static func toWIF(_ privateKey: Data, isTestnet: Bool = true) -> String? { WIFParser.encodeToWIF(privateKey, isTestnet: isTestnet) } - - /// Format a public key for display. - /// - Parameters: - /// - publicKey: The public key. - /// - truncate: If true, shows only first and last 8 characters. - /// - Returns: Formatted string. - public static func formatPublicKey(_ publicKey: IdentityPublicKey, truncate: Bool = false) -> String { - let hex = AddressTransformer.dataToHex(publicKey.data) - if truncate && hex.count > 20 { - return "\(hex.prefix(8))...\(hex.suffix(8))" - } - return hex - } - - /// Format key information for display. - /// - Parameter publicKey: The public key to format. - /// - Returns: Multi-line description of the key. - public static func formatKeyInfo(_ publicKey: IdentityPublicKey) -> String { - """ - Key ID: #\(publicKey.id) - Purpose: \(publicKey.purpose.name) - Type: \(publicKey.keyType.name) - Security: \(publicKey.securityLevel.name) - """ - } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Utils/StateManagement.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Utils/StateManagement.swift index bc8b7e77cf7..9af8b70275d 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Utils/StateManagement.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Utils/StateManagement.swift @@ -35,237 +35,6 @@ public enum LoadingState: Equatable, Sendable { } } -// MARK: - Result State - -/// A generic result state that can hold either a success value or an error. -public enum ResultState: Sendable { - case idle - case loading - case success(T) - case failure(String) - - public var isLoading: Bool { - if case .loading = self { return true } - return false - } - - public var isSuccess: Bool { - if case .success = self { return true } - return false - } - - public var isFailure: Bool { - if case .failure = self { return true } - return false - } - - public var value: T? { - if case .success(let value) = self { return value } - return nil - } - - public var error: String? { - if case .failure(let error) = self { return error } - return nil - } - - /// Map the success value to a new type. - public func map(_ transform: (T) -> U) -> ResultState { - switch self { - case .idle: - return .idle - case .loading: - return .loading - case .success(let value): - return .success(transform(value)) - case .failure(let error): - return .failure(error) - } - } -} - -// MARK: - Async Operation Result - -/// Result of an async operation with timing information. -public struct AsyncOperationResult: Sendable { - public let value: T? - public let error: String? - public let duration: TimeInterval - public let isSuccess: Bool - - public init(value: T, duration: TimeInterval) { - self.value = value - self.error = nil - self.duration = duration - self.isSuccess = true - } - - public init(error: String, duration: TimeInterval) { - self.value = nil - self.error = error - self.duration = duration - self.isSuccess = false - } - - public init(error: Error, duration: TimeInterval) { - self.value = nil - self.error = error.localizedDescription - self.duration = duration - self.isSuccess = false - } -} - -// MARK: - Async Operation Helper - -/// Helper for running async operations with consistent state management. -public enum AsyncOperation { - - /// Run an async operation and return the result with timing. - /// - Parameters: - /// - operation: The async operation to run. - /// - Returns: AsyncOperationResult with value or error and timing. - public static func run(_ operation: () async throws -> T) async -> AsyncOperationResult { - let startTime = Date() - do { - let result = try await operation() - let duration = Date().timeIntervalSince(startTime) - return AsyncOperationResult(value: result, duration: duration) - } catch { - let duration = Date().timeIntervalSince(startTime) - return AsyncOperationResult(error: error, duration: duration) - } - } - - /// Run an async operation with callbacks for state updates. - /// - Parameters: - /// - onStart: Called when operation starts. - /// - onSuccess: Called with value on success. - /// - onError: Called with error message on failure. - /// - onComplete: Called when operation completes (success or failure). - /// - operation: The async operation to run. - @MainActor - public static func execute( - onStart: (() -> Void)? = nil, - onSuccess: ((T) -> Void)? = nil, - onError: ((String) -> Void)? = nil, - onComplete: (() -> Void)? = nil, - operation: () async throws -> T - ) async { - onStart?() - do { - let result = try await operation() - onSuccess?(result) - } catch { - onError?(error.localizedDescription) - } - onComplete?() - } -} - -// MARK: - Form State - -/// Represents the state of a form submission. -public enum FormState: Equatable, Sendable { - case editing - case submitting - case submitted - case error(String) - - public var isSubmitting: Bool { - if case .submitting = self { return true } - return false - } - - public var isSubmitted: Bool { - if case .submitted = self { return true } - return false - } - - public var hasError: Bool { - if case .error = self { return true } - return false - } - - public var errorMessage: String? { - if case .error(let message) = self { return message } - return nil - } - - public var canSubmit: Bool { - switch self { - case .editing, .error: - return true - case .submitting, .submitted: - return false - } - } -} - -// MARK: - Pagination State - -/// Represents pagination state for list views. -public struct PaginationState: Equatable, Sendable { - public var currentPage: Int - public var totalPages: Int - public var isLoadingMore: Bool - public var hasMore: Bool - - public init(currentPage: Int = 0, totalPages: Int = 0, isLoadingMore: Bool = false, hasMore: Bool = true) { - self.currentPage = currentPage - self.totalPages = totalPages - self.isLoadingMore = isLoadingMore - self.hasMore = hasMore - } - - public var canLoadMore: Bool { - !isLoadingMore && hasMore - } - - public mutating func startLoadingMore() { - isLoadingMore = true - } - - public mutating func finishLoadingMore(hasMore: Bool) { - currentPage += 1 - isLoadingMore = false - self.hasMore = hasMore - } - - public mutating func reset() { - currentPage = 0 - totalPages = 0 - isLoadingMore = false - hasMore = true - } -} - -// MARK: - Refresh State - -/// Represents pull-to-refresh state. -public struct RefreshState: Equatable, Sendable { - public var isRefreshing: Bool - public var lastRefreshed: Date? - - public init(isRefreshing: Bool = false, lastRefreshed: Date? = nil) { - self.isRefreshing = isRefreshing - self.lastRefreshed = lastRefreshed - } - - public var timeSinceLastRefresh: TimeInterval? { - guard let lastRefreshed = lastRefreshed else { return nil } - return Date().timeIntervalSince(lastRefreshed) - } - - public mutating func startRefresh() { - isRefreshing = true - } - - public mutating func finishRefresh() { - isRefreshing = false - lastRefreshed = Date() - } -} - // MARK: - Error State Helper /// Helper for managing error state with auto-dismiss. diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Utils/Validation.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Utils/Validation.swift index c084120061f..0647f8ddcb7 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Utils/Validation.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Utils/Validation.swift @@ -65,28 +65,12 @@ public enum AddressValidator { Bech32m.isValidPlatformAddress(address) } - /// Validates an address in either hex (42 chars) or bech32m format. - /// - Returns: `true` if string is valid in either format. - public static func validateAddress(_ address: String) -> Bool { - let trimmed = address.trimmingCharacters(in: .whitespacesAndNewlines) - if trimmed.lowercased().hasPrefix("dashevo1") || trimmed.lowercased().hasPrefix("tdashevo1") { - return validateBech32mAddress(trimmed) - } - return validateHexAddress(trimmed) - } - /// Validates a 32-byte hash in hex format (64 characters). /// - Returns: `true` if string is exactly 64 hex characters. public static func validateHash(_ hex: String) -> Bool { validateHexString(hex, byteLength: 32) } - /// Validates an identity ID in hex format (64 characters = 32 bytes). - /// - Returns: `true` if string is exactly 64 hex characters. - public static func validateIdentityIdHex(_ hex: String) -> Bool { - validateHexString(hex, byteLength: 32) - } - /// Checks if a string is a valid hex identity ID (64 chars). /// Useful for format detection (hex vs base58). /// - Returns: `true` if string is exactly 64 hex characters. diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Wallet/WalletModels.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Wallet/WalletModels.swift deleted file mode 100644 index a5cbd9d3a01..00000000000 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Wallet/WalletModels.swift +++ /dev/null @@ -1,119 +0,0 @@ -import Foundation - -// MARK: - Detailed Balance - -/// Balance with additional metadata about the wallet -public struct DetailedBalance: Equatable, Sendable { - /// The balance amounts - public let balance: Balance - - /// Number of addresses in the wallet - public let addressCount: Int - - /// Number of unspent transaction outputs - public let utxoCount: Int - - /// When this balance was last updated - public let lastUpdated: Date - - public init( - balance: Balance, - addressCount: Int = 0, - utxoCount: Int = 0, - lastUpdated: Date = Date() - ) { - self.balance = balance - self.addressCount = addressCount - self.utxoCount = utxoCount - self.lastUpdated = lastUpdated - } -} - -// MARK: - UTXO Selection - -/// Result of UTXO selection for transaction building -public struct UTXOSelection: Sendable { - /// UTXOs selected for the transaction - public let selectedUTXOs: [UTXO] - - /// Target amount to send (excluding fee) - public let totalAmount: UInt64 - - /// Estimated transaction fee - public let fee: UInt64 - - /// Change amount to return to sender - public let change: UInt64 - - public init( - selectedUTXOs: [UTXO], - totalAmount: UInt64, - fee: UInt64, - change: UInt64 - ) { - self.selectedUTXOs = selectedUTXOs - self.totalAmount = totalAmount - self.fee = fee - self.change = change - } - - /// Total amount available from selected UTXOs - public var inputAmount: UInt64 { - selectedUTXOs.reduce(0) { $0 + $1.amount } - } - - /// Whether the selection covers the target amount plus fee - public var isValid: Bool { - inputAmount >= totalAmount + fee - } -} - -// MARK: - UTXO Selector - -/// Utility for selecting optimal UTXOs for transaction building -public struct UTXOSelector { - - /// Select UTXOs to cover a target amount plus fees - /// - Parameters: - /// - available: Available UTXOs to choose from - /// - targetAmount: Amount to send (in duffs) - /// - feePerByte: Fee rate in duffs per byte - /// - Returns: UTXO selection if sufficient funds available, nil otherwise - public static func selectUTXOs( - from available: [UTXO], - targetAmount: UInt64, - feePerByte: UInt64 = 1 - ) -> UTXOSelection? { - // Filter to only confirmed UTXOs - let spendable = available.filter { $0.isSpendable } - - // Sort by amount (largest first for now - could implement better algorithms) - let sorted = spendable.sorted { $0.amount > $1.amount } - - var selected: [UTXO] = [] - var totalSelected: UInt64 = 0 - - // Simple selection - take UTXOs until we have enough - for utxo in sorted { - selected.append(utxo) - totalSelected += utxo.amount - - // Estimate fee (simplified - real implementation would be more complex) - let estimatedSize = (selected.count * 148) + (2 * 34) + 10 // inputs + outputs + overhead - let estimatedFee = UInt64(estimatedSize) * feePerByte - - if totalSelected >= targetAmount + estimatedFee { - let change = totalSelected - targetAmount - estimatedFee - return UTXOSelection( - selectedUTXOs: selected, - totalAmount: targetAmount, - fee: estimatedFee, - change: change - ) - } - } - - // Not enough funds - return nil - } -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift index df6365d1078..a3e519956c7 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift @@ -41,7 +41,6 @@ class AppState: ObservableObject { var useLocalPlatform: Bool { useDockerSetup } var useLocalCore: Bool { useDockerSetup } - private let testSigner = TestSigner() private var dataManager: DataManager? private var modelContext: ModelContext? diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/DPPCoreTypes.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/DPPCoreTypes.swift deleted file mode 100644 index f618e31ccb2..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/DPPCoreTypes.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation -import SwiftDashSDK - -// Re-export SDK DPP types for backward compatibility -public typealias Identifier = SwiftDashSDK.Identifier -public typealias Revision = SwiftDashSDK.Revision -public typealias TimestampMillis = SwiftDashSDK.TimestampMillis -public typealias Credits = SwiftDashSDK.Credits -public typealias KeyID = SwiftDashSDK.KeyID -public typealias BlockHeight = SwiftDashSDK.BlockHeight -public typealias CoreBlockHeight = SwiftDashSDK.CoreBlockHeight -public typealias KeyCount = SwiftDashSDK.KeyCount -public typealias EpochIndex = SwiftDashSDK.EpochIndex -public typealias BinaryData = SwiftDashSDK.BinaryData -public typealias Bytes32 = SwiftDashSDK.Bytes32 -public typealias DocumentName = SwiftDashSDK.DocumentName -public typealias DefinitionName = SwiftDashSDK.DefinitionName -public typealias GroupContractPosition = SwiftDashSDK.GroupContractPosition -public typealias TokenContractPosition = SwiftDashSDK.TokenContractPosition -public typealias PlatformValue = SwiftDashSDK.PlatformValue diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/DataContract.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/DataContract.swift deleted file mode 100644 index 51e454cabfc..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/DataContract.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation -import SwiftDashSDK - -// Re-export SDK Data Contract types for backward compatibility -public typealias DPPDataContract = SwiftDashSDK.DPPDataContract -public typealias DocumentType = SwiftDashSDK.DocumentType -public typealias DocumentProperty = SwiftDashSDK.DocumentProperty -public typealias PropertyType = SwiftDashSDK.PropertyType -public typealias Index = SwiftDashSDK.Index -public typealias IndexProperty = SwiftDashSDK.IndexProperty -public typealias IndexOrder = SwiftDashSDK.IndexOrder -public typealias ContestedUniqueIndexInformation = SwiftDashSDK.ContestedUniqueIndexInformation -public typealias ContestResolution = SwiftDashSDK.ContestResolution -public typealias DocumentTypeSecurity = SwiftDashSDK.DocumentTypeSecurity -public typealias KeyBounds = SwiftDashSDK.KeyBounds -public typealias SignatureVerificationConfiguration = SwiftDashSDK.SignatureVerificationConfiguration -public typealias Transferable = SwiftDashSDK.Transferable -public typealias TradeMode = SwiftDashSDK.TradeMode -public typealias DataContractConfig = SwiftDashSDK.DataContractConfig -public typealias Group = SwiftDashSDK.Group -public typealias TokenConfiguration = SwiftDashSDK.DPPTokenConfiguration -public typealias TokenRuleGroups = SwiftDashSDK.TokenRuleGroups -public typealias TokenOwnerRules = SwiftDashSDK.TokenOwnerRules -public typealias TokenEveryoneRules = SwiftDashSDK.TokenEveryoneRules -public typealias JsonSchema = SwiftDashSDK.JsonSchema -public typealias JsonSchemaProperty = SwiftDashSDK.JsonSchemaProperty -public typealias JsonSchemaPropertyValue = SwiftDashSDK.JsonSchemaPropertyValue diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/Document.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/Document.swift deleted file mode 100644 index f56f257526e..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/Document.swift +++ /dev/null @@ -1,66 +0,0 @@ -import Foundation -import SwiftDashSDK - -// Re-export SDK Document types for backward compatibility -public typealias DPPDocument = SwiftDashSDK.DPPDocument -public typealias ExtendedDocument = SwiftDashSDK.ExtendedDocument -public typealias DocumentMetadata = SwiftDashSDK.DocumentMetadata -public typealias TokenPaymentInfo = SwiftDashSDK.TokenPaymentInfo -public typealias DocumentPatch = SwiftDashSDK.DocumentPatch -public typealias DocumentPropertyNames = SwiftDashSDK.DocumentPropertyNames - -// MARK: - App-Specific Extensions - -extension DPPDocument { - /// Create from our simplified DocumentModel - init(from model: DocumentModel) { - // model.id is a string, convert it to Data - let documentId = Data.identifier(fromHex: model.id) ?? Data(repeating: 0, count: 32) - // model.ownerId is already Data - let ownerIdData = model.ownerId - - // Convert properties - in a real implementation, this would properly convert types - var platformProperties: [String: PlatformValue] = [:] - for (key, value) in model.data { - if let stringValue = value as? String { - platformProperties[key] = .string(stringValue) - } else if let intValue = value as? Int { - platformProperties[key] = .integer(Int64(intValue)) - } else if let boolValue = value as? Bool { - platformProperties[key] = .bool(boolValue) - } - // Add more type conversions as needed - } - - self.init( - id: documentId, - ownerId: ownerIdData, - properties: platformProperties, - revision: 0, - createdAt: model.createdAt.map { TimestampMillis($0.timeIntervalSince1970 * 1000) }, - updatedAt: model.updatedAt.map { TimestampMillis($0.timeIntervalSince1970 * 1000) }, - transferredAt: nil, - createdAtBlockHeight: nil, - updatedAtBlockHeight: nil, - transferredAtBlockHeight: nil, - createdAtCoreBlockHeight: nil, - updatedAtCoreBlockHeight: nil, - transferredAtCoreBlockHeight: nil - ) - } -} - -// MARK: - Helper Extensions - -extension Data { - /// Pad or truncate data to specified length - func paddedToLength(_ length: Int) -> Data { - if self.count >= length { - return self.prefix(length) - } else { - var padded = self - padded.append(Data(repeating: 0, count: length - self.count)) - return padded - } - } -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/Identity.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/Identity.swift deleted file mode 100644 index 9ff4c805388..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/Identity.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Foundation -import SwiftDashSDK - -// Re-export SDK Identity types for backward compatibility -public typealias DPPIdentity = SwiftDashSDK.DPPIdentity -public typealias PartialIdentity = SwiftDashSDK.PartialIdentity - -// MARK: - App-Specific Extensions - -extension DPPIdentity { - /// Create an identity from our simplified IdentityModel - init?(from model: IdentityModel) { - // model.id is already Data, no conversion needed - let idData = model.id - - self.init( - id: idData, - publicKeys: [:], - balance: model.balance, - revision: 0 - ) - - // Note: In a real implementation, we would convert private keys to public keys - } -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/README.md b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/README.md deleted file mode 100644 index 3d606a2df63..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/README.md +++ /dev/null @@ -1,165 +0,0 @@ -# DPP Models for Swift - -This directory contains Swift implementations of the Dash Platform Protocol (DPP) models, providing type-safe representations of core platform data structures. - -## Overview - -These models are based on the official DPP specification and provide a foundation for building iOS applications that interact with Dash Platform. - -## Core Types - -### Basic Types -- `Identifier`: 32-byte unique identifier (Data) -- `Revision`: Version number for documents and identities (UInt64) -- `TimestampMillis`: Unix timestamp in milliseconds (UInt64) -- `Credits`: Platform credits amount (UInt64) -- `BlockHeight`: Platform chain block height (UInt64) -- `CoreBlockHeight`: Core chain block height (UInt32) - -### Platform Value -- `PlatformValue`: Enum representing all possible value types in documents - - Supports: null, bool, integer, float, string, bytes, array, map - -## Identity Models - -### DPPIdentity -The main identity structure containing: -- Unique identifier -- Public keys with purposes and security levels -- Credit balance -- Revision number - -### IdentityPublicKey -Represents a public key with: -- **Purpose**: Authentication, Encryption, Transfer, Voting, etc. -- **Security Level**: Master, Critical, High, Medium -- **Key Type**: ECDSA, BLS12-381, etc. -- **Contract Bounds**: Optional restrictions to specific contracts - -### Key Features -- Support for different identity types (User, Masternode, Evonode) -- Hierarchical security levels for keys -- Contract-specific key restrictions - -## Document Models - -### DPPDocument -Core document structure with: -- Unique identifier and owner -- Flexible properties using PlatformValue -- Timestamps for creation, updates, and transfers -- Block height tracking for both chains - -### ExtendedDocument -Enhanced document that includes: -- Document type information -- Associated data contract -- Metadata and entropy -- Token payment information - -### DocumentPatch -Partial document updates containing only changed fields - -## Data Contract Models - -### DPPDataContract -Complete contract definition including: -- Document type schemas -- Indices for efficient querying -- Token configurations -- Multi-party control groups -- Keywords and descriptions - -### DocumentType -Defines the structure and rules for documents: -- JSON schema for validation -- Index definitions -- Security settings (insert/update/delete signatures) -- Transferability rules -- Token association - -### TokenConfiguration -Comprehensive token settings: -- Basic info (name, symbol, decimals) -- Supply controls (mintable, burnable, capped) -- Trading features (transferable, tradeable, sellable) -- Security features (freezable, pausable, destructible) -- Rule-based permissions - -## State Transitions - -### Supported Transitions -- **Identity**: Create, Update, TopUp, CreditWithdrawal, CreditTransfer -- **DataContract**: Create, Update -- **Document**: Create, Replace, Delete, Transfer, Purchase -- **Token**: Transfer, Mint, Burn, Freeze, Unfreeze - -### Common Properties -- Type identification -- Optional signatures with public key references -- Structured data for each operation - -## Integration with Existing Models - -The existing app models have been enhanced to support DPP: - -### IdentityModel -- Added `dppIdentity` property for full DPP data -- Added `publicKeys` array for key management -- Conversion methods between simplified and DPP models - -### DocumentModel -- Added `dppDocument` property -- Added `revision` tracking -- Automatic conversion from PlatformValue to simple types - -### ContractModel -- Added `dppDataContract` property -- Added token configurations -- Added keywords and description support - -## Usage Examples - -```swift -// Create a DPP Identity -let identity = DPPIdentity.create( - id: identifierData, - publicKeys: [authKey, transferKey], - balance: 1000000000 -) - -// Create a Document -let document = DPPDocument.create( - ownerId: ownerIdentifier, - properties: [ - "name": .string("Example"), - "value": .integer(42) - ] -) - -// Convert between models -let identityModel = IdentityModel(from: dppIdentity) -let documentModel = DocumentModel(from: dppDocument, - contractId: "...", - documentType: "profile") -``` - -## Best Practices - -1. **Use DPP models for platform interactions**: When communicating with Dash Platform, use the DPP models for accurate data representation. - -2. **Use simplified models for UI**: The existing models (IdentityModel, DocumentModel, etc.) are better suited for UI binding and display. - -3. **Handle conversions carefully**: When converting between PlatformValue and Swift native types, ensure proper type checking. - -4. **Respect security levels**: Always check key purposes and security levels before performing operations. - -5. **Track revisions**: Use revision numbers to handle concurrent updates properly. - -## Future Enhancements - -- Add validation methods for all models -- Implement serialization for network transport -- Add cryptographic signature verification -- Support for binary serialization formats -- Enhanced error handling for model conversions diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/StateTransition.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/StateTransition.swift deleted file mode 100644 index 7ad1bd9f7c3..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/DPP/StateTransition.swift +++ /dev/null @@ -1,41 +0,0 @@ -import Foundation -import SwiftDashSDK - -// Re-export SDK State Transition types for backward compatibility -public typealias StateTransition = SwiftDashSDK.StateTransition -public typealias StateTransitionType = SwiftDashSDK.StateTransitionType - -// Identity transitions -public typealias IdentityCreateTransition = SwiftDashSDK.IdentityCreateTransition -public typealias IdentityUpdateTransition = SwiftDashSDK.IdentityUpdateTransition -public typealias IdentityTopUpTransition = SwiftDashSDK.IdentityTopUpTransition -public typealias IdentityCreditWithdrawalTransition = SwiftDashSDK.IdentityCreditWithdrawalTransition -public typealias IdentityCreditTransferTransition = SwiftDashSDK.IdentityCreditTransferTransition - -// Data Contract transitions -public typealias DataContractCreateTransition = SwiftDashSDK.DataContractCreateTransition -public typealias DataContractUpdateTransition = SwiftDashSDK.DataContractUpdateTransition - -// Document transitions -public typealias DocumentsBatchTransition = SwiftDashSDK.DocumentsBatchTransition -public typealias DocumentTransition = SwiftDashSDK.DocumentTransition -public typealias DocumentCreateTransition = SwiftDashSDK.DocumentCreateTransition -public typealias DocumentReplaceTransition = SwiftDashSDK.DocumentReplaceTransition -public typealias DocumentDeleteTransition = SwiftDashSDK.DocumentDeleteTransition -public typealias DocumentTransferTransition = SwiftDashSDK.DocumentTransferTransition -public typealias DocumentPurchaseTransition = SwiftDashSDK.DocumentPurchaseTransition -public typealias DocumentUpdatePriceTransition = SwiftDashSDK.DocumentUpdatePriceTransition - -// Token transitions -public typealias TokenTransferTransition = SwiftDashSDK.TokenTransferTransition -public typealias TokenMintTransition = SwiftDashSDK.TokenMintTransition -public typealias TokenBurnTransition = SwiftDashSDK.TokenBurnTransition -public typealias TokenFreezeTransition = SwiftDashSDK.TokenFreezeTransition -public typealias TokenUnfreezeTransition = SwiftDashSDK.TokenUnfreezeTransition - -// Supporting types -public typealias Pooling = SwiftDashSDK.Pooling -public typealias StateTransitionResult = SwiftDashSDK.StateTransitionResult -public typealias StateTransitionError = SwiftDashSDK.StateTransitionError -public typealias BroadcastStateTransitionRequest = SwiftDashSDK.BroadcastStateTransitionRequest -public typealias WaitForStateTransitionResultRequest = SwiftDashSDK.WaitForStateTransitionResultRequest diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/ModelContainer+App.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/ModelContainer+App.swift deleted file mode 100644 index e219474a29c..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/ModelContainer+App.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation -import SwiftData -import SwiftDashSDK - -/// App-specific SwiftData model container configuration -extension ModelContainer { - /// Create the app's model container with all persistent models - static func appContainer() throws -> ModelContainer { - return try DashModelContainer.create() - } - - /// Create an in-memory container for testing - static func inMemoryContainer() throws -> ModelContainer { - return try DashModelContainer.createInMemory() - } -} - -/// Re-export SDK migration types for backward compatibility -public typealias AppMigrationPlan = DashMigrationPlan -public typealias AppSchemaV1 = DashSchemaV1 diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentDataContract.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentDataContract.swift deleted file mode 100644 index b728ca025cc..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentDataContract.swift +++ /dev/null @@ -1,116 +0,0 @@ -import Foundation -import SwiftData -import SwiftDashSDK - -// Re-export SDK type for backward compatibility -public typealias PersistentDataContract = SwiftDashSDK.PersistentDataContract - -// App-specific extensions that depend on app types -extension SwiftDashSDK.PersistentDataContract { - /// Convert to app's ContractModel - func toContractModel() -> ContractModel { - var tokenConfigs: [TokenConfiguration] = [] - if let tokensDict = tokenConfigurations { - tokenConfigs = tokensDict.compactMap { (_, value) in - guard value is [String: Any] else { return nil } - return nil - } - } - - return ContractModel( - id: idBase58, - name: name, - version: version ?? 1, - ownerId: ownerId ?? Data(), - documentTypes: documentTypesList, - schema: schema, - dppDataContract: nil, - tokens: tokenConfigs, - keywords: self.keywords, - description: contractDescription - ) - } - - /// Create from ContractModel - static func from(_ model: ContractModel, network: String = "testnet") -> SwiftDashSDK.PersistentDataContract { - let idData = Data.identifier(fromBase58: model.id) ?? Data() - let persistent = SwiftDashSDK.PersistentDataContract( - id: idData, - name: model.name, - serializedContract: Data(), - version: model.version, - ownerId: model.ownerId, - schema: model.schema, - documentTypesList: model.documentTypes, - keywords: model.keywords, - description: model.description, - hasTokens: !model.tokens.isEmpty, - network: network - ) - - if let serialized = try? JSONSerialization.data(withJSONObject: model.schema) { - persistent.serializedContract = serialized - } - - if !model.tokens.isEmpty { - var tokensDict: [String: Any] = [:] - for token in model.tokens { - tokensDict[token.symbol] = tokenConfigurationToJSON(token) - } - persistent.tokenConfigurations = tokensDict - } - - if let dppContract = model.dppDataContract { - var schemaDict: [String: Any] = [:] - for (docType, documentType) in dppContract.documentTypes { - var docSchema: [String: Any] = [:] - docSchema["type"] = "object" - docSchema["indices"] = documentType.indices.map { index in - return [ - "name": index.name, - "properties": index.properties.map { $0.name }, - "unique": index.unique - ] - } - docSchema["properties"] = documentType.properties.mapValues { prop in - return ["type": prop.type.rawValue] - } - schemaDict[docType] = docSchema - } - persistent.schema = schemaDict - - if !dppContract.groups.isEmpty { - var groupsDict: [String: Any] = [:] - for (groupId, group) in dppContract.groups { - groupsDict[String(groupId)] = [ - "members": group.members.map { member in - Data(member).base64EncodedString() - }, - "requiredPower": group.requiredPower - ] - } - persistent.groups = groupsDict - } - } - - return persistent - } - - private static func tokenConfigurationToJSON(_ token: TokenConfiguration) -> [String: Any] { - return [ - "name": token.name, - "symbol": token.symbol, - "description": token.description as Any, - "decimals": token.decimals, - "totalSupplyInLowestDenomination": token.totalSupplyInLowestDenomination, - "mintable": token.mintable, - "burnable": token.burnable, - "cappedSupply": token.cappedSupply, - "transferable": token.transferable, - "tradeable": token.tradeable, - "sellable": token.sellable, - "freezable": token.freezable, - "pausable": token.pausable - ] - } -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentDocument.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentDocument.swift deleted file mode 100644 index 6cf5ed6ebe3..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentDocument.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation -import SwiftData -import SwiftDashSDK - -// Re-export SDK type for backward compatibility -public typealias PersistentDocument = SwiftDashSDK.PersistentDocument - -// App-specific extensions that depend on app types -extension SwiftDashSDK.PersistentDocument { - func toDocumentModel() -> DocumentModel { - let dataDict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] ?? [:] - - return DocumentModel( - id: documentId, - contractId: contractId, - documentType: documentType, - ownerId: Data.identifier(fromBase58: ownerId) ?? Data(), - data: dataDict, - createdAt: createdAt, - updatedAt: updatedAt, - dppDocument: nil, - revision: Revision(revision) - ) - } - - static func from(_ document: DocumentModel) -> SwiftDashSDK.PersistentDocument { - let dataToStore = (try? JSONSerialization.data(withJSONObject: document.data, options: [])) ?? Data() - - return SwiftDashSDK.PersistentDocument( - documentId: document.id, - documentType: document.documentType, - revision: Int32(document.revision), - data: dataToStore, - contractId: document.contractId, - ownerId: document.ownerId.toBase58String(), - network: "testnet" - ) - } -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentDocumentType.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentDocumentType.swift deleted file mode 100644 index d581dc78c9e..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentDocumentType.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation -import SwiftData -import SwiftDashSDK - -// Re-export SDK type for backward compatibility -public typealias PersistentDocumentType = SwiftDashSDK.PersistentDocumentType diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentIdentity.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentIdentity.swift deleted file mode 100644 index 799ca01ea96..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentIdentity.swift +++ /dev/null @@ -1,127 +0,0 @@ -import Foundation -import SwiftData -import SwiftDashSDK - -// Re-export SDK types for backward compatibility -public typealias PersistentIdentity = SwiftDashSDK.PersistentIdentity - -// App-specific extensions that depend on app types -extension SwiftDashSDK.PersistentIdentity { - /// Convert to app's IdentityModel - @MainActor - func toIdentityModel() -> IdentityModel { - let publicKeyModels = publicKeys.compactMap { $0.toIdentityPublicKey() } - - // Convert public keys with private keys to Data array by retrieving from keychain - let privateKeyData = publicKeys - .filter { $0.hasPrivateKeyIdentifier } - .sorted(by: { $0.keyId < $1.keyId }) - .compactMap { persistentKey -> Data? in - guard let identityData = Data.identifier(fromBase58: persistentKey.identityId) else { return nil } - return KeychainManager.shared.retrievePrivateKey(identityId: identityData, keyIndex: persistentKey.keyId) - } - - // Retrieve special keys from keychain - let votingKey = votingPrivateKeyIdentifier != nil ? - KeychainManager.shared.retrieveSpecialKey(identityId: identityId, keyType: .voting) : nil - let ownerKey = ownerPrivateKeyIdentifier != nil ? - KeychainManager.shared.retrieveSpecialKey(identityId: identityId, keyType: .owner) : nil - let payoutKey = payoutPrivateKeyIdentifier != nil ? - KeychainManager.shared.retrieveSpecialKey(identityId: identityId, keyType: .payout) : nil - - return IdentityModel( - id: identityId, - balance: UInt64(balance), - isLocal: isLocal, - alias: alias, - type: identityTypeEnum, - privateKeys: privateKeyData, - votingPrivateKey: votingKey, - ownerPrivateKey: ownerKey, - payoutPrivateKey: payoutKey, - dpnsName: dpnsName, - mainDpnsName: mainDpnsName, - publicKeys: publicKeyModels - ) - } - - /// Create from IdentityModel - @MainActor - static func from(_ model: IdentityModel, network: String = "testnet") -> SwiftDashSDK.PersistentIdentity { - // Store special keys in keychain first - var votingKeyId: String? = nil - var ownerKeyId: String? = nil - var payoutKeyId: String? = nil - - if let votingKey = model.votingPrivateKey { - votingKeyId = KeychainManager.shared.storeSpecialKey(votingKey, identityId: model.id, keyType: .voting) - } - if let ownerKey = model.ownerPrivateKey { - ownerKeyId = KeychainManager.shared.storeSpecialKey(ownerKey, identityId: model.id, keyType: .owner) - } - if let payoutKey = model.payoutPrivateKey { - payoutKeyId = KeychainManager.shared.storeSpecialKey(payoutKey, identityId: model.id, keyType: .payout) - } - - let persistent = SwiftDashSDK.PersistentIdentity( - identityId: model.id, - balance: Int64(model.balance), - revision: 0, - isLocal: model.isLocal, - alias: model.alias, - dpnsName: model.dpnsName, - mainDpnsName: model.mainDpnsName, - identityType: model.type, - votingPrivateKeyIdentifier: votingKeyId, - ownerPrivateKeyIdentifier: ownerKeyId, - payoutPrivateKeyIdentifier: payoutKeyId, - network: network - ) - - // Add public keys - for publicKey in model.publicKeys { - if let persistentKey = SwiftDashSDK.PersistentPublicKey.from(publicKey, identityId: model.idString) { - persistent.addPublicKey(persistentKey) - } - } - - // Handle private keys - match them to their corresponding public keys using cryptographic validation - for privateKeyData in model.privateKeys { - if let matchingPublicKey = KeyValidation.matchPrivateKeyToPublicKeys( - privateKeyData: privateKeyData, - publicKeys: model.publicKeys, - isTestnet: network == "testnet" - ) { - if let persistentKey = persistent.publicKeys.first(where: { $0.keyId == matchingPublicKey.id }) { - if let keychainId = KeychainManager.shared.storePrivateKey(privateKeyData, identityId: model.id, keyIndex: persistentKey.keyId) { - persistentKey.privateKeyKeychainIdentifier = keychainId - } - } - } - } - - return persistent - } - - /// Create from DPPIdentity - static func from(_ dppIdentity: DPPIdentity, alias: String? = nil, type: SwiftDashSDK.IdentityType = .user, network: String = "testnet") -> SwiftDashSDK.PersistentIdentity { - let persistent = SwiftDashSDK.PersistentIdentity( - identityId: dppIdentity.id, - balance: Int64(dppIdentity.balance), - revision: Int64(dppIdentity.revision), - isLocal: false, - alias: alias, - identityType: type, - network: network - ) - - // Add public keys - for (_, publicKey) in dppIdentity.publicKeys { - if let persistentKey = SwiftDashSDK.PersistentPublicKey.from(publicKey, identityId: dppIdentity.idString) { - persistent.addPublicKey(persistentKey) - } - } - - return persistent - } -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentIndex.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentIndex.swift deleted file mode 100644 index 1d02d5e2f84..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentIndex.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation -import SwiftData -import SwiftDashSDK - -// Re-export SDK type for backward compatibility -public typealias PersistentIndex = SwiftDashSDK.PersistentIndex diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentKeyword.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentKeyword.swift deleted file mode 100644 index 8bb64b0117b..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentKeyword.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation -import SwiftData -import SwiftDashSDK - -// Re-export SDK type for backward compatibility -public typealias PersistentKeyword = SwiftDashSDK.PersistentKeyword diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentProperty.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentProperty.swift deleted file mode 100644 index 0af1df6aa89..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentProperty.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation -import SwiftData -import SwiftDashSDK - -// Re-export SDK type for backward compatibility -public typealias PersistentProperty = SwiftDashSDK.PersistentProperty diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentPublicKey.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentPublicKey.swift deleted file mode 100644 index 0a88a46fb98..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentPublicKey.swift +++ /dev/null @@ -1,46 +0,0 @@ -import Foundation -import SwiftData -import SwiftDashSDK - -// Re-export SDK type for backward compatibility -public typealias PersistentPublicKey = SwiftDashSDK.PersistentPublicKey - -// App-specific extensions that depend on KeychainManager -extension SwiftDashSDK.PersistentPublicKey { - /// Check if this public key has an associated private key available in keychain - @MainActor - var hasPrivateKey: Bool { - privateKeyKeychainIdentifier != nil && isPrivateKeyAvailable - } - - /// Check if the private key is still available in keychain - @MainActor - var isPrivateKeyAvailable: Bool { - guard privateKeyKeychainIdentifier != nil else { return false } - return KeychainManager.shared.hasPrivateKey(identityId: Data.identifier(fromBase58: identityId) ?? Data(), keyIndex: keyId) - } - - /// Retrieve the private key data from keychain - @MainActor - func getPrivateKeyData() -> Data? { - guard let identityData = Data.identifier(fromBase58: identityId) else { return nil } - return KeychainManager.shared.retrievePrivateKey(identityId: identityData, keyIndex: keyId) - } - - /// Store a private key for this public key - @MainActor - func setPrivateKey(_ privateKeyData: Data) { - guard let identityData = Data.identifier(fromBase58: identityId) else { return } - if let keychainId = KeychainManager.shared.storePrivateKey(privateKeyData, identityId: identityData, keyIndex: keyId) { - self.privateKeyKeychainIdentifier = keychainId - } - } - - /// Remove the private key from keychain - @MainActor - func removePrivateKey() { - guard let identityData = Data.identifier(fromBase58: identityId) else { return } - _ = KeychainManager.shared.deletePrivateKey(identityId: identityData, keyIndex: keyId) - self.privateKeyKeychainIdentifier = nil - } -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentToken.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentToken.swift deleted file mode 100644 index 1f72e7bddce..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentToken.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation -import SwiftData -import SwiftDashSDK - -// Re-export SDK types for backward compatibility -public typealias PersistentToken = SwiftDashSDK.PersistentToken -public typealias TokenLocalization = SwiftDashSDK.TokenLocalization -public typealias ChangeControlRules = SwiftDashSDK.ChangeControlRules -public typealias TokenPerpetualDistribution = SwiftDashSDK.TokenPerpetualDistribution -public typealias TokenPreProgrammedDistribution = SwiftDashSDK.TokenPreProgrammedDistribution -public typealias DistributionEvent = SwiftDashSDK.DistributionEvent -public typealias TokenDistributionChangeRules = SwiftDashSDK.TokenDistributionChangeRules -public typealias AuthorizedActionTakers = SwiftDashSDK.AuthorizedActionTakers -public typealias TokenTradeMode = SwiftDashSDK.TokenTradeMode -public typealias ControlRuleType = SwiftDashSDK.ControlRuleType -public typealias ChangeControlRuleType = SwiftDashSDK.ChangeControlRuleType diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentTokenBalance.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentTokenBalance.swift deleted file mode 100644 index 6a3727270fb..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentTokenBalance.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation -import SwiftData -import SwiftDashSDK - -// Re-export SDK type for backward compatibility -public typealias PersistentTokenBalance = SwiftDashSDK.PersistentTokenBalance diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentTokenHistoryEvent.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentTokenHistoryEvent.swift deleted file mode 100644 index 0b2de5990e8..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Models/SwiftData/PersistentTokenHistoryEvent.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation -import SwiftData -import SwiftDashSDK - -// Re-export SDK types for backward compatibility -public typealias PersistentTokenHistoryEvent = SwiftDashSDK.PersistentTokenHistoryEvent -public typealias TokenEventType = SwiftDashSDK.TokenEventType diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SDK/IdentityBalanceExample.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SDK/IdentityBalanceExample.swift deleted file mode 100644 index 40cd91c6037..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SDK/IdentityBalanceExample.swift +++ /dev/null @@ -1,59 +0,0 @@ -import Foundation -import SwiftDashSDK - -// Example of using the new Data-based fetchBalances API - -func exampleFetchBalances(sdk: SDK) async throws { - // Example 1: Using Data objects directly (recommended for secp256k1 compatibility) - - // Create identity IDs as Data objects (32 bytes each) - let id1 = Data(hexString: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")! - let id2 = Data(hexString: "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210")! - - // Fetch balances using Data objects - let balances = try sdk.identities.fetchBalances(ids: [id1, id2]) - - // Process results - for (idData, balance) in balances { - let idHex = idData.toHexString() - if let balance = balance { - print("Identity \(idHex) has balance: \(balance)") - } else { - print("Identity \(idHex) not found") - } - } - - // Example 2: Using string IDs (convenience method) - - let stringIds = [ - "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", - "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210" - ] - - let dataIds = stringIds.compactMap { Data(hexString: $0) } - let stringBalances = try sdk.identities.fetchBalances(ids: dataIds) - - for (id, balance) in stringBalances { - if let balance = balance { - print("Identity \(id) has balance: \(balance)") - } else { - print("Identity \(id) not found") - } - } -} - - -// Example with secp256k1 integration -// When using swift-secp256k1, you typically have keys/identifiers as 32-byte arrays -// You can convert them to Data for use with fetchBalances: - -func exampleWithSecp256k1() async throws { - // Assuming you have a secp256k1 public key or identifier - // let secp256k1Bytes: [UInt8] = [...] // 32 bytes from secp256k1 - - // Convert to Data - // let identityData = Data(secp256k1Bytes) - - // Use with fetchBalances - // let balances = try sdk.identities.fetchBalances(ids: [identityData]) -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SDK/SDKExtensions.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SDK/SDKExtensions.swift deleted file mode 100644 index be9d60f6ec8..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SDK/SDKExtensions.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation -import SwiftDashSDK - -// Re-export SDK types for backward compatibility -// The Signer protocol and TestSigner are now in SwiftDashSDK -public typealias Signer = SwiftDashSDK.Signer -public typealias TestSigner = SwiftDashSDK.TestSigner diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Services/KeychainManager.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Services/KeychainManager.swift deleted file mode 100644 index 06f50ccde48..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Services/KeychainManager.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation -import SwiftDashSDK - -/// App-specific KeychainManager that uses the legacy service name for data continuity. -/// This ensures existing keys stored under "com.dash.swiftexampleapp.keys" remain accessible. -/// -/// New apps should use `SwiftDashSDK.KeychainManager` directly with their own service name. -@MainActor -final class KeychainManager { - /// Shared instance using the app's legacy service name - static let shared = SwiftDashSDK.KeychainManager( - serviceName: "com.dash.swiftexampleapp.keys" - ) -} - -// Re-export SpecialKeyType for backwards compatibility -// (KeychainError is not used in the app, so no need to re-export) -typealias SpecialKeyType = SwiftDashSDK.SpecialKeyType diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SwiftExampleAppApp.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SwiftExampleAppApp.swift index b35e0251681..f81f56613c8 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SwiftExampleAppApp.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SwiftExampleAppApp.swift @@ -14,7 +14,6 @@ import SwiftDashSDK @main struct SwiftExampleAppApp: App { @StateObject private var unifiedState = UnifiedAppState() - @State private var shouldResetApp = false init() { // Suppress auto layout constraint warnings in debug builds @@ -26,45 +25,19 @@ struct SwiftExampleAppApp: App { var body: some Scene { WindowGroup { - if shouldResetApp { - // Show reset view - VStack(spacing: 20) { - ProgressView("Resetting app...") - .scaleEffect(1.5) - Text("The app is being reset to its initial state.") - .font(.caption) - .foregroundColor(.secondary) + ContentView() + .environmentObject(unifiedState) + .environmentObject(unifiedState.walletService) + .environmentObject(unifiedState.platformState) + .environmentObject(unifiedState.shieldedService) + .environmentObject(unifiedState.platformBalanceSyncService) + .environmentObject(unifiedState.zkSyncService) + .environment(\.modelContext, unifiedState.modelContainer.mainContext) + .task { + SDKLogger.log("🚀 SwiftExampleApp: Starting initialization...", minimumLevel: .medium) + await unifiedState.initialize() + SDKLogger.log("🚀 SwiftExampleApp: Initialization complete", minimumLevel: .medium) } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .onAppear { - Task { - try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second - await resetAppState() - } - } - } else { - ContentView() - .environmentObject(unifiedState) - .environmentObject(unifiedState.walletService) - .environmentObject(unifiedState.platformState) - .environmentObject(unifiedState.unifiedState) - .environmentObject(unifiedState.shieldedService) - .environmentObject(unifiedState.platformBalanceSyncService) - .environmentObject(unifiedState.zkSyncService) - .environment(\.modelContext, unifiedState.modelContainer.mainContext) - .task { - SDKLogger.log("🚀 SwiftExampleApp: Starting initialization...", minimumLevel: .medium) - await unifiedState.initialize() - SDKLogger.log("🚀 SwiftExampleApp: Initialization complete", minimumLevel: .medium) - } - } } } - - @MainActor - private func resetAppState() async { - await unifiedState.reset() - await unifiedState.initialize() - shouldResetApp = false - } } diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/UnifiedAppState.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/UnifiedAppState.swift index 3b10c146cc2..f1836862749 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/UnifiedAppState.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/UnifiedAppState.swift @@ -38,9 +38,6 @@ class UnifiedAppState: ObservableObject { // State from Platform let platformState: AppState - // Unified state manager - let unifiedState: UnifiedStateManager - // SwiftData container let modelContainer: ModelContainer @@ -69,8 +66,6 @@ class UnifiedAppState: ObservableObject { // Initialize services self.platformState = AppState() self.walletService = WalletService(modelContainer: modelContainer, network: platformState.currentNetwork) - // Initialize unified state (will be updated with real SDKs during async init) - self.unifiedState = UnifiedStateManager() } func initialize() async { diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Utils/TestKeyGenerator.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Utils/TestKeyGenerator.swift deleted file mode 100644 index 3e569e669ef..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Utils/TestKeyGenerator.swift +++ /dev/null @@ -1,46 +0,0 @@ -import Foundation -import CryptoKit - -/// Test key generator for demo purposes only -/// DO NOT USE IN PRODUCTION - This generates deterministic keys which are insecure -struct TestKeyGenerator { - - /// Generate a deterministic private key from identity ID (FOR DEMO ONLY) - static func generateTestPrivateKey(identityId: Data, keyIndex: UInt32, purpose: UInt8) -> Data { - // Create deterministic seed from identity ID, key index, and purpose - var seedData = Data() - seedData.append(identityId) - seedData.append(contentsOf: withUnsafeBytes(of: keyIndex) { Data($0) }) - seedData.append(purpose) - - // Use SHA256 to generate a 32-byte private key - let hash = SHA256.hash(data: seedData) - return Data(hash) - } - - /// Generate test private keys for an identity - static func generateTestPrivateKeys(identityId: Data) -> [String: Data] { - var keys: [String: Data] = [:] - - // Generate keys for different purposes - // Key 0: Master key (not used in state transitions) - keys["0"] = generateTestPrivateKey(identityId: identityId, keyIndex: 0, purpose: 0) - - // Key 1: Authentication key (HIGH security) - keys["1"] = generateTestPrivateKey(identityId: identityId, keyIndex: 1, purpose: 0) - - // Key 2: Transfer key (CRITICAL security, purpose 3 = TRANSFER) - keys["2"] = generateTestPrivateKey(identityId: identityId, keyIndex: 2, purpose: 3) - - // Key 3: Another transfer key (some identities might have transfer key at index 3) - keys["3"] = generateTestPrivateKey(identityId: identityId, keyIndex: 3, purpose: 3) - - return keys - } - - /// Get private key for a specific key ID - static func getPrivateKey(identityId: Data, keyId: UInt32) -> Data? { - let keys = generateTestPrivateKeys(identityId: identityId) - return keys[String(keyId)] - } -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Version.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Version.swift deleted file mode 100644 index 829f09fce13..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Version.swift +++ /dev/null @@ -1,6 +0,0 @@ -// Auto-generated file - DO NOT EDIT -// Generated at build time with git commit hash - -struct AppVersion { - static let gitCommit = "814df" -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/ViewModels/BaseViewModel.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/ViewModels/BaseViewModel.swift index ae70bb6186c..9e75ec1987e 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/ViewModels/BaseViewModel.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/ViewModels/BaseViewModel.swift @@ -12,11 +12,6 @@ class BaseViewModel: ObservableObject { @Published var showResult = false @Published var currentError: UserFacingError? - /// Whether the current error is retryable - var isErrorRetryable: Bool { - currentError?.isRetryable ?? false - } - /// Recovery suggestion for the current error var errorRecoverySuggestion: String? { currentError?.recoverySuggestion diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DataContractDetailsView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DataContractDetailsView.swift index cf4fb92b7fb..231a37fde00 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DataContractDetailsView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DataContractDetailsView.swift @@ -1,5 +1,5 @@ import SwiftUI -import SwiftData +import SwiftDashSDK import UIKit struct DataContractDetailsView: View { diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentFieldsView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentFieldsView.swift index edd627c9fe7..20d0fcb9ebd 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentFieldsView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentFieldsView.swift @@ -1,4 +1,5 @@ import SwiftUI +import SwiftDashSDK import SwiftData struct DocumentFieldsView: View { diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentTypeDetailsView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentTypeDetailsView.swift index b1f0c77c315..79d7b7e5ca0 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentTypeDetailsView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentTypeDetailsView.swift @@ -1,4 +1,5 @@ import SwiftUI +import SwiftDashSDK import SwiftData struct DocumentTypeDetailsView: View { diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift index 3dafa69bcd7..072c1ce692f 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift @@ -1,97 +1,6 @@ import SwiftUI import SwiftDashSDK -struct DocumentsView: View { - @EnvironmentObject var appState: AppState - @State private var showingCreateDocument = false - @State private var selectedDocument: DocumentModel? - - var body: some View { - NavigationView { - List { - if appState.documents.isEmpty { - EmptyStateView( - systemImage: "doc.text", - title: "No Documents", - message: "Create documents to see them here" - ) - .listRowBackground(Color.clear) - } else { - ForEach(appState.documents) { document in - DocumentRow(document: document) { - selectedDocument = document - } - } - .onDelete { indexSet in - deleteDocuments(at: indexSet) - } - } - } - .navigationTitle("Documents") - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button(action: { showingCreateDocument = true }) { - Image(systemName: "plus") - } - } - } - .sheet(isPresented: $showingCreateDocument) { - CreateDocumentView() - .environmentObject(appState) - } - .sheet(item: $selectedDocument) { document in - DocumentDetailView(document: document) - } - .onAppear { - if appState.documents.isEmpty { - loadSampleDocuments() - } - } - } - } - - private func loadSampleDocuments() { - // Add sample documents for demonstration - appState.documents = [ - DocumentModel( - id: "doc1", - contractId: "dpns-contract", - documentType: "domain", - ownerId: Data(hexString: "1111111111111111111111111111111111111111111111111111111111111111")!, - data: [ - "label": "alice", - "normalizedLabel": "alice", - "normalizedParentDomainName": "dash" - ], - createdAt: Date(), - updatedAt: Date() - ), - DocumentModel( - id: "doc2", - contractId: "dashpay-contract", - documentType: "profile", - ownerId: Data(hexString: "2222222222222222222222222222222222222222222222222222222222222222")!, - data: [ - "displayName": "Bob", - "publicMessage": "Hello from Bob!" - ], - createdAt: Date(), - updatedAt: Date() - ) - ] - } - - private func deleteDocuments(at offsets: IndexSet) { - for index in offsets { - if index < appState.documents.count { - let document = appState.documents[index] - // In a real app, we would delete the document - appState.documents.removeAll { $0.id == document.id } - } - } - } -} - struct DocumentRow: View { let document: DocumentModel let onTap: () -> Void @@ -130,226 +39,6 @@ struct DocumentRow: View { } } -struct DocumentDetailView: View { - let document: DocumentModel - @Environment(\.dismiss) var dismiss - - var body: some View { - NavigationView { - ScrollView { - VStack(alignment: .leading, spacing: 16) { - Section { - VStack(alignment: .leading, spacing: 8) { - DetailRow(label: "Document Type", value: document.documentType) - DetailRow(label: "Document ID", value: document.id) - DetailRow(label: "Contract ID", value: document.contractId) - DetailRow(label: "Owner ID", value: document.ownerIdString) - - if let createdAt = document.createdAt { - DetailRow(label: "Created", value: createdAt.formatted()) - } - - if let updatedAt = document.updatedAt { - DetailRow(label: "Updated", value: updatedAt.formatted()) - } - } - .padding() - .background(Color.gray.opacity(0.1)) - .cornerRadius(10) - } - - Section { - VStack(alignment: .leading, spacing: 8) { - Text("Document Data") - .font(.headline) - - Text(document.formattedData) - .font(.system(.caption, design: .monospaced)) - .padding() - .background(Color.gray.opacity(0.1)) - .cornerRadius(8) - } - .padding() - } - } - } - .navigationTitle("Document Details") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button("Done") { - dismiss() - } - } - } - } - } -} - -struct CreateDocumentView: View { - @EnvironmentObject var appState: AppState - @Environment(\.dismiss) var dismiss - @State private var selectedContract: ContractModel? - @State private var selectedDocumentType = "" - @State private var selectedOwnerId: String = "" - @State private var dataKeyToAdd = "" - @State private var dataValueToAdd = "" - @State private var documentData: [String: Any] = [:] - @State private var isLoading = false - - var body: some View { - NavigationView { - Form { - Section(header: Text("Document Configuration")) { - Picker("Contract", selection: $selectedContract) { - Text("Select a contract").tag(nil as ContractModel?) - ForEach(appState.contracts) { contract in - Text(contract.name).tag(contract as ContractModel?) - } - } - - if let contract = selectedContract { - Picker("Document Type", selection: $selectedDocumentType) { - Text("Select type").tag("") - ForEach(contract.documentTypes, id: \.self) { type in - Text(type).tag(type) - } - } - } - - Picker("Owner", selection: $selectedOwnerId) { - Text("Select owner").tag("") - ForEach(appState.identities) { identity in - Text(identity.alias ?? identity.idString) - .tag(identity.idString) - } - } - } - - Section("Document Data") { - ForEach(Array(documentData.keys), id: \.self) { key in - HStack { - Text(key) - .font(.caption) - .foregroundColor(.secondary) - Spacer() - Text("\(String(describing: documentData[key] ?? ""))") - .font(.subheadline) - } - } - - HStack { - TextField("Key", text: $dataKeyToAdd) - .textFieldStyle(RoundedBorderTextFieldStyle()) - TextField("Value", text: $dataValueToAdd) - .textFieldStyle(RoundedBorderTextFieldStyle()) - Button("Add") { - if !dataKeyToAdd.isEmpty && !dataValueToAdd.isEmpty { - documentData[dataKeyToAdd] = dataValueToAdd - dataKeyToAdd = "" - dataValueToAdd = "" - } - } - } - } - } - .navigationTitle("Create Document") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button("Cancel") { - dismiss() - } - } - ToolbarItem(placement: .navigationBarTrailing) { - Button("Create") { - Task { - await createDocument() - dismiss() - } - } - .disabled(selectedContract == nil || - selectedDocumentType.isEmpty || - selectedOwnerId.isEmpty || - isLoading) - } - } - .onAppear { - if appState.contracts.isEmpty { - // Load sample contracts if needed - loadSampleContracts() - } - } - } - } - - private func createDocument() async { - guard appState.sdk != nil, - let contract = selectedContract, - !selectedDocumentType.isEmpty else { - appState.showError(message: "Please select a contract and document type") - return - } - - isLoading = true - - // In a real app, we would use the SDK's document creation functionality - let document = DocumentModel( - id: UUID().uuidString, - contractId: contract.id, - documentType: selectedDocumentType, - ownerId: Data(hexString: selectedOwnerId) ?? Data(), - data: documentData, - createdAt: Date(), - updatedAt: Date() - ) - - appState.documents.append(document) - appState.showError(message: "Document created successfully") - - isLoading = false - } - - private func loadSampleContracts() { - // Add sample contracts for demonstration - appState.contracts = [ - ContractModel( - id: "dpns-contract", - name: "DPNS", - version: 1, - ownerId: Data(hexString: "0000000000000000000000000000000000000000000000000000000000000000") ?? Data(), - documentTypes: ["domain", "preorder"], - schema: [ - "domain": [ - "type": "object", - "properties": [ - "label": ["type": "string"], - "normalizedLabel": ["type": "string"], - "normalizedParentDomainName": ["type": "string"] - ] - ] - ] - ), - ContractModel( - id: "dashpay-contract", - name: "DashPay", - version: 1, - ownerId: Data(hexString: "0000000000000000000000000000000000000000000000000000000000000000") ?? Data(), - documentTypes: ["profile", "contactRequest"], - schema: [ - "profile": [ - "type": "object", - "properties": [ - "displayName": ["type": "string"], - "publicMessage": ["type": "string"] - ] - ] - ] - ) - ] - } -} - struct DetailRow: View { let label: String let value: String diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/EmptyStateView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/EmptyStateView.swift new file mode 100644 index 00000000000..093781e2f6f --- /dev/null +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/EmptyStateView.swift @@ -0,0 +1,27 @@ +import SwiftUI +import SwiftDashSDK + +struct EmptyStateView: View { + let systemImage: String + let title: String + let message: String + + var body: some View { + VStack(spacing: 20) { + Image(systemName: systemImage) + .font(.system(size: 60)) + .foregroundColor(.gray) + + Text(title) + .font(.title2) + .fontWeight(.semibold) + + Text(message) + .font(.body) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal) + } + .padding() + } +} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/IdentitiesView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/IdentitiesView.swift index 2e0fb9c3335..a42d7825660 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/IdentitiesView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/IdentitiesView.swift @@ -1,145 +1,6 @@ import SwiftUI import SwiftDashSDK -struct IdentitiesView: View { - @EnvironmentObject var appState: AppState - @State private var showingLoadIdentity = false - - var body: some View { - NavigationView { - if appState.identities.isEmpty { - // Empty state view - VStack(spacing: 20) { - Spacer() - - Image(systemName: "person.crop.circle.badge.plus") - .font(.system(size: 60)) - .foregroundColor(.gray) - - Text("No Identities") - .font(.title2) - .fontWeight(.semibold) - - Text("Create or load an identity to get started\nwith Dash Platform") - .multilineTextAlignment(.center) - .foregroundColor(.secondary) - - Button(action: { showingLoadIdentity = true }) { - Label("Load Identity", systemImage: "square.and.arrow.down") - .padding(.horizontal, 20) - .padding(.vertical, 10) - } - .buttonStyle(.borderedProminent) - - Spacer() - } - .navigationTitle("Identities") - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button(action: { showingLoadIdentity = true }) { - Image(systemName: "square.and.arrow.down") - } - } - } - .sheet(isPresented: $showingLoadIdentity) { - LoadIdentityView() - .environmentObject(appState) - } - } else { - List { - ForEach(appState.identities) { identity in - IdentityRow(identity: identity) - } - .onDelete { indexSet in - deleteIdentities(at: indexSet) - } - } - .navigationTitle("Identities") - .refreshable { - await refreshAllBalances() - } - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button(action: { showingLoadIdentity = true }) { - Image(systemName: "square.and.arrow.down") - } - } - } - .sheet(isPresented: $showingLoadIdentity) { - LoadIdentityView() - .environmentObject(appState) - } - } - } - } - - private func refreshAllBalances() async { - guard appState.sdk != nil else { return } - - // Get all non-local identities - let nonLocalIdentities = appState.identities.filter { !$0.isLocal } - - guard !nonLocalIdentities.isEmpty else { return } - - // Fetch each identity's balance and DPNS name - await withTaskGroup(of: Void.self) { group in - for identity in nonLocalIdentities { - // Capture only Sendable primitives for the concurrent task - let identityId = identity.id - let identityIdString = identity.idString - let needsDPNS = (identity.dpnsName == nil && identity.mainDpnsName == nil) - - group.addTask { - do { - // Perform SDK calls and state updates on the main actor - try await Task { @MainActor in - guard let sdk = appState.sdk else { return } - - let fetchedIdentity = try await sdk.identityGet(identityId: identityIdString) - - if let balanceValue = fetchedIdentity["balance"] { - let newBalanceLocal: UInt64 = { - if let balanceNum = balanceValue as? NSNumber { - return balanceNum.uint64Value - } else if let balanceString = balanceValue as? String, - let balanceUInt = UInt64(balanceString) { - return balanceUInt - } else { - return 0 - } - }() - appState.updateIdentityBalance(id: identityId, newBalance: newBalanceLocal) - } - - if needsDPNS { - do { - let usernames = try await sdk.dpnsGetUsername(identityId: identityIdString, limit: 1) - if let firstUsername = usernames.first, - let label = firstUsername["label"] as? String { - appState.updateIdentityDPNSName(id: identityId, dpnsName: label) - } - } catch { - // ignore - } - } - }.value - } catch { - print("Failed to refresh identity \(identityIdString): \(error)") - } - } - } - } - } - - private func deleteIdentities(at offsets: IndexSet) { - for index in offsets { - if index < appState.identities.count { - appState.removeIdentity(appState.identities[index]) - } - } - } -} - struct IdentityRow: View { let identity: IdentityModel @EnvironmentObject var appState: AppState diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/LocalDataContractsView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/LocalDataContractsView.swift deleted file mode 100644 index af25c37c30f..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/LocalDataContractsView.swift +++ /dev/null @@ -1,499 +0,0 @@ -import SwiftUI -import SwiftData -import SwiftDashSDK - -struct LocalDataContractsView: View { - @EnvironmentObject var unifiedState: UnifiedAppState - @Query(sort: \PersistentDataContract.lastAccessedAt, order: .reverse) - private var dataContracts: [PersistentDataContract] - - @State private var showingLoadContract = false - @State private var isLoading = false - @State private var errorMessage: String? - @State private var showError = false - - @Environment(\.modelContext) private var modelContext - - var body: some View { - List { - if dataContracts.isEmpty { - VStack(spacing: 20) { - Image(systemName: "doc.text") - .font(.system(size: 60)) - .foregroundColor(.secondary) - - Text("No Local Contracts") - .font(.title2) - .fontWeight(.semibold) - - Text("Load data contracts from the network to use them offline") - .font(.subheadline) - .foregroundColor(.secondary) - .multilineTextAlignment(.center) - .padding(.horizontal) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .listRowBackground(Color.clear) - .listRowInsets(EdgeInsets()) - } else { - ForEach(dataContracts) { contract in - DataContractRow(contract: contract) - } - .onDelete(perform: deleteContracts) - } - } - .navigationTitle("Local Data Contracts") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button(action: { showingLoadContract = true }) { - Label("Load Contract", systemImage: "arrow.down.circle") - } - .disabled(isLoading) - } - } - .sheet(isPresented: $showingLoadContract) { - LoadDataContractView(isLoading: $isLoading) - .environmentObject(unifiedState) - .environment(\.modelContext, modelContext) - } - .alert("Error", isPresented: $showError) { - Button("OK") { } - } message: { - Text(errorMessage ?? "Unknown error occurred") - } - } - - private func deleteContracts(at offsets: IndexSet) { - for index in offsets { - modelContext.delete(dataContracts[index]) - } - - do { - try modelContext.save() - } catch { - errorMessage = "Failed to delete contract: \(error.localizedDescription)" - showError = true - } - } -} - -struct DataContractRow: View { - let contract: PersistentDataContract - @State private var showingDetails = false - - var displayName: String { - // Check if this is a token-only contract - if let tokens = contract.tokens, - tokens.count == 1, - let documentTypes = contract.documentTypes, - documentTypes.isEmpty, - let token = tokens.first { - // Use the token's singular form for display - if let singularName = token.getSingularForm(languageCode: "en") { - return "\(singularName) Token Contract" - } else { - return "Token Contract" - } - } - - // Otherwise use the stored name - return contract.name - } - - var body: some View { - Button(action: { showingDetails = true }) { - VStack(alignment: .leading, spacing: 4) { - HStack { - Text(displayName) - .font(.headline) - .foregroundColor(.primary) - Spacer() - Image(systemName: "chevron.right") - .font(.caption) - .foregroundColor(.secondary) - } - - Text(contract.idBase58) - .font(.caption) - .foregroundColor(.secondary) - .lineLimit(1) - .truncationMode(.middle) - - HStack { - Text("Size: \(ByteCountFormatter.string(fromByteCount: Int64(contract.serializedContract.count), countStyle: .binary))") - .font(.caption2) - .foregroundColor(.secondary) - - Spacer() - - Text("Last used: \(contract.lastAccessedAt, style: .relative)") - .font(.caption2) - .foregroundColor(.secondary) - } - } - .padding(.vertical, 4) - } - .buttonStyle(PlainButtonStyle()) - .sheet(isPresented: $showingDetails) { - DataContractDetailsView(contract: contract) - } - } -} - -struct LoadDataContractView: View { - @EnvironmentObject var unifiedState: UnifiedAppState - @Environment(\.dismiss) var dismiss - @Environment(\.modelContext) private var modelContext - @Binding var isLoading: Bool - - @Query private var existingContracts: [PersistentDataContract] - - @State private var contractId = "" - @State private var contractName = "" - @State private var errorMessage: String? - @State private var showError = false - @State private var fetchedContract: [String: Any]? - @State private var showExampleContracts = false - @State private var currentNetwork: String = "Unknown" - - // Known testnet contracts - these are the common system contracts - let exampleContracts = [ - ("DPNS Contract", "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec"), - ("DashPay Contract", "Bwr4WHCPz5rFVAD87RqTs3izo4zpzwsEdKPWUT1NS1C7"), - ("Withdrawals Contract", "4fJLR2GYTPFdomuTVvNy3VRrvWgvkKPzqehEBpNf2nk6"), - ("Wallet Utils", "7CSFGeF4WNzgDmx94zwvHkYaG3Dx4XEe5LFsFgJswLbm"), - ("Token History", "43gujrzZgXqcKBiScLa4T8XTDnRhenR9BLx8GWVHjPxF"), - ("Keyword Search", "BsjE6tQxG47wffZCRQCovFx5rYrAYYC3rTVRWKro27LA") - ] - - var body: some View { - NavigationView { - Form { - Section(footer: Text("Connected to: \(unifiedState.platformState.currentNetwork.rawValue)")) { - EmptyView() - } - - Section("Contract Details") { - HStack { - TextField("Contract ID (Base58)", text: $contractId) - .textContentType(.none) - .autocapitalization(.none) - .disabled(isLoading) - - Button(action: { showExampleContracts.toggle() }) { - Image(systemName: "list.bullet") - .foregroundColor(.blue) - } - .disabled(isLoading) - } - - TextField("Name (Optional)", text: $contractName) - .textContentType(.none) - .disabled(isLoading) - - if showExampleContracts { - Section(header: Text("Common System Contracts (\(unifiedState.platformState.currentNetwork.rawValue))")) { - ForEach(exampleContracts, id: \.1) { example in - Button(action: { - contractId = example.1 - contractName = example.0 - showExampleContracts = false - }) { - HStack { - VStack(alignment: .leading) { - Text(example.0) - .font(.subheadline) - Text(example.1) - .font(.caption) - .foregroundColor(.secondary) - .lineLimit(1) - .truncationMode(.middle) - } - Spacer() - Image(systemName: "arrow.right.circle") - .foregroundColor(.secondary) - } - } - .disabled(isLoading) - } - } - } - } - - if isLoading { - Section { - HStack { - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - Text("Loading contract from network...") - .foregroundColor(.secondary) - } - } - } - - if let contract = fetchedContract { - Section("Fetched Contract") { - if let id = contract["id"] as? String { - HStack { - Text("ID") - .foregroundColor(.secondary) - Spacer() - Text(id) - .font(.caption) - .lineLimit(1) - .truncationMode(.middle) - } - } - - if let schema = contract["schema"] as? [String: Any], - let documentTypes = schema["documents"] as? [String: Any] { - HStack { - Text("Document Types") - .foregroundColor(.secondary) - Spacer() - Text("\(documentTypes.count)") - } - } - } - } - } - .navigationTitle("Load Data Contract") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button("Cancel") { - dismiss() - } - .disabled(isLoading) - } - - ToolbarItem(placement: .navigationBarTrailing) { - Button("Load") { - Task { - await loadContract() - } - } - .disabled(contractId.isEmpty || isLoading) - } - } - .alert("Error", isPresented: $showError) { - Button("OK") { } - } message: { - Text(errorMessage ?? "Unknown error occurred") - } - } - } - - private func loadContract() async { - guard let sdk = unifiedState.sdk else { - errorMessage = "SDK not initialized" - showError = true - return - } - - await MainActor.run { - isLoading = true - } - - do { - // Validate contract ID - let trimmedId = contractId.trimmingCharacters(in: .whitespacesAndNewlines) - - print("🔵 Attempting to load contract with ID: \(trimmedId)") - - // Basic validation - just check it's not empty - guard !trimmedId.isEmpty else { - await MainActor.run { - errorMessage = "Please enter a contract ID" - showError = true - isLoading = false - } - return - } - - // Fetch the contract with both JSON and binary serialization - guard let handle = sdk.handle else { - throw SDKError.invalidState("SDK not initialized") - } - - let result = trimmedId.withCString { idCStr in - dash_sdk_data_contract_fetch_with_serialization(handle, idCStr, true, true) - } - - // Check for error - if let error = result.error { - let errorMessage = error.pointee.message != nil ? String(cString: error.pointee.message!) : "Unknown error" - dash_sdk_error_free(error) - throw SDKError.internalError("Failed to fetch data contract: \(errorMessage)") - } - - // Get the JSON string - guard result.json_string != nil else { - throw SDKError.internalError("No JSON data returned from contract fetch") - } - - let jsonString = String(cString: result.json_string!) - - // Get the binary serialization - var binaryData: Data? = nil - if result.serialized_data != nil && result.serialized_data_len > 0 { - binaryData = Data(bytes: result.serialized_data, count: Int(result.serialized_data_len)) - } - - // Clean up the contract handle if it was returned - defer { - if result.contract_handle != nil { - dash_sdk_data_contract_destroy(result.contract_handle) - } - } - - // Parse the JSON - guard let jsonData = jsonString.data(using: String.Encoding.utf8), - let contractData = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else { - throw SDKError.serializationError("Failed to parse contract JSON") - } - - print("✅ Contract fetched successfully") - if let binaryData = binaryData { - print("📦 Binary serialization size: \(binaryData.count) bytes") - } - - // Add the contract to the trusted context if we have binary data - if let binaryData = binaryData, - let contractId = contractData["id"] as? String { - do { - try sdk.addContractToContext(contractId: contractId, binaryData: binaryData) - print("✅ Added contract to trusted context provider") - } catch { - print("⚠️ Failed to add contract to trusted context: \(error)") - // Continue even if adding to context fails - } - } else { - print("⚠️ No binary data available to add contract to trusted context") - } - - await MainActor.run { - fetchedContract = contractData - } - - // Store the JSON for the contract - let serializedContract = jsonData - - // Get the contract ID from the response or convert from the input - let contractIdData: Data - if let idString = contractData["id"] as? String, - let idData = Data.identifier(fromBase58: idString) ?? Data(hexString: idString) { - contractIdData = idData - } else { - // Fall back to converting the input ID - guard let idData = Data.identifier(fromBase58: trimmedId) else { - await MainActor.run { - errorMessage = "Could not extract contract ID from response" - showError = true - isLoading = false - } - return - } - contractIdData = idData - } - - // Check if contract already exists - if existingContracts.contains(where: { $0.id == contractIdData }) { - await MainActor.run { - errorMessage = "This contract is already saved locally" - showError = true - isLoading = false - } - return - } - - // Determine name - var finalName = contractName.trimmingCharacters(in: .whitespacesAndNewlines) - if finalName.isEmpty { - // Check if it's a token-only contract - let documents = contractData["documents"] as? [String: Any] ?? contractData["documentSchemas"] as? [String: Any] ?? [:] - let tokens = contractData["tokens"] as? [String: Any] ?? [:] - - if documents.isEmpty && tokens.count == 1, - let tokenData = tokens.values.first as? [String: Any] { - // Extract token name - var tokenName: String? = nil - - // Try to get localized name first - if let conventions = tokenData["conventions"] as? [String: Any], - let localizations = conventions["localizations"] as? [String: Any], - let enLocalization = localizations["en"] as? [String: Any], - let singularForm = enLocalization["singularForm"] as? String { - tokenName = singularForm - } - - // Fallback to description or generic name - if tokenName == nil { - tokenName = tokenData["description"] as? String ?? tokenData["name"] as? String - } - - if let tokenName = tokenName { - finalName = "\(tokenName) Token Contract" - } else { - finalName = "Token Contract" - } - } else if let firstDocType = documents.keys.first { - // Has documents - finalName = "Contract with \(firstDocType)" - } else { - // Fallback - finalName = "Contract \(trimmedId.prefix(8))..." - } - } - - // Save to persistent storage - let persistentContract = PersistentDataContract( - id: contractIdData, - name: finalName, - serializedContract: serializedContract - ) - - // Add the binary serialization if available - persistentContract.binarySerialization = binaryData - - modelContext.insert(persistentContract) - try modelContext.save() - - // Parse tokens and document types from the contract - try DataContractParser.parseDataContract( - contractData: contractData, - contractId: contractIdData, - modelContext: modelContext - ) - - // Save again to persist relationships - try modelContext.save() - - await MainActor.run { - isLoading = false - dismiss() - } - - } catch { - print("❌ Failed to load contract: \(error)") - await MainActor.run { - // Provide more helpful error messages - if error.localizedDescription.contains("Data contract not found") { - errorMessage = "Contract not found on \(unifiedState.platformState.currentNetwork.rawValue). This contract may exist on a different network or the ID may be incorrect." - } else { - errorMessage = "Failed to load contract: \(error.localizedDescription)" - } - showError = true - isLoading = false - } - } - } -} - -#Preview { - NavigationStack { - LocalDataContractsView() - .environmentObject(UnifiedAppState()) - } -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TokenDetailsView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TokenDetailsView.swift index f3340f5ba71..6bbe5d62a3c 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TokenDetailsView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TokenDetailsView.swift @@ -1,4 +1,5 @@ import SwiftUI +import SwiftDashSDK struct TokenDetailsView: View { let token: PersistentToken diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TokenSearchView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TokenSearchView.swift deleted file mode 100644 index 727a32346e5..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TokenSearchView.swift +++ /dev/null @@ -1,246 +0,0 @@ -import SwiftUI -import SwiftData - -struct TokenSearchView: View { - @Query private var allTokens: [PersistentToken] - @State private var selectedFilter: TokenFilter = .all - @State private var searchText = "" - - enum TokenFilter: String, CaseIterable { - case all = "All Tokens" - case mintable = "Can Mint" - case burnable = "Can Burn" - case freezable = "Can Freeze" - case hasDistribution = "Has Distribution" - case paused = "Paused" - - var predicate: Predicate? { - switch self { - case .all: - return nil - case .mintable: - return PersistentToken.mintableTokensPredicate() - case .burnable: - return PersistentToken.burnableTokensPredicate() - case .freezable: - return PersistentToken.freezableTokensPredicate() - case .hasDistribution: - return PersistentToken.distributionTokensPredicate() - case .paused: - return PersistentToken.pausedTokensPredicate() - } - } - } - - var filteredTokens: [PersistentToken] { - var tokens = allTokens - - // Apply control rule filter - switch selectedFilter { - case .mintable: - tokens = tokens.filter { $0.canManuallyMint } - case .burnable: - tokens = tokens.filter { $0.canManuallyBurn } - case .freezable: - tokens = tokens.filter { $0.canFreeze } - case .hasDistribution: - tokens = tokens.filter { $0.hasDistribution } - case .paused: - tokens = tokens.filter { $0.isPaused } - case .all: - break - } - - // Apply text search - if !searchText.isEmpty { - tokens = tokens.filter { token in - token.name.localizedCaseInsensitiveContains(searchText) || - token.displayName.localizedCaseInsensitiveContains(searchText) || - (token.tokenDescription ?? "").localizedCaseInsensitiveContains(searchText) - } - } - - return tokens - } - - var body: some View { - VStack(spacing: 0) { - // Search and Filter - VStack(spacing: 12) { - HStack { - Image(systemName: "magnifyingglass") - .foregroundColor(.secondary) - TextField("Search tokens...", text: $searchText) - .textFieldStyle(RoundedBorderTextFieldStyle()) - } - .padding(.horizontal) - - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 8) { - ForEach(TokenFilter.allCases, id: \.self) { filter in - FilterChip( - title: filter.rawValue, - isSelected: selectedFilter == filter, - action: { selectedFilter = filter } - ) - } - } - .padding(.horizontal) - } - } - .padding(.vertical) - .background(Color(UIColor.systemBackground)) - - // Results - if filteredTokens.isEmpty { - VStack(spacing: 20) { - Image(systemName: "magnifyingglass.circle") - .font(.system(size: 60)) - .foregroundColor(.secondary) - - Text("No tokens found") - .font(.title2) - .fontWeight(.semibold) - - Text("Try adjusting your search or filters") - .font(.subheadline) - .foregroundColor(.secondary) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .padding() - } else { - List(filteredTokens) { token in - NavigationLink(destination: TokenDetailsView(token: token)) { - TokenSearchRow(token: token) - } - } - .listStyle(PlainListStyle()) - } - } - .navigationTitle("Token Search") - .navigationBarTitleDisplayMode(.inline) - } -} - -struct FilterChip: View { - let title: String - let isSelected: Bool - let action: () -> Void - - var body: some View { - Button(action: action) { - Text(title) - .font(.subheadline) - .padding(.horizontal, 16) - .padding(.vertical, 8) - .background(isSelected ? Color.blue : Color(UIColor.secondarySystemBackground)) - .foregroundColor(isSelected ? .white : .primary) - .cornerRadius(20) - } - .buttonStyle(PlainButtonStyle()) - } -} - -struct TokenSearchRow: View { - let token: PersistentToken - - var body: some View { - VStack(alignment: .leading, spacing: 8) { - HStack { - VStack(alignment: .leading) { - Text(token.getPluralForm() ?? token.displayName) - .font(.headline) - - if let contract = token.dataContract { - Text(contract.name) - .font(.caption) - .foregroundColor(.secondary) - } - } - - Spacer() - - // Show capabilities - HStack(spacing: 4) { - if token.canManuallyMint { - CapabilityBadge(icon: "plus.circle.fill", color: .green) - } - if token.canManuallyBurn { - CapabilityBadge(icon: "flame.fill", color: .orange) - } - if token.canFreeze { - CapabilityBadge(icon: "snowflake", color: .blue) - } - if token.hasDistribution { - CapabilityBadge(icon: "arrow.clockwise", color: .purple) - } - if token.isPaused { - CapabilityBadge(icon: "pause.circle.fill", color: .red) - } - } - } - - // Token info - HStack { - Text("Supply: \(token.formattedBaseSupply)") - .font(.caption) - .foregroundColor(.secondary) - - Spacer() - - if let maxSupply = token.maxSupply, maxSupply != "0" { - Text("Max: \(formatTokenAmount(maxSupply, decimals: token.decimals))") - .font(.caption) - .foregroundColor(.secondary) - } - } - } - .padding(.vertical, 4) - } - - private func formatTokenAmount(_ amount: String, decimals: Int) -> String { - guard let value = Double(amount) else { return amount } - let divisor = pow(10.0, Double(decimals)) - let actualAmount = value / divisor - let formatter = NumberFormatter() - formatter.numberStyle = .decimal - formatter.maximumFractionDigits = decimals - formatter.minimumFractionDigits = 0 - return formatter.string(from: NSNumber(value: actualAmount)) ?? amount - } -} - -struct CapabilityBadge: View { - let icon: String - let color: Color - - var body: some View { - Image(systemName: icon) - .font(.caption) - .foregroundColor(color) - } -} - -// Example of using the predicate in a query -struct MintableTokensView: View { - @Query(filter: PersistentToken.mintableTokensPredicate()) - private var mintableTokens: [PersistentToken] - - var body: some View { - List(mintableTokens) { token in - VStack(alignment: .leading) { - Text(token.displayName) - .font(.headline) - Text("Can mint new tokens") - .font(.caption) - .foregroundColor(.secondary) - } - } - } -} - -#Preview { - NavigationStack { - TokenSearchView() - } -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TokensView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TokensView.swift deleted file mode 100644 index 98e3cef7fcd..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TokensView.swift +++ /dev/null @@ -1,594 +0,0 @@ -import SwiftUI -import SwiftDashSDK - -// MARK: - View Extensions -extension View { - func placeholder( - when shouldShow: Bool, - alignment: Alignment = .leading, - @ViewBuilder placeholder: () -> Content) -> some View { - - ZStack(alignment: alignment) { - placeholder().opacity(shouldShow ? 1 : 0) - self - } - } -} - -struct TokensView: View { - @EnvironmentObject var appState: AppState - @State private var selectedToken: TokenModel? - @State private var selectedIdentity: IdentityModel? - - var body: some View { - NavigationView { - VStack { - if appState.identities.isEmpty { - EmptyStateView( - systemImage: "person.3", - title: "No Identities", - message: "Add identities in the Identities tab to use tokens" - ) - } else { - List { - Section("Select Identity") { - Picker("Identity", selection: $selectedIdentity) { - Text("Select an identity").tag(nil as IdentityModel?) - ForEach(appState.identities) { identity in - Text(identity.alias ?? identity.idString) - .tag(identity as IdentityModel?) - } - } - .pickerStyle(MenuPickerStyle()) - } - - if selectedIdentity != nil { - Section("Available Tokens") { - ForEach(appState.tokens) { token in - TokenRow(token: token) { - selectedToken = token - } - } - } - } - } - } - } - .navigationTitle("Tokens") - .sheet(item: $selectedToken) { token in - TokenActionsView(token: token, selectedIdentity: selectedIdentity) - .environmentObject(appState) - } - .onAppear { - if appState.tokens.isEmpty { - loadSampleTokens() - } - } - } - } - - private func loadSampleTokens() { - // Add sample tokens for demonstration - appState.tokens = [ - TokenModel( - id: "token1", - contractId: "contract1", - name: "Dash Platform Token", - symbol: "DPT", - decimals: 8, - totalSupply: 1000000000000000, - balance: 10000000000, - frozenBalance: 250000000, // 2.5 DPT frozen - availableClaims: [ - ("Reward Distribution", 100000000), // 1 DPT - ("Airdrop #42", 50000000) // 0.5 DPT - ], - pricePerToken: 0.001 - ), - TokenModel( - id: "token2", - contractId: "contract2", - name: "Test Token", - symbol: "TEST", - decimals: 6, - totalSupply: 500000000000, - balance: 5000000, - frozenBalance: 0, - availableClaims: [], - pricePerToken: 0.0001 - ) - ] - } -} - -struct TokenRow: View { - let token: TokenModel - let onTap: () -> Void - - var body: some View { - Button(action: onTap) { - VStack(alignment: .leading, spacing: 4) { - HStack { - Text(token.name) - .font(.headline) - .foregroundColor(.primary) - Spacer() - Text(token.symbol) - .font(.subheadline) - .foregroundColor(.secondary) - } - - HStack { - Text("Balance: \(token.formattedBalance)") - .font(.subheadline) - .foregroundColor(.blue) - - if token.frozenBalance > 0 { - Text("(\(token.formattedFrozenBalance) frozen)") - .font(.caption) - .foregroundColor(.orange) - } - } - - HStack { - Text("Total Supply: \(token.formattedTotalSupply)") - .font(.caption) - .foregroundColor(.secondary) - - if !token.availableClaims.isEmpty { - Spacer() - Label("\(token.availableClaims.count)", systemImage: "gift") - .font(.caption) - .foregroundColor(.green) - } - } - } - .padding(.vertical, 4) - } - .buttonStyle(PlainButtonStyle()) - } -} - -struct TokenActionsView: View { - let token: TokenModel - let selectedIdentity: IdentityModel? - @EnvironmentObject var appState: AppState - @Environment(\.dismiss) var dismiss - @State private var selectedAction: TokenAction? - - var body: some View { - NavigationView { - List { - Section("Token Information") { - VStack(alignment: .leading, spacing: 8) { - HStack { - Text("Name:") - .font(.caption) - .foregroundColor(.secondary) - Text(token.name) - .font(.subheadline) - } - HStack { - Text("Symbol:") - .font(.caption) - .foregroundColor(.secondary) - Text(token.symbol) - .font(.subheadline) - } - HStack { - Text("Balance:") - .font(.caption) - .foregroundColor(.secondary) - Text(token.formattedBalance) - .font(.subheadline) - .foregroundColor(.blue) - } - } - } - - Section("Actions") { - ForEach(TokenAction.allCases, id: \.self) { action in - Button(action: { - if action.isEnabled { - selectedAction = action - } - }) { - HStack { - Image(systemName: action.systemImage) - .frame(width: 24) - .foregroundColor(action.isEnabled ? .blue : .gray) - - VStack(alignment: .leading) { - Text(action.rawValue) - .foregroundColor(action.isEnabled ? .primary : .gray) - Text(action.description) - .font(.caption) - .foregroundColor(.secondary) - } - - Spacer() - } - .padding(.vertical, 4) - } - .disabled(!action.isEnabled) - } - } - } - .navigationTitle(token.name) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button("Done") { - dismiss() - } - } - } - .sheet(item: $selectedAction) { action in - TokenActionDetailView( - token: token, - action: action, - selectedIdentity: selectedIdentity - ) - .environmentObject(appState) - } - } - } -} - -struct TokenActionDetailView: View { - let token: TokenModel - let action: TokenAction - let selectedIdentity: IdentityModel? - @EnvironmentObject var appState: AppState - @Environment(\.dismiss) var dismiss - @State private var isProcessing = false - @State private var recipientId = "" - @State private var amount = "" - @State private var tokenNote = "" - - var body: some View { - NavigationView { - Form { - Section("Selected Identity") { - if let identity = selectedIdentity { - VStack(alignment: .leading) { - Text(identity.alias ?? "Identity") - .font(.headline) - Text(identity.idString) - .font(.caption) - .foregroundColor(.secondary) - .lineLimit(1) - .truncationMode(.middle) - Text("Balance: \(identity.formattedBalance)") - .font(.subheadline) - .foregroundColor(.blue) - } - } - } - - switch action { - case .transfer: - Section("Transfer Details") { - TextField("Recipient Identity ID", text: $recipientId) - .textContentType(.none) - .autocapitalization(.none) - - TextField("Amount", text: $amount) - .keyboardType(.numberPad) - - TextField("Note (Optional)", text: $tokenNote) - } - - case .mint: - Section("Mint Details") { - TextField("Amount", text: $amount) - .keyboardType(.numberPad) - - TextField("Recipient Identity ID (Optional)", text: $recipientId) - .textContentType(.none) - .autocapitalization(.none) - } - - case .burn: - Section("Burn Details") { - TextField("Amount", text: $amount) - .keyboardType(.numberPad) - - Text("Warning: This action is irreversible") - .font(.caption) - .foregroundColor(.red) - } - - case .claim: - Section("Claim Details") { - if token.availableClaims.isEmpty { - Text("No claims available at this time") - .font(.caption) - .foregroundColor(.secondary) - } else { - Text("Available claims:") - .font(.caption) - .foregroundColor(.secondary) - - VStack(alignment: .leading, spacing: 8) { - ForEach(token.availableClaims, id: \.name) { claim in - HStack { - Text(claim.name) - Spacer() - let divisor = pow(10.0, Double(token.decimals)) - let claimAmount = Double(claim.amount) / divisor - Text(String(format: "%.\(token.decimals)f %@", claimAmount, token.symbol)) - .foregroundColor(.green) - } - } - } - .padding(.vertical, 4) - - Text("All available claims will be processed") - .font(.caption) - .foregroundColor(.secondary) - } - } - - case .freeze: - Section("Freeze Details") { - TextField("Amount to Freeze", text: $amount) - .keyboardType(.numberPad) - - TextField("Reason (Optional)", text: $tokenNote) - - Text("Frozen tokens cannot be transferred until unfrozen") - .font(.caption) - .foregroundColor(.secondary) - } - - case .unfreeze: - Section("Unfreeze Details") { - if token.frozenBalance > 0 { - Text("Frozen Balance: \(token.formattedFrozenBalance)") - .font(.subheadline) - .foregroundColor(.orange) - } else { - Text("No frozen tokens available") - .font(.subheadline) - .foregroundColor(.secondary) - } - - TextField("Amount to Unfreeze", text: $amount) - .keyboardType(.numberPad) - .disabled(token.frozenBalance == 0) - - Text("Unfrozen tokens will be available for use immediately") - .font(.caption) - .foregroundColor(.secondary) - } - - case .destroyFrozenFunds: - Section("Destroy Frozen Funds") { - if token.frozenBalance > 0 { - Text("Frozen Balance: \(token.formattedFrozenBalance)") - .font(.subheadline) - .foregroundColor(.orange) - } else { - Text("No frozen tokens available") - .font(.subheadline) - .foregroundColor(.secondary) - } - - TextField("Amount to Destroy", text: $amount) - .keyboardType(.numberPad) - - Text("⚠️ This action permanently destroys frozen tokens") - .font(.caption) - .foregroundColor(.red) - - TextField("Confirmation Reason", text: $tokenNote) - .placeholder(when: tokenNote.isEmpty) { - Text("Required for audit trail") - .foregroundColor(.secondary) - } - } - - case .directPurchase: - Section("Direct Purchase") { - Text("Price: \(token.pricePerToken, specifier: "%.6f") DASH per \(token.symbol)") - .font(.subheadline) - - TextField("Amount to Purchase", text: $amount) - .keyboardType(.numberPad) - - if let purchaseAmount = Double(amount) { - let totalCost = purchaseAmount * token.pricePerToken - Text("Total Cost: \(totalCost, specifier: "%.6f") DASH") - .font(.caption) - .foregroundColor(.blue) - } - - if let identity = selectedIdentity { - Text("Available Balance: \(identity.formattedBalance)") - .font(.caption) - .foregroundColor(.secondary) - } - - Text("Purchase will be deducted from your identity balance") - .font(.caption) - .foregroundColor(.secondary) - } - } - - Section { - Button(action: { - Task { - isProcessing = true - await performTokenAction() - isProcessing = false - dismiss() - } - }) { - HStack { - Spacer() - if isProcessing { - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - } else { - Text("Execute \(action.rawValue)") - } - Spacer() - } - } - .disabled(isProcessing || !isActionValid) - } - } - .navigationTitle(action.rawValue) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button("Cancel") { - dismiss() - } - } - } - } - } - - private var isActionValid: Bool { - switch action { - case .transfer: - return !recipientId.isEmpty && !amount.isEmpty - case .mint: - return !amount.isEmpty - case .burn, .freeze, .unfreeze, .directPurchase: - return !amount.isEmpty - case .destroyFrozenFunds: - return !amount.isEmpty && !tokenNote.isEmpty - case .claim: - return true // Claims don't require input - } - } - - private func performTokenAction() async { - guard appState.sdk != nil, - selectedIdentity != nil else { - appState.showError(message: "Please select an identity") - return - } - - do { - switch action { - case .transfer: - guard !recipientId.isEmpty else { - throw TokenError.invalidRecipient - } - - guard let transferAmount = UInt64(amount) else { - throw TokenError.invalidAmount - } - - // In a real app, we would use the SDK's token transfer functionality - appState.showError(message: "Transfer of \(transferAmount) \(token.symbol) tokens initiated") - - case .mint: - guard let mintAmount = UInt64(amount) else { - throw TokenError.invalidAmount - } - - // In a real app, we would use the SDK's token mint functionality - appState.showError(message: "Minting \(mintAmount) \(token.symbol) tokens") - - case .burn: - guard let burnAmount = UInt64(amount) else { - throw TokenError.invalidAmount - } - - // In a real app, we would use the SDK's token burn functionality - appState.showError(message: "Burning \(burnAmount) \(token.symbol) tokens") - - case .claim: - // In a real app, we would fetch available claims and process them - appState.showError(message: "Claiming available \(token.symbol) tokens from distributions") - - case .freeze: - guard let freezeAmount = UInt64(amount) else { - throw TokenError.invalidAmount - } - - // In a real app, we would use the SDK's token freeze functionality - let reason = tokenNote.isEmpty ? "No reason provided" : tokenNote - appState.showError(message: "Freezing \(freezeAmount) \(token.symbol) tokens. Reason: \(reason)") - - case .unfreeze: - guard let unfreezeAmount = UInt64(amount) else { - throw TokenError.invalidAmount - } - - // In a real app, we would use the SDK's token unfreeze functionality - appState.showError(message: "Unfreezing \(unfreezeAmount) \(token.symbol) tokens") - - case .destroyFrozenFunds: - guard let destroyAmount = UInt64(amount) else { - throw TokenError.invalidAmount - } - - guard !tokenNote.isEmpty else { - throw TokenError.missingReason - } - - // In a real app, we would use the SDK's destroy frozen funds functionality - appState.showError(message: "Destroying \(destroyAmount) frozen \(token.symbol) tokens. Reason: \(tokenNote)") - - case .directPurchase: - guard let purchaseAmount = UInt64(amount) else { - throw TokenError.invalidAmount - } - - let cost = Double(purchaseAmount) * token.pricePerToken - // In a real app, we would use the SDK's direct purchase functionality - appState.showError(message: "Purchasing \(purchaseAmount) \(token.symbol) tokens for \(String(format: "%.6f", cost)) DASH") - } - } catch { - appState.showError(message: "Failed to perform \(action.rawValue): \(error.localizedDescription)") - } - } -} - -enum TokenError: LocalizedError { - case invalidRecipient - case invalidAmount - case missingReason - - var errorDescription: String? { - switch self { - case .invalidRecipient: - return "Please enter a valid recipient ID" - case .invalidAmount: - return "Please enter a valid amount" - case .missingReason: - return "Please provide a reason for this action" - } - } -} - -struct EmptyStateView: View { - let systemImage: String - let title: String - let message: String - - var body: some View { - VStack(spacing: 20) { - Image(systemName: systemImage) - .font(.system(size: 60)) - .foregroundColor(.gray) - - Text(title) - .font(.title2) - .fontWeight(.semibold) - - Text(message) - .font(.body) - .foregroundColor(.secondary) - .multilineTextAlignment(.center) - .padding(.horizontal) - } - .padding() - } -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/ErrorHandlingTests.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/ErrorHandlingTests.swift index 6aa2c5959fc..91aae7b3fa8 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/ErrorHandlingTests.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/ErrorHandlingTests.swift @@ -45,7 +45,6 @@ final class ErrorHandlingTests: XCTestCase { category: .network, recoverySuggestion: "Try again", underlyingError: "Original error", - isRetryable: true ) XCTAssertEqual(error.title, "Test Error") @@ -53,7 +52,6 @@ final class ErrorHandlingTests: XCTestCase { XCTAssertEqual(error.category, .network) XCTAssertEqual(error.recoverySuggestion, "Try again") XCTAssertEqual(error.underlyingError, "Original error") - XCTAssertTrue(error.isRetryable) } func testUserFacingErrorLocalizedError() { @@ -113,12 +111,6 @@ final class ErrorHandlingTests: XCTestCase { XCTAssertEqual(ErrorFormatter.formatForDisplay(userFacing), "Title: Message") } - func testErrorFormatterFormatWithCategory() { - struct SimpleError: Error {} - let message = ErrorFormatter.formatWithCategory(SimpleError(), category: .network) - XCTAssertTrue(message.hasPrefix("[Network]")) - } - func testErrorFormatterFormatValidationErrorsSingle() { let errors = ["Invalid email address"] let formatted = ErrorFormatter.formatValidationErrors(errors) @@ -136,13 +128,6 @@ final class ErrorHandlingTests: XCTestCase { XCTAssertEqual(formatted, "") } - func testErrorFormatterFormatForLogging() { - struct TestError: Error {} - let formatted = ErrorFormatter.formatForLogging(TestError(), context: "TestContext") - XCTAssertTrue(formatted.contains("[TestContext]")) - XCTAssertTrue(formatted.contains("TestError")) - } - // MARK: - ErrorRecovery Tests func testErrorRecoverySuggestionForCategory() { @@ -156,25 +141,6 @@ final class ErrorHandlingTests: XCTestCase { XCTAssertTrue(timeoutSuggestion.contains("long") || timeoutSuggestion.contains("again")) } - func testErrorRecoverySuggestionFromMessage() { - XCTAssertNotNil(ErrorRecovery.suggestionFromMessage("Network connection failed")) - XCTAssertNotNil(ErrorRecovery.suggestionFromMessage("Request timed out")) - XCTAssertNotNil(ErrorRecovery.suggestionFromMessage("Invalid input")) - XCTAssertNotNil(ErrorRecovery.suggestionFromMessage("Resource not found")) - XCTAssertNotNil(ErrorRecovery.suggestionFromMessage("Unauthorized access")) - XCTAssertNil(ErrorRecovery.suggestionFromMessage("Some random error")) - } - - func testErrorRecoveryIsRetryable() { - XCTAssertTrue(ErrorRecovery.isRetryable(category: .network)) - XCTAssertTrue(ErrorRecovery.isRetryable(category: .timeout)) - XCTAssertTrue(ErrorRecovery.isRetryable(category: .system)) - - XCTAssertFalse(ErrorRecovery.isRetryable(category: .validation)) - XCTAssertFalse(ErrorRecovery.isRetryable(category: .userInput)) - XCTAssertFalse(ErrorRecovery.isRetryable(category: .authentication)) - } - // MARK: - ErrorCategorizer Tests func testErrorCategorizerCategorize() { @@ -209,175 +175,4 @@ final class ErrorHandlingTests: XCTestCase { XCTAssertEqual(result.message, "Keep this") XCTAssertEqual(result.category, .cryptography) } - - // MARK: - ErrorBuilder Tests - - func testErrorBuilderBasic() { - let error = ErrorBuilder() - .withTitle("Custom Title") - .withMessage("Custom message") - .withCategory(.authentication) - .build() - - XCTAssertEqual(error.title, "Custom Title") - XCTAssertEqual(error.message, "Custom message") - XCTAssertEqual(error.category, .authentication) - } - - func testErrorBuilderWithAllOptions() { - struct UnderlyingError: Error {} - - let error = ErrorBuilder() - .withTitle("Title") - .withMessage("Message") - .withCategory(.network) - .withRecoverySuggestion("Custom suggestion") - .withUnderlyingError(UnderlyingError()) - .retryable() - .build() - - XCTAssertEqual(error.title, "Title") - XCTAssertEqual(error.message, "Message") - XCTAssertEqual(error.category, .network) - XCTAssertEqual(error.recoverySuggestion, "Custom suggestion") - XCTAssertNotNil(error.underlyingError) - XCTAssertTrue(error.isRetryable) - } - - func testErrorBuilderValidationFactory() { - let error = ErrorBuilder.validation("Invalid email") - - XCTAssertEqual(error.title, "Validation Error") - XCTAssertEqual(error.message, "Invalid email") - XCTAssertEqual(error.category, .validation) - } - - func testErrorBuilderNetworkFactory() { - let error = ErrorBuilder.network("Connection failed") - - XCTAssertEqual(error.title, "Network Error") - XCTAssertEqual(error.message, "Connection failed") - XCTAssertEqual(error.category, .network) - XCTAssertTrue(error.isRetryable) - } - - func testErrorBuilderNotFoundFactory() { - let error = ErrorBuilder.notFound("User not found") - - XCTAssertEqual(error.title, "Not Found") - XCTAssertEqual(error.message, "User not found") - XCTAssertEqual(error.category, .notFound) - } - - func testErrorBuilderTimeoutFactory() { - let error = ErrorBuilder.timeout() - - XCTAssertEqual(error.title, "Timeout") - XCTAssertEqual(error.category, .timeout) - XCTAssertTrue(error.isRetryable) - } - - func testErrorBuilderAuthenticationFactory() { - let error = ErrorBuilder.authentication("Invalid credentials") - - XCTAssertEqual(error.title, "Authentication Error") - XCTAssertEqual(error.message, "Invalid credentials") - XCTAssertEqual(error.category, .authentication) - } - - func testErrorBuilderUserInputFactory() { - let error = ErrorBuilder.userInput("Field required") - - XCTAssertEqual(error.title, "Input Error") - XCTAssertEqual(error.message, "Field required") - XCTAssertEqual(error.category, .userInput) - } - - // MARK: - ErrorAggregator Tests - - func testErrorAggregatorAddErrors() { - var aggregator = ErrorAggregator() - - XCTAssertFalse(aggregator.hasErrors) - XCTAssertEqual(aggregator.count, 0) - - aggregator.add("Error 1", category: .validation) - aggregator.add("Error 2", category: .network) - - XCTAssertTrue(aggregator.hasErrors) - XCTAssertEqual(aggregator.count, 2) - } - - func testErrorAggregatorAddValidation() { - var aggregator = ErrorAggregator() - aggregator.addValidation("Invalid input") - - XCTAssertEqual(aggregator.allErrors.first?.category, .validation) - } - - func testErrorAggregatorMessages() { - var aggregator = ErrorAggregator() - aggregator.add("First error") - aggregator.add("Second error") - - XCTAssertEqual(aggregator.messages, ["First error", "Second error"]) - } - - func testErrorAggregatorCombinedMessage() { - var aggregator = ErrorAggregator() - aggregator.add("Error A") - aggregator.add("Error B") - - let combined = aggregator.combinedMessage - XCTAssertTrue(combined.contains("Error A")) - XCTAssertTrue(combined.contains("Error B")) - } - - func testErrorAggregatorPrimaryError() { - var aggregator = ErrorAggregator() - aggregator.add("Validation error", category: .validation) - aggregator.add("Network error", category: .network) - aggregator.add("System error", category: .system) - - // System should be highest priority - let primary = aggregator.primaryError - XCTAssertEqual(primary?.category, .system) - } - - func testErrorAggregatorClear() { - var aggregator = ErrorAggregator() - aggregator.add("Error") - XCTAssertTrue(aggregator.hasErrors) - - aggregator.clear() - XCTAssertFalse(aggregator.hasErrors) - XCTAssertEqual(aggregator.count, 0) - } - - // MARK: - Result Extensions Tests - - func testResultMapToUserFacingError() { - struct TestError: Error {} - - let failureResult: Result = .failure(TestError()) - let mapped = failureResult.mapToUserFacingError() - - if case .failure(let error) = mapped { - XCTAssertNotNil(error.recoverySuggestion) - } else { - XCTFail("Expected failure") - } - } - - func testResultErrorMessage() { - struct TestError: Error, LocalizedError { - var errorDescription: String? { "Test failure" } - } - - let successResult: Result = .success("OK") - XCTAssertNil(successResult.errorMessage) - - let failureResult: Result = .failure(TestError()) - XCTAssertEqual(failureResult.errorMessage, "Test failure") - } } diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/KeyManagerTests.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/KeyManagerTests.swift index fff8bd68551..aa38885e72d 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/KeyManagerTests.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/KeyManagerTests.swift @@ -123,35 +123,6 @@ final class KeyManagerTests: XCTestCase { XCTAssertEqual(result.format, .unknown) } - // MARK: - KeySizeValidator Tests - - func testExpectedPrivateKeySizeECDSA() { - XCTAssertEqual(KeySizeValidator.expectedPrivateKeySize(for: .ecdsaSecp256k1), 32) - } - - func testExpectedPrivateKeySizeBLS() { - XCTAssertEqual(KeySizeValidator.expectedPrivateKeySize(for: .bls12_381), 32) - } - - func testExpectedPrivateKeySizeEdDSA() { - XCTAssertEqual(KeySizeValidator.expectedPrivateKeySize(for: .eddsa25519Hash160), 32) - } - - func testIsValidSizeCorrect() { - let key = Data(repeating: 0x00, count: 32) - XCTAssertTrue(KeySizeValidator.isValidSize(key, for: .ecdsaSecp256k1)) - } - - func testIsValidSizeTooShort() { - let key = Data(repeating: 0x00, count: 16) - XCTAssertFalse(KeySizeValidator.isValidSize(key, for: .ecdsaSecp256k1)) - } - - func testIsValidSizeTooLong() { - let key = Data(repeating: 0x00, count: 64) - XCTAssertFalse(KeySizeValidator.isValidSize(key, for: .ecdsaSecp256k1)) - } - // MARK: - KeyFormatter Tests func testToHex() { @@ -209,14 +180,4 @@ final class KeyManagerTests: XCTestCase { XCTAssertFalse(result.isValid) XCTAssertTrue(result.error?.contains("No public keys") ?? false) } - - func testValidatePrivateKeyInputEmpty() { - let result = KeyValidator.validatePrivateKeyInput("", against: []) - XCTAssertFalse(result.isValid) - } - - func testValidatePrivateKeyInputInvalidFormat() { - let result = KeyValidator.validatePrivateKeyInput("not-a-key", against: []) - XCTAssertFalse(result.isValid) - } } diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/StateManagementTests.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/StateManagementTests.swift index 6b02e2c87ce..b62ff17aaaf 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/StateManagementTests.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/StateManagementTests.swift @@ -48,162 +48,6 @@ final class StateManagementTests: XCTestCase { XCTAssertNotEqual(LoadingState.idle, LoadingState.loading) } - // MARK: - ResultState Tests - - func testResultStateIdle() { - let state: ResultState = .idle - XCTAssertFalse(state.isLoading) - XCTAssertFalse(state.isSuccess) - XCTAssertFalse(state.isFailure) - XCTAssertNil(state.value) - XCTAssertNil(state.error) - } - - func testResultStateLoading() { - let state: ResultState = .loading - XCTAssertTrue(state.isLoading) - XCTAssertFalse(state.isSuccess) - XCTAssertFalse(state.isFailure) - } - - func testResultStateSuccess() { - let state: ResultState = .success("test value") - XCTAssertFalse(state.isLoading) - XCTAssertTrue(state.isSuccess) - XCTAssertFalse(state.isFailure) - XCTAssertEqual(state.value, "test value") - XCTAssertNil(state.error) - } - - func testResultStateFailure() { - let state: ResultState = .failure("test error") - XCTAssertFalse(state.isLoading) - XCTAssertFalse(state.isSuccess) - XCTAssertTrue(state.isFailure) - XCTAssertNil(state.value) - XCTAssertEqual(state.error, "test error") - } - - func testResultStateMap() { - let intState: ResultState = .success(42) - let stringState = intState.map { String($0) } - XCTAssertEqual(stringState.value, "42") - } - - func testResultStateMapIdle() { - let state: ResultState = .idle - let mapped = state.map { String($0) } - XCTAssertFalse(mapped.isSuccess) - } - - func testResultStateMapFailure() { - let state: ResultState = .failure("error") - let mapped = state.map { String($0) } - XCTAssertTrue(mapped.isFailure) - XCTAssertEqual(mapped.error, "error") - } - - // MARK: - FormState Tests - - func testFormStateEditing() { - let state = FormState.editing - XCTAssertFalse(state.isSubmitting) - XCTAssertFalse(state.isSubmitted) - XCTAssertFalse(state.hasError) - XCTAssertTrue(state.canSubmit) - } - - func testFormStateSubmitting() { - let state = FormState.submitting - XCTAssertTrue(state.isSubmitting) - XCTAssertFalse(state.isSubmitted) - XCTAssertFalse(state.hasError) - XCTAssertFalse(state.canSubmit) - } - - func testFormStateSubmitted() { - let state = FormState.submitted - XCTAssertFalse(state.isSubmitting) - XCTAssertTrue(state.isSubmitted) - XCTAssertFalse(state.hasError) - XCTAssertFalse(state.canSubmit) - } - - func testFormStateError() { - let state = FormState.error("Validation failed") - XCTAssertFalse(state.isSubmitting) - XCTAssertFalse(state.isSubmitted) - XCTAssertTrue(state.hasError) - XCTAssertTrue(state.canSubmit) - XCTAssertEqual(state.errorMessage, "Validation failed") - } - - // MARK: - PaginationState Tests - - func testPaginationStateDefault() { - let state = PaginationState() - XCTAssertEqual(state.currentPage, 0) - XCTAssertEqual(state.totalPages, 0) - XCTAssertFalse(state.isLoadingMore) - XCTAssertTrue(state.hasMore) - XCTAssertTrue(state.canLoadMore) - } - - func testPaginationStateStartLoadingMore() { - var state = PaginationState() - state.startLoadingMore() - XCTAssertTrue(state.isLoadingMore) - XCTAssertFalse(state.canLoadMore) - } - - func testPaginationStateFinishLoadingMore() { - var state = PaginationState() - state.startLoadingMore() - state.finishLoadingMore(hasMore: true) - XCTAssertEqual(state.currentPage, 1) - XCTAssertFalse(state.isLoadingMore) - XCTAssertTrue(state.hasMore) - } - - func testPaginationStateFinishLoadingMoreNoMore() { - var state = PaginationState() - state.finishLoadingMore(hasMore: false) - XCTAssertFalse(state.hasMore) - XCTAssertFalse(state.canLoadMore) - } - - func testPaginationStateReset() { - var state = PaginationState(currentPage: 5, totalPages: 10, isLoadingMore: false, hasMore: false) - state.reset() - XCTAssertEqual(state.currentPage, 0) - XCTAssertEqual(state.totalPages, 0) - XCTAssertTrue(state.hasMore) - } - - // MARK: - RefreshState Tests - - func testRefreshStateDefault() { - let state = RefreshState() - XCTAssertFalse(state.isRefreshing) - XCTAssertNil(state.lastRefreshed) - XCTAssertNil(state.timeSinceLastRefresh) - } - - func testRefreshStateStartRefresh() { - var state = RefreshState() - state.startRefresh() - XCTAssertTrue(state.isRefreshing) - } - - func testRefreshStateFinishRefresh() { - var state = RefreshState() - state.startRefresh() - state.finishRefresh() - XCTAssertFalse(state.isRefreshing) - XCTAssertNotNil(state.lastRefreshed) - XCTAssertNotNil(state.timeSinceLastRefresh) - } - // MARK: - ErrorState Tests func testErrorStateDefault() { @@ -229,44 +73,4 @@ final class StateManagementTests: XCTestCase { XCTAssertFalse(state.showError) XCTAssertFalse(state.hasError) } - - // MARK: - AsyncOperationResult Tests - - func testAsyncOperationResultSuccess() { - let result = AsyncOperationResult(value: "success", duration: 1.5) - XCTAssertTrue(result.isSuccess) - XCTAssertEqual(result.value, "success") - XCTAssertNil(result.error) - XCTAssertEqual(result.duration, 1.5) - } - - func testAsyncOperationResultErrorString() { - let result = AsyncOperationResult(error: "failed", duration: 0.5) - XCTAssertFalse(result.isSuccess) - XCTAssertNil(result.value) - XCTAssertEqual(result.error, "failed") - XCTAssertEqual(result.duration, 0.5) - } - - // MARK: - AsyncOperation Tests - - func testAsyncOperationRunSuccess() async { - let result = await AsyncOperation.run { - return 42 - } - XCTAssertTrue(result.isSuccess) - XCTAssertEqual(result.value, 42) - XCTAssertNil(result.error) - XCTAssertGreaterThanOrEqual(result.duration, 0) - } - - func testAsyncOperationRunFailure() async { - struct TestError: Error {} - let result = await AsyncOperation.run { - throw TestError() - } - XCTAssertFalse(result.isSuccess) - XCTAssertNil(result.value) - XCTAssertNotNil(result.error) - } } diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/ValidationTests.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/ValidationTests.swift index 68f21874f6e..1fc3e0d73a6 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/ValidationTests.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/ValidationTests.swift @@ -93,19 +93,6 @@ final class ValidationTests: XCTestCase { XCTAssertFalse(AddressValidator.validateBech32mAddress("notanaddress")) } - func testValidateAddress_autoDetect() { - // Hex address - let hexAddr = "00aaff11223344556677889900aabbccddeeff0011" - XCTAssertTrue(AddressValidator.validateAddress(hexAddr)) - - // Bech32m address (if valid) - let bech32mAddr = "tdashevo1qz4242424242424242424242424242424g4dj6u7" - XCTAssertTrue(AddressValidator.validateAddress(bech32mAddr)) - - // Invalid - XCTAssertFalse(AddressValidator.validateAddress("invalid")) - } - // MARK: - TransferInputValidator Tests func testTransferInputValidator_allValid() { @@ -382,11 +369,6 @@ final class ValidationTests: XCTestCase { // MARK: - Identity ID Hex Validation Tests - func testValidateIdentityIdHex_valid() { - let validId = "aaff11223344556677889900aabbccddeeff0011223344556677889900112233" - XCTAssertTrue(AddressValidator.validateIdentityIdHex(validId)) - } - func testIsHexIdentityId_valid() { let validId = "aaff11223344556677889900aabbccddeeff0011223344556677889900112233" XCTAssertTrue(AddressValidator.isHexIdentityId(validId)) diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletTests/KeyDerivationTests.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletTests/KeyDerivationTests.swift deleted file mode 100644 index 0711ef7ee52..00000000000 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletTests/KeyDerivationTests.swift +++ /dev/null @@ -1,17 +0,0 @@ -import XCTest - -@testable import SwiftExampleApp - -// MARK: - Key Derivation Tests -// NOTE: Tests require CoreSDKWrapper, DerivationPath, HDKeyDerivation, WalletFFIBridge -// which are not exposed to the app test target. Re-enable when those types are -// available from SwiftDashSDK or a test helper. - -final class KeyDerivationTests: XCTestCase { - - func testKeyDerivationTestsDisabled() throws { - throw XCTSkip( - "Key derivation tests require CoreSDKWrapper, DerivationPath, HDKeyDerivation which are not in scope for app tests." - ) - } -} diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletTests/TransactionTests.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletTests/TransactionTests.swift index c964f14c8fb..12c165352ba 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletTests/TransactionTests.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletTests/TransactionTests.swift @@ -7,35 +7,6 @@ import XCTest // MARK: - Transaction Tests final class TransactionTests: XCTestCase { - - // MARK: - Transaction Builder Tests (using SDKTransactionBuilder) - - func testTransactionBuilderBasic() { - let builder = SDKTransactionBuilder(feePerKB: 1000) - XCTAssertNotNil(builder) - } - - func testTransactionBuilderAddInput() throws { - let builder = SDKTransactionBuilder(feePerKB: 1000) - XCTAssertNotNil(builder) - // SDKTransactionBuilder.addInput takes Input(txid:vout:scriptPubKey:privateKey), not HDUTXO - } - - - - func testTransactionBuilderInsufficientBalance() throws { - let builder = SDKTransactionBuilder(feePerKB: 1000) - try builder.addOutput( - SDKTransactionBuilder.Output( - address: "yXdUfGBfX6rQmNq5speeNGD5HfL2qkYBNe", amount: 100_000_000)) - do { - _ = try builder.build() - XCTFail("Should have thrown") - } catch { - // SDK currently throws SDKTxError.notImplemented; any throw is acceptable here - } - } - // MARK: - UTXO Manager Tests (skipped: UTXOManager / WalletManager(modelContainer:) not in SDK) @MainActor