From fdb41209e125ff01690a31ffb2cc1659b68278f8 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 27 Aug 2026 12:34:04 -0400 Subject: [PATCH 1/2] feat(profile): scaffold SetMinDmChatInitFee Wrap the new Profile.SetMinDmChatInitFee RPC (flipcash2-protobuf-api commit 0300d252) through the usual service/client chain: ProfileService.setMinDmChatInitFee builds the request and maps the result enum to a new ErrorSetMinDmChatInitFee, FlipClient+Profile exposes it as an async throwing call, and UserProfile.min_dm_chat_init_fee is decoded into Profile.minDmChatInitFee (FiatPaymentAmount to FiatAmount). Built against the local flipcash2-client-protocol checkout; the published package version isn't bumped here. --- .../Clients/Flip API/FlipClient+Profile.swift | 6 +++ .../Flip API/Services/ProfileService.swift | 51 +++++++++++++++++++ .../Sources/FlipcashCore/Models/Profile.swift | 19 +++++-- .../TransportClassificationTests.swift | 1 + 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/FlipcashCore/Sources/FlipcashCore/Clients/Flip API/FlipClient+Profile.swift b/FlipcashCore/Sources/FlipcashCore/Clients/Flip API/FlipClient+Profile.swift index 11fbce7e9..ffaeea772 100644 --- a/FlipcashCore/Sources/FlipcashCore/Clients/Flip API/FlipClient+Profile.swift +++ b/FlipcashCore/Sources/FlipcashCore/Clients/Flip API/FlipClient+Profile.swift @@ -51,4 +51,10 @@ extension FlipClient { public func updateTipCard(_ customization: TipCardCustomization, owner: KeyPair) async throws { try await profileService.updateTipCard(color: customization.colorProto, owner: owner) } + + /// Sets the minimum fee another user must pay to initialize a DM chat with + /// the caller, replacing any fee already set. + public func setMinDmChatInitFee(_ fee: FiatAmount, owner: KeyPair) async throws { + try await profileService.setMinDmChatInitFee(fee, owner: owner) + } } diff --git a/FlipcashCore/Sources/FlipcashCore/Clients/Flip API/Services/ProfileService.swift b/FlipcashCore/Sources/FlipcashCore/Clients/Flip API/Services/ProfileService.swift index 98dc56db9..8eb1dd616 100644 --- a/FlipcashCore/Sources/FlipcashCore/Clients/Flip API/Services/ProfileService.swift +++ b/FlipcashCore/Sources/FlipcashCore/Clients/Flip API/Services/ProfileService.swift @@ -179,6 +179,35 @@ final class ProfileService: Sendable { throw ErrorUpdateTipCard.unknown } } + + /// Sets the minimum fee another user must pay to initialize a DM chat with + /// the caller, replacing any fee already set. + func setMinDmChatInitFee(_ fee: FiatAmount, owner: KeyPair) async throws { + logger.info("Setting minimum DM chat init fee") + + var request = Flipcash_Profile_V1_SetMinDmChatInitFeeRequest() + request.minDmChatInitFee = .with { + $0.currency = fee.currency.rawValue + $0.nativeAmount = fee.doubleValue + } + request.auth = owner.authFor(message: request) + + do { + let response = try await service.setMinDmChatInitFee(request, options: .unaryDefault) + let error = ErrorSetMinDmChatInitFee(rawValue: response.result.rawValue) ?? .unknown + guard error == .ok else { + logger.error("Failed to set minimum DM chat init fee", metadata: ["error": "\(error)"]) + throw error + } + logger.info("Minimum DM chat init fee set") + } catch let error as ErrorSetMinDmChatInitFee { + throw error + } catch let error as RPCError { + throw ErrorSetMinDmChatInitFee.from(transportError: error) + } catch { + throw ErrorSetMinDmChatInitFee.unknown + } + } } // MARK: - Errors - @@ -225,6 +254,28 @@ extension ErrorUpdateTipCard: ServerError, TransportClassifiableError { } } +public enum ErrorSetMinDmChatInitFee: Int, Error { + case ok + case denied + /// e.g. unsupported currency, or amount outside the allowed range. + case invalidAmount + case unknown = -1 + case transportFailure = -2 + case cancelled = -3 + case rejected = -4 +} + +extension ErrorSetMinDmChatInitFee: ServerError, TransportClassifiableError { + public var reportingLevel: ErrorReportingLevel { + switch self { + case .ok, .transportFailure: .suppressed + case .cancelled: .info + case .denied, .invalidAmount: .info + case .unknown, .rejected: .error + } + } +} + public enum ErrorProfile: Error, Sendable { case denied case invalidDisplayName diff --git a/FlipcashCore/Sources/FlipcashCore/Models/Profile.swift b/FlipcashCore/Sources/FlipcashCore/Models/Profile.swift index b409a3682..d86c398f1 100644 --- a/FlipcashCore/Sources/FlipcashCore/Models/Profile.swift +++ b/FlipcashCore/Sources/FlipcashCore/Models/Profile.swift @@ -39,6 +39,11 @@ public struct Profile: Codable, Equatable, Sendable { /// Public — the server returns it for any user, not just the caller. public let username: Username? + /// The minimum fee another user must pay to initialize a DM chat with this + /// user. Public — returned for any user, not just the caller. `nil` when + /// the user hasn't set one, in which case the server default applies. + public let minDmChatInitFee: FiatAmount? + public var isPhoneVerified: Bool { phone != nil } @@ -56,7 +61,7 @@ public struct Profile: Codable, Equatable, Sendable { phone != nil && phone?.e164 != previous?.phone?.e164 } - public init(displayName: String?, phone: String?, email: String?, profilePicture: ProfilePicture? = nil, joinedAt: Date? = nil, tipCardCustomization: TipCardCustomization? = nil, userID: UserID? = nil, username: Username? = nil) throws { + public init(displayName: String?, phone: String?, email: String?, profilePicture: ProfilePicture? = nil, joinedAt: Date? = nil, tipCardCustomization: TipCardCustomization? = nil, userID: UserID? = nil, username: Username? = nil, minDmChatInitFee: FiatAmount? = nil) throws { // Only parse phone if it's not empty var parsedPhone: Phone? @@ -80,11 +85,12 @@ public struct Profile: Codable, Equatable, Sendable { joinedAt: joinedAt, tipCardCustomization: tipCardCustomization, userID: userID, - username: username + username: username, + minDmChatInitFee: minDmChatInitFee ) } - public init(displayName: String?, phone: Phone?, email: String?, profilePicture: ProfilePicture? = nil, joinedAt: Date? = nil, tipCardCustomization: TipCardCustomization? = nil, userID: UserID? = nil, username: Username? = nil) { + public init(displayName: String?, phone: Phone?, email: String?, profilePicture: ProfilePicture? = nil, joinedAt: Date? = nil, tipCardCustomization: TipCardCustomization? = nil, userID: UserID? = nil, username: Username? = nil, minDmChatInitFee: FiatAmount? = nil) { self.displayName = displayName self.phone = phone self.email = email @@ -93,6 +99,7 @@ public struct Profile: Codable, Equatable, Sendable { self.tipCardCustomization = tipCardCustomization self.userID = userID self.username = username + self.minDmChatInitFee = minDmChatInitFee } } @@ -126,7 +133,11 @@ extension Profile { joinedAt: proto.hasJoinTs ? proto.joinTs.date : nil, tipCardCustomization: proto.hasTipCardCustomization ? TipCardCustomization(proto.tipCardCustomization) : nil, userID: proto.hasUserID ? try? UUID(data: proto.userID.value) : nil, - username: proto.hasUsername ? Username(proto.username) : nil + username: proto.hasUsername ? Username(proto.username) : nil, + minDmChatInitFee: proto.hasMinDmChatInitFee ? FiatAmount( + value: Decimal(proto.minDmChatInitFee.nativeAmount), + currency: try CurrencyCode(currencyCode: proto.minDmChatInitFee.currency) + ) : nil ) } } diff --git a/FlipcashCore/Tests/FlipcashCoreTests/TransportClassificationTests.swift b/FlipcashCore/Tests/FlipcashCoreTests/TransportClassificationTests.swift index fc3349142..42b12dc45 100644 --- a/FlipcashCore/Tests/FlipcashCoreTests/TransportClassificationTests.swift +++ b/FlipcashCore/Tests/FlipcashCoreTests/TransportClassificationTests.swift @@ -57,6 +57,7 @@ struct TransportClassificationTests { @Test func errorUnlinkEmail() { assertClassifies(ErrorUnlinkEmail.self) } @Test func errorFetchProfile() { assertClassifies(ErrorFetchProfile.self) } @Test func errorUpdateTipCard() { assertClassifies(ErrorUpdateTipCard.self) } + @Test func errorSetMinDmChatInitFee() { assertClassifies(ErrorSetMinDmChatInitFee.self) } @Test func errorBlocklist() { assertClassifies(ErrorBlocklist.self) } @Test func errorRateHistory() { assertClassifies(ErrorRateHistory.self) } @Test func errorGetSwap() { assertClassifies(ErrorGetSwap.self) } From 1dde363d6b8bda4fbb41058bb22e7254bce2035b Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 27 Aug 2026 12:35:14 -0400 Subject: [PATCH 2/2] chore(deps): pin flipcash2-client-protocol to 0.2.0 --- FlipcashAPI/Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlipcashAPI/Package.swift b/FlipcashAPI/Package.swift index 75921f913..b9d2ebbc5 100644 --- a/FlipcashAPI/Package.swift +++ b/FlipcashAPI/Package.swift @@ -35,7 +35,7 @@ let contractDependencies: [Package.Dependency] = protoLocalRoot.map { root in ] } ?? [ .package(url: "https://github.com/code-payments/ocp-client-protocol", exact: "0.2.0"), - .package(url: "https://github.com/code-payments/flipcash2-client-protocol", exact: "0.1.0"), + .package(url: "https://github.com/code-payments/flipcash2-client-protocol", exact: "0.2.0"), ] let package = Package(