From 932ea5e4a59c988ce5c4188cd41907b9b9655cea Mon Sep 17 00:00:00 2001 From: Dmitry Fedorov <80246944+fedorov-d@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:27:26 +0400 Subject: [PATCH 1/4] IOS-7349 Implement cosmos staking flow --- BlockchainSdk.xcodeproj/project.pbxproj | 4 + .../Cosmos/CosmosProtoMessage.swift | 788 ++++++++++++++++++ .../Cosmos/CosmosTransactionBuilder.swift | 137 ++- .../Cosmos/CosmosWalletManager.swift | 142 ++-- .../PendingTransactionRecordMapper.swift | 19 + 5 files changed, 1034 insertions(+), 56 deletions(-) create mode 100644 BlockchainSdk/Blockchains/Cosmos/CosmosProtoMessage.swift diff --git a/BlockchainSdk.xcodeproj/project.pbxproj b/BlockchainSdk.xcodeproj/project.pbxproj index 10daba92b..a22457095 100644 --- a/BlockchainSdk.xcodeproj/project.pbxproj +++ b/BlockchainSdk.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 0A1399212B637DB900934D7D /* ShibariumExternalLinkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A1399202B637DB900934D7D /* ShibariumExternalLinkProvider.swift */; }; 0A158C022B74E44D0004DC23 /* BlockBookResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A158C002B74E44D0004DC23 /* BlockBookResponses.swift */; }; 0A158C052B74E4680004DC23 /* BitcoinCashNowNodesNetworkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A158C042B74E4680004DC23 /* BitcoinCashNowNodesNetworkProvider.swift */; }; + 0A3BF8EA2C69F5D900163492 /* CosmosProtoMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3BF8E92C69F5D900163492 /* CosmosProtoMessage.swift */; }; 0A54FF452BB475B9000D293D /* TangemNetworkLoggerPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A54FF442BB475B9000D293D /* TangemNetworkLoggerPlugin.swift */; }; 0A6272652BD96736003B2F4D /* SS58.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6272642BD96736003B2F4D /* SS58.swift */; }; 0A7084B82BF797E900FD519D /* TONAddressService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7084B72BF797E900FD519D /* TONAddressService.swift */; }; @@ -908,6 +909,7 @@ 0A1399202B637DB900934D7D /* ShibariumExternalLinkProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShibariumExternalLinkProvider.swift; sourceTree = ""; }; 0A158C002B74E44D0004DC23 /* BlockBookResponses.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockBookResponses.swift; sourceTree = ""; }; 0A158C042B74E4680004DC23 /* BitcoinCashNowNodesNetworkProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCashNowNodesNetworkProvider.swift; sourceTree = ""; }; + 0A3BF8E92C69F5D900163492 /* CosmosProtoMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CosmosProtoMessage.swift; sourceTree = ""; }; 0A54FF442BB475B9000D293D /* TangemNetworkLoggerPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TangemNetworkLoggerPlugin.swift; sourceTree = ""; }; 0A6272642BD96736003B2F4D /* SS58.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SS58.swift; sourceTree = ""; }; 0A7084B72BF797E900FD519D /* TONAddressService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TONAddressService.swift; sourceTree = ""; }; @@ -3352,6 +3354,7 @@ DA60AF1129E8308E0013F5AC /* CosmosWalletAssembly.swift */, DA67A69529E51FEA001B6799 /* CosmosWalletManager.swift */, DC0468FB2B87CC6C00C785D1 /* OtherChains */, + 0A3BF8E92C69F5D900163492 /* CosmosProtoMessage.swift */, ); path = Cosmos; sourceTree = ""; @@ -4682,6 +4685,7 @@ DC5E646D2B16098C00E81AA5 /* RIPEMD160+.swift in Sources */, DC5E65072B1650F400E81AA5 /* OP_2DIV.swift in Sources */, 2DA4A4422BB5431700E55526 /* RadiantTransactionBuilder.swift in Sources */, + 0A3BF8EA2C69F5D900163492 /* CosmosProtoMessage.swift in Sources */, EF3B19342AA85CE90084AA1C /* DogecoinExternalLinkProvider.swift in Sources */, EF2D9DA52BC3F6770055C485 /* EthereumTransactionParams.swift in Sources */, EF0DA78C285246A90081092A /* DashMainNetworkParams.swift in Sources */, diff --git a/BlockchainSdk/Blockchains/Cosmos/CosmosProtoMessage.swift b/BlockchainSdk/Blockchains/Cosmos/CosmosProtoMessage.swift new file mode 100644 index 000000000..644c77a9c --- /dev/null +++ b/BlockchainSdk/Blockchains/Cosmos/CosmosProtoMessage.swift @@ -0,0 +1,788 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: cosmos_1.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +struct CosmosProtoMessage: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var delegateContainer: CosmosProtoMessage.CosmosMessageDelegateContainer { + get {return _delegateContainer ?? CosmosProtoMessage.CosmosMessageDelegateContainer()} + set {_delegateContainer = newValue} + } + /// Returns true if `delegateContainer` has been explicitly set. + var hasDelegateContainer: Bool {return self._delegateContainer != nil} + /// Clears the value of `delegateContainer`. Subsequent reads from it will return its default value. + mutating func clearDelegateContainer() {self._delegateContainer = nil} + + var feeAndKeyContainer: CosmosProtoMessage.CosmosMessageFeeAndKeyContainer { + get {return _feeAndKeyContainer ?? CosmosProtoMessage.CosmosMessageFeeAndKeyContainer()} + set {_feeAndKeyContainer = newValue} + } + /// Returns true if `feeAndKeyContainer` has been explicitly set. + var hasFeeAndKeyContainer: Bool {return self._feeAndKeyContainer != nil} + /// Clears the value of `feeAndKeyContainer`. Subsequent reads from it will return its default value. + mutating func clearFeeAndKeyContainer() {self._feeAndKeyContainer = nil} + + var chainID: String = String() + + var accountNumber: Int32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + struct CosmosMessageDelegateContainer: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var delegate: CosmosProtoMessage.CosmosMessageDelegate { + get {return _delegate ?? CosmosProtoMessage.CosmosMessageDelegate()} + set {_delegate = newValue} + } + /// Returns true if `delegate` has been explicitly set. + var hasDelegate: Bool {return self._delegate != nil} + /// Clears the value of `delegate`. Subsequent reads from it will return its default value. + mutating func clearDelegate() {self._delegate = nil} + + var stakingProvider: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _delegate: CosmosProtoMessage.CosmosMessageDelegate? = nil + } + + struct CosmosMessageDelegate: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var messageType: String = String() + + var delegateData: CosmosProtoMessage.DelegateData { + get {return _delegateData ?? CosmosProtoMessage.DelegateData()} + set {_delegateData = newValue} + } + /// Returns true if `delegateData` has been explicitly set. + var hasDelegateData: Bool {return self._delegateData != nil} + /// Clears the value of `delegateData`. Subsequent reads from it will return its default value. + mutating func clearDelegateData() {self._delegateData = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _delegateData: CosmosProtoMessage.DelegateData? = nil + } + + struct CosmosMessagePublicKeyContainer: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var publicKeyWrapper: CosmosProtoMessage.CosmosMessagePublicKeyWrapper { + get {return _publicKeyWrapper ?? CosmosProtoMessage.CosmosMessagePublicKeyWrapper()} + set {_publicKeyWrapper = newValue} + } + /// Returns true if `publicKeyWrapper` has been explicitly set. + var hasPublicKeyWrapper: Bool {return self._publicKeyWrapper != nil} + /// Clears the value of `publicKeyWrapper`. Subsequent reads from it will return its default value. + mutating func clearPublicKeyWrapper() {self._publicKeyWrapper = nil} + + var publicKeyParamWrapper: CosmosProtoMessage.CosmosMessagePublicKeyParamWrapper { + get {return _publicKeyParamWrapper ?? CosmosProtoMessage.CosmosMessagePublicKeyParamWrapper()} + set {_publicKeyParamWrapper = newValue} + } + /// Returns true if `publicKeyParamWrapper` has been explicitly set. + var hasPublicKeyParamWrapper: Bool {return self._publicKeyParamWrapper != nil} + /// Clears the value of `publicKeyParamWrapper`. Subsequent reads from it will return its default value. + mutating func clearPublicKeyParamWrapper() {self._publicKeyParamWrapper = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _publicKeyWrapper: CosmosProtoMessage.CosmosMessagePublicKeyWrapper? = nil + fileprivate var _publicKeyParamWrapper: CosmosProtoMessage.CosmosMessagePublicKeyParamWrapper? = nil + } + + struct CosmosMessagePublicKeyWrapper: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var publicKeyType: String = String() + + var publicKey: CosmosProtoMessage.CosmosMessagePublicKey { + get {return _publicKey ?? CosmosProtoMessage.CosmosMessagePublicKey()} + set {_publicKey = newValue} + } + /// Returns true if `publicKey` has been explicitly set. + var hasPublicKey: Bool {return self._publicKey != nil} + /// Clears the value of `publicKey`. Subsequent reads from it will return its default value. + mutating func clearPublicKey() {self._publicKey = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _publicKey: CosmosProtoMessage.CosmosMessagePublicKey? = nil + } + + struct CosmosMessagePublicKeyParam: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var param: Int32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + } + + struct DelegateData: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var delegatorAddress: String = String() + + var validatorAddress: String = String() + + var delegateAmount: CosmosProtoMessage.DelegateAmount { + get {return _delegateAmount ?? CosmosProtoMessage.DelegateAmount()} + set {_delegateAmount = newValue} + } + /// Returns true if `delegateAmount` has been explicitly set. + var hasDelegateAmount: Bool {return self._delegateAmount != nil} + /// Clears the value of `delegateAmount`. Subsequent reads from it will return its default value. + mutating func clearDelegateAmount() {self._delegateAmount = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _delegateAmount: CosmosProtoMessage.DelegateAmount? = nil + } + + struct CosmosMessageFeeAndKeyContainer: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var publicKeyContainer: CosmosProtoMessage.CosmosMessagePublicKeyContainer { + get {return _publicKeyContainer ?? CosmosProtoMessage.CosmosMessagePublicKeyContainer()} + set {_publicKeyContainer = newValue} + } + /// Returns true if `publicKeyContainer` has been explicitly set. + var hasPublicKeyContainer: Bool {return self._publicKeyContainer != nil} + /// Clears the value of `publicKeyContainer`. Subsequent reads from it will return its default value. + mutating func clearPublicKeyContainer() {self._publicKeyContainer = nil} + + var feeContainer: CosmosProtoMessage.CosmosMessageFeeContainer { + get {return _feeContainer ?? CosmosProtoMessage.CosmosMessageFeeContainer()} + set {_feeContainer = newValue} + } + /// Returns true if `feeContainer` has been explicitly set. + var hasFeeContainer: Bool {return self._feeContainer != nil} + /// Clears the value of `feeContainer`. Subsequent reads from it will return its default value. + mutating func clearFeeContainer() {self._feeContainer = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _publicKeyContainer: CosmosProtoMessage.CosmosMessagePublicKeyContainer? = nil + fileprivate var _feeContainer: CosmosProtoMessage.CosmosMessageFeeContainer? = nil + } + + struct CosmosMessagePublicKey: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var publicKey: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + } + + struct CosmosMessagePublicKeyParamWrapper: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var publicKeyParam: CosmosProtoMessage.CosmosMessagePublicKeyParam { + get {return _publicKeyParam ?? CosmosProtoMessage.CosmosMessagePublicKeyParam()} + set {_publicKeyParam = newValue} + } + /// Returns true if `publicKeyParam` has been explicitly set. + var hasPublicKeyParam: Bool {return self._publicKeyParam != nil} + /// Clears the value of `publicKeyParam`. Subsequent reads from it will return its default value. + mutating func clearPublicKeyParam() {self._publicKeyParam = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _publicKeyParam: CosmosProtoMessage.CosmosMessagePublicKeyParam? = nil + } + + struct CosmosMessageFeeContainer: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var feeAmount: CosmosProtoMessage.DelegateAmount { + get {return _feeAmount ?? CosmosProtoMessage.DelegateAmount()} + set {_feeAmount = newValue} + } + /// Returns true if `feeAmount` has been explicitly set. + var hasFeeAmount: Bool {return self._feeAmount != nil} + /// Clears the value of `feeAmount`. Subsequent reads from it will return its default value. + mutating func clearFeeAmount() {self._feeAmount = nil} + + var gas: UInt64 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _feeAmount: CosmosProtoMessage.DelegateAmount? = nil + } + + struct DelegateAmount: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var denomination: String = String() + + var amount: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + } + + init() {} + + fileprivate var _delegateContainer: CosmosProtoMessage.CosmosMessageDelegateContainer? = nil + fileprivate var _feeAndKeyContainer: CosmosProtoMessage.CosmosMessageFeeAndKeyContainer? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension CosmosProtoMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "CosmosProtoMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "delegateContainer"), + 2: .same(proto: "feeAndKeyContainer"), + 3: .same(proto: "chainId"), + 4: .same(proto: "accountNumber"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._delegateContainer) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._feeAndKeyContainer) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.chainID) }() + case 4: try { try decoder.decodeSingularInt32Field(value: &self.accountNumber) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._delegateContainer { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._feeAndKeyContainer { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + if !self.chainID.isEmpty { + try visitor.visitSingularStringField(value: self.chainID, fieldNumber: 3) + } + if self.accountNumber != 0 { + try visitor.visitSingularInt32Field(value: self.accountNumber, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: CosmosProtoMessage, rhs: CosmosProtoMessage) -> Bool { + if lhs._delegateContainer != rhs._delegateContainer {return false} + if lhs._feeAndKeyContainer != rhs._feeAndKeyContainer {return false} + if lhs.chainID != rhs.chainID {return false} + if lhs.accountNumber != rhs.accountNumber {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension CosmosProtoMessage.CosmosMessageDelegateContainer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = CosmosProtoMessage.protoMessageName + ".CosmosMessageDelegateContainer" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "delegate"), + 2: .same(proto: "stakingProvider"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._delegate) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.stakingProvider) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._delegate { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if !self.stakingProvider.isEmpty { + try visitor.visitSingularStringField(value: self.stakingProvider, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: CosmosProtoMessage.CosmosMessageDelegateContainer, rhs: CosmosProtoMessage.CosmosMessageDelegateContainer) -> Bool { + if lhs._delegate != rhs._delegate {return false} + if lhs.stakingProvider != rhs.stakingProvider {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension CosmosProtoMessage.CosmosMessageDelegate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = CosmosProtoMessage.protoMessageName + ".CosmosMessageDelegate" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "messageType"), + 2: .same(proto: "delegateData"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.messageType) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._delegateData) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.messageType.isEmpty { + try visitor.visitSingularStringField(value: self.messageType, fieldNumber: 1) + } + try { if let v = self._delegateData { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: CosmosProtoMessage.CosmosMessageDelegate, rhs: CosmosProtoMessage.CosmosMessageDelegate) -> Bool { + if lhs.messageType != rhs.messageType {return false} + if lhs._delegateData != rhs._delegateData {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension CosmosProtoMessage.CosmosMessagePublicKeyContainer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = CosmosProtoMessage.protoMessageName + ".CosmosMessagePublicKeyContainer" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "publicKeyWrapper"), + 2: .same(proto: "publicKeyParamWrapper"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._publicKeyWrapper) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._publicKeyParamWrapper) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._publicKeyWrapper { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._publicKeyParamWrapper { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: CosmosProtoMessage.CosmosMessagePublicKeyContainer, rhs: CosmosProtoMessage.CosmosMessagePublicKeyContainer) -> Bool { + if lhs._publicKeyWrapper != rhs._publicKeyWrapper {return false} + if lhs._publicKeyParamWrapper != rhs._publicKeyParamWrapper {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension CosmosProtoMessage.CosmosMessagePublicKeyWrapper: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = CosmosProtoMessage.protoMessageName + ".CosmosMessagePublicKeyWrapper" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "publicKeyType"), + 2: .same(proto: "publicKey"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.publicKeyType) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._publicKey) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.publicKeyType.isEmpty { + try visitor.visitSingularStringField(value: self.publicKeyType, fieldNumber: 1) + } + try { if let v = self._publicKey { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: CosmosProtoMessage.CosmosMessagePublicKeyWrapper, rhs: CosmosProtoMessage.CosmosMessagePublicKeyWrapper) -> Bool { + if lhs.publicKeyType != rhs.publicKeyType {return false} + if lhs._publicKey != rhs._publicKey {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension CosmosProtoMessage.CosmosMessagePublicKeyParam: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = CosmosProtoMessage.protoMessageName + ".CosmosMessagePublicKeyParam" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "param"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt32Field(value: &self.param) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.param != 0 { + try visitor.visitSingularInt32Field(value: self.param, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: CosmosProtoMessage.CosmosMessagePublicKeyParam, rhs: CosmosProtoMessage.CosmosMessagePublicKeyParam) -> Bool { + if lhs.param != rhs.param {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension CosmosProtoMessage.DelegateData: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = CosmosProtoMessage.protoMessageName + ".DelegateData" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "delegatorAddress"), + 2: .same(proto: "validatorAddress"), + 3: .same(proto: "delegateAmount"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.delegatorAddress) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.validatorAddress) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._delegateAmount) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.delegatorAddress.isEmpty { + try visitor.visitSingularStringField(value: self.delegatorAddress, fieldNumber: 1) + } + if !self.validatorAddress.isEmpty { + try visitor.visitSingularStringField(value: self.validatorAddress, fieldNumber: 2) + } + try { if let v = self._delegateAmount { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: CosmosProtoMessage.DelegateData, rhs: CosmosProtoMessage.DelegateData) -> Bool { + if lhs.delegatorAddress != rhs.delegatorAddress {return false} + if lhs.validatorAddress != rhs.validatorAddress {return false} + if lhs._delegateAmount != rhs._delegateAmount {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension CosmosProtoMessage.CosmosMessageFeeAndKeyContainer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = CosmosProtoMessage.protoMessageName + ".CosmosMessageFeeAndKeyContainer" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "publicKeyContainer"), + 2: .same(proto: "feeContainer"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._publicKeyContainer) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._feeContainer) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._publicKeyContainer { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._feeContainer { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: CosmosProtoMessage.CosmosMessageFeeAndKeyContainer, rhs: CosmosProtoMessage.CosmosMessageFeeAndKeyContainer) -> Bool { + if lhs._publicKeyContainer != rhs._publicKeyContainer {return false} + if lhs._feeContainer != rhs._feeContainer {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension CosmosProtoMessage.CosmosMessagePublicKey: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = CosmosProtoMessage.protoMessageName + ".CosmosMessagePublicKey" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "publicKey"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.publicKey) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.publicKey.isEmpty { + try visitor.visitSingularBytesField(value: self.publicKey, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: CosmosProtoMessage.CosmosMessagePublicKey, rhs: CosmosProtoMessage.CosmosMessagePublicKey) -> Bool { + if lhs.publicKey != rhs.publicKey {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension CosmosProtoMessage.CosmosMessagePublicKeyParamWrapper: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = CosmosProtoMessage.protoMessageName + ".CosmosMessagePublicKeyParamWrapper" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "publicKeyParam"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._publicKeyParam) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._publicKeyParam { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: CosmosProtoMessage.CosmosMessagePublicKeyParamWrapper, rhs: CosmosProtoMessage.CosmosMessagePublicKeyParamWrapper) -> Bool { + if lhs._publicKeyParam != rhs._publicKeyParam {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension CosmosProtoMessage.CosmosMessageFeeContainer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = CosmosProtoMessage.protoMessageName + ".CosmosMessageFeeContainer" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "feeAmount"), + 2: .same(proto: "gas"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._feeAmount) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.gas) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._feeAmount { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if self.gas != 0 { + try visitor.visitSingularUInt64Field(value: self.gas, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: CosmosProtoMessage.CosmosMessageFeeContainer, rhs: CosmosProtoMessage.CosmosMessageFeeContainer) -> Bool { + if lhs._feeAmount != rhs._feeAmount {return false} + if lhs.gas != rhs.gas {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension CosmosProtoMessage.DelegateAmount: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = CosmosProtoMessage.protoMessageName + ".DelegateAmount" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "denomination"), + 2: .same(proto: "amount"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.denomination) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.amount) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.denomination.isEmpty { + try visitor.visitSingularStringField(value: self.denomination, fieldNumber: 1) + } + if !self.amount.isEmpty { + try visitor.visitSingularStringField(value: self.amount, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: CosmosProtoMessage.DelegateAmount, rhs: CosmosProtoMessage.DelegateAmount) -> Bool { + if lhs.denomination != rhs.denomination {return false} + if lhs.amount != rhs.amount {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/BlockchainSdk/Blockchains/Cosmos/CosmosTransactionBuilder.swift b/BlockchainSdk/Blockchains/Cosmos/CosmosTransactionBuilder.swift index b2d8204ee..89fea7d45 100644 --- a/BlockchainSdk/Blockchains/Cosmos/CosmosTransactionBuilder.swift +++ b/BlockchainSdk/Blockchains/Cosmos/CosmosTransactionBuilder.swift @@ -32,10 +32,45 @@ class CosmosTransactionBuilder { func setAccountNumber(_ accountNumber: UInt64) { self.accountNumber = accountNumber } + + // MARK: Regular transaction func buildForSign(transaction: Transaction) throws -> Data { let input = try makeInput(transaction: transaction, fee: transaction.fee) let txInputData = try input.serializedData() + + return try buildForSignInternal(txInputData: txInputData) + } + + func buildForSend(transaction: Transaction, signature: Data) throws -> Data { + let input = try makeInput(transaction: transaction, fee: transaction.fee) + let txInputData = try input.serializedData() + + return try buildForSendInternal(txInputData: txInputData, signature: signature) + } + + // MARK: Staking + + func buildForSign(stakingTransaction: StakeKitTransaction) throws -> Data { + let input = try makeInput(stakingTransaction: stakingTransaction) + let txInputData = try input.serializedData() + + return try buildForSignInternal(txInputData: txInputData) + } + + func buildForSend( + stakingTransaction: StakeKitTransaction, + signature: Data + ) throws -> Data { + let input = try makeInput(stakingTransaction: stakingTransaction) + let txInputData = try input.serializedData() + + return try buildForSendInternal(txInputData: txInputData, signature: signature) + } + + // MARK: Private + + private func buildForSignInternal(txInputData: Data) throws -> Data { let preImageHashes = TransactionCompiler.preImageHashes(coinType: cosmosChain.coin, txInputData: txInputData) let output = try TxCompilerPreSigningOutput(serializedData: preImageHashes) @@ -46,10 +81,7 @@ class CosmosTransactionBuilder { return output.dataHash } - func buildForSend(transaction: Transaction, signature: Data) throws -> Data { - let input = try makeInput(transaction: transaction, fee: transaction.fee) - let txInputData = try input.serializedData() - + private func buildForSendInternal(txInputData: Data, signature: Data) throws -> Data { let publicKeys = DataVector() publicKeys.add(data: publicKey) @@ -165,6 +197,45 @@ class CosmosTransactionBuilder { return input } + private func makeInput( + stakingTransaction: StakeKitTransaction + ) throws -> CosmosSigningInput { + guard let accountNumber, let sequenceNumber else { + throw WalletError.failedToBuildTx + } + + let stakingProtoMessage = try CosmosProtoMessage(serializedData: Data(hex: stakingTransaction.unsignedData)) + + let feeMessage = stakingProtoMessage.feeAndKeyContainer.feeContainer + let feeValue = feeMessage.feeAmount + + guard let message = CosmosMessage.createStakeMessage(message: stakingProtoMessage.delegateContainer.delegate) else { + throw WalletError.failedToBuildTx + } + let fee = CosmosFee.with { fee in + fee.gas = feeMessage.gas + fee.amounts = [ + CosmosAmount.with { amount in + amount.amount = feeValue.amount + amount.denom = feeValue.denomination + } + ] + } + + let input = CosmosSigningInput.with { + $0.mode = .sync + $0.signingMode = .protobuf + $0.accountNumber = accountNumber + $0.chainID = cosmosChain.chainID + $0.sequence = sequenceNumber + $0.publicKey = publicKey + $0.messages = [message] + $0.privateKey = Data(repeating: 1, count: 32) + $0.fee = fee + } + return input + } + private func denomination(for amount: Amount) throws -> String { switch amount.type { case .coin: @@ -198,3 +269,61 @@ class CosmosTransactionBuilder { } } } + +extension CosmosMessage { + static func createStakeMessage( + message: CosmosProtoMessage.CosmosMessageDelegate + ) -> Self? { + let type = message.messageType + guard message.hasDelegateData else { + return nil + } + let delegateData = message.delegateData + + switch type { + case (let string) where string.contains(Constants.delegateMessage.rawValue): + let delegateAmount = delegateData.delegateAmount + let stakeMessage = CosmosMessage.Delegate.with { delegate in + delegate.amount = CosmosAmount.with { amount in + amount.amount = delegateAmount.amount + amount.denom = delegateAmount.denomination + } + delegate.delegatorAddress = delegateData.delegatorAddress + delegate.validatorAddress = delegateData.validatorAddress + } + return CosmosMessage.with { + $0.stakeMessage = stakeMessage + } + case (let string) where string.contains(Constants.withdrawMessage.rawValue): + let withdrawMessage = CosmosMessage.WithdrawDelegationReward.with { reward in + reward.delegatorAddress = delegateData.delegatorAddress + reward.validatorAddress = delegateData.validatorAddress + } + return CosmosMessage.with { + $0.withdrawStakeRewardMessage = withdrawMessage + } + case (let string) where string.contains(Constants.undelegateMessage.rawValue): + let delegateAmount = delegateData.delegateAmount + let unstakeMessage = CosmosMessage.Undelegate.with { delegate in + delegate.amount = CosmosAmount.with { amount in + amount.amount = delegateAmount.amount + amount.denom = delegateAmount.denomination + } + delegate.delegatorAddress = delegateData.delegatorAddress + delegate.validatorAddress = delegateData.validatorAddress + } + return CosmosMessage.with { + $0.unstakeMessage = unstakeMessage + } + default: return nil + } + } +} + +extension CosmosMessage { + enum Constants: String { + case delegateMessage = "MsgDelegate" + case withdrawMessage = "MsgWithdrawDelegatorReward" + case undelegateMessage = "MsgUndelegate" + } +} diff --git a/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift b/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift index 38422b793..b6594d993 100644 --- a/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift +++ b/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift @@ -45,60 +45,22 @@ class CosmosWalletManager: BaseManager, WalletManager { } func send(_ transaction: Transaction, signer: TransactionSigner) -> AnyPublisher { - return Just(()) - .receive(on: DispatchQueue.global()) - .setFailureType(to: Error.self) - .tryMap { [weak self] Void -> Data in - guard let self else { - throw WalletError.empty - } - - return try self.txBuilder.buildForSign(transaction: transaction) - } - .flatMap { [weak self] hash -> AnyPublisher in - guard let self else { - return .anyFail(error: WalletError.empty) - } - - return signer - .sign(hash: hash, walletPublicKey: self.wallet.publicKey) - .tryMap { [weak self] signature -> Data in - guard let self else { - throw WalletError.empty - } - - let signature = try Secp256k1Signature(with: signature) - return try signature.unmarshal(with: self.wallet.publicKey.blockchainKey, hash: hash).data - } - .eraseToAnyPublisher() - } - .tryMap { [weak self] signature -> Data in - guard let self else { - throw WalletError.empty - } - - return try self.txBuilder.buildForSend(transaction: transaction, signature: signature) - } - .flatMap { [weak self] transaction -> AnyPublisher in - guard let self else { - return .anyFail(error: WalletError.empty) - } - - return self.networkService - .send(transaction: transaction) - .mapSendError(tx: transaction.hexString.lowercased()) - .eraseToAnyPublisher() - } - .handleEvents(receiveOutput: { [weak self] hash in + sendGeneric( + transaction: transaction, + signer: signer, + buildForSign: { [weak self] in + guard let self else { throw WalletError.empty } + return try txBuilder.buildForSign(transaction: transaction) + }, + buildForSend: { [weak self] signature in + guard let self else { throw WalletError.empty } + return try txBuilder.buildForSend(transaction: transaction, signature: signature) + }, + mapToPending: { hash in let mapper = PendingTransactionRecordMapper() - let record = mapper.mapToPendingTransactionRecord(transaction: transaction, hash: hash) - self?.wallet.addPendingTransaction(record) - }) - .map { - TransactionSendResult(hash: $0) + return mapper.mapToPendingTransactionRecord(transaction: transaction, hash: hash) } - .eraseSendError() - .eraseToAnyPublisher() + ) } func getFee(amount: Amount, destination: String) -> AnyPublisher<[Fee], Error> { @@ -220,3 +182,79 @@ class CosmosWalletManager: BaseManager, WalletManager { } extension CosmosWalletManager: ThenProcessable { } + +extension CosmosWalletManager: StakeKitTransactionSender { + func sendStakeKit( + transaction: StakeKitTransaction, + signer: any TransactionSigner + ) -> AnyPublisher { + sendGeneric( + transaction: transaction, + signer: signer, + buildForSign: { [weak self] in + guard let self else { throw WalletError.empty } + return try txBuilder.buildForSign(stakingTransaction: transaction) + }, + buildForSend: { [weak self] signature in + guard let self else { throw WalletError.empty } + return try txBuilder.buildForSend(stakingTransaction: transaction, signature: signature) + }, + mapToPending: { hash in + let mapper = PendingTransactionRecordMapper() + return mapper.mapToPendingTransactionRecord(stakeKitTransaction: transaction, hash: hash) + } + ) + } +} + +extension CosmosWalletManager { + func sendGeneric( + transaction: T, + signer: any TransactionSigner, + buildForSign: @escaping () throws -> Data, + buildForSend: @escaping (Data) throws -> Data, + mapToPending: @escaping (String) -> PendingTransactionRecord + ) -> AnyPublisher { + Just(()) + .receive(on: DispatchQueue.global()) + .setFailureType(to: Error.self) + .tryMap(buildForSign) + .flatMap { [weak self] hash -> AnyPublisher in + guard let self else { + return .anyFail(error: WalletError.empty) + } + + return signer + .sign(hash: hash, walletPublicKey: wallet.publicKey) + .tryMap { [weak self] signature -> Data in + guard let self else { + throw WalletError.empty + } + + let signature = try Secp256k1Signature(with: signature) + return try signature.unmarshal(with: wallet.publicKey.blockchainKey, hash: hash).data + } + .eraseToAnyPublisher() + } + .tryMap(buildForSend) + .flatMap { [weak self] transaction -> AnyPublisher in + guard let self else { + return .anyFail(error: WalletError.empty) + } + + return self.networkService + .send(transaction: transaction) + .mapSendError(tx: transaction.hexString.lowercased()) + .eraseToAnyPublisher() + } + .handleEvents(receiveOutput: { [weak self] hash in + let record = mapToPending(hash) + self?.wallet.addPendingTransaction(record) + }) + .map { + TransactionSendResult(hash: $0) + } + .eraseSendError() + .eraseToAnyPublisher() + } +} diff --git a/BlockchainSdk/Common/PendingTransactionRecordMapper.swift b/BlockchainSdk/Common/PendingTransactionRecordMapper.swift index fe4b7be46..117b0f785 100644 --- a/BlockchainSdk/Common/PendingTransactionRecordMapper.swift +++ b/BlockchainSdk/Common/PendingTransactionRecordMapper.swift @@ -41,6 +41,25 @@ struct PendingTransactionRecordMapper { transactionParams: transaction.params ) } + + func mapToPendingTransactionRecord( + transaction: StakeKitTransaction, + hash: String, + date: Date = Date(), + isIncoming: Bool = false + ) -> PendingTransactionRecord { + PendingTransactionRecord( + hash: hash, + source: transaction.sourceAddress, + destination: "", + amount: transaction.amount, + fee: transaction.fee, + date: date, + isIncoming: isIncoming, + transactionType: .stake, + transactionParams: nil + ) + } func mapToPendingTransactionRecord( stakeKitTransaction: StakeKitTransaction, From 5378d4467473ace1c50324abb5593c70a9a99acb Mon Sep 17 00:00:00 2001 From: Dmitry Fedorov <80246944+fedorov-d@users.noreply.github.com> Date: Wed, 14 Aug 2024 20:49:49 +0400 Subject: [PATCH 2/4] IOS-7349 Remove duplicated method --- .../PendingTransactionRecordMapper.swift | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/BlockchainSdk/Common/PendingTransactionRecordMapper.swift b/BlockchainSdk/Common/PendingTransactionRecordMapper.swift index 117b0f785..fe4b7be46 100644 --- a/BlockchainSdk/Common/PendingTransactionRecordMapper.swift +++ b/BlockchainSdk/Common/PendingTransactionRecordMapper.swift @@ -41,25 +41,6 @@ struct PendingTransactionRecordMapper { transactionParams: transaction.params ) } - - func mapToPendingTransactionRecord( - transaction: StakeKitTransaction, - hash: String, - date: Date = Date(), - isIncoming: Bool = false - ) -> PendingTransactionRecord { - PendingTransactionRecord( - hash: hash, - source: transaction.sourceAddress, - destination: "", - amount: transaction.amount, - fee: transaction.fee, - date: date, - isIncoming: isIncoming, - transactionType: .stake, - transactionParams: nil - ) - } func mapToPendingTransactionRecord( stakeKitTransaction: StakeKitTransaction, From fd0db4a17c7e3453ac05dd0b5e703ade0b33853c Mon Sep 17 00:00:00 2001 From: Dmitry Fedorov <80246944+fedorov-d@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:13:00 +0400 Subject: [PATCH 3/4] IOS-7349 Remove generic send method --- .../Cosmos/CosmosWalletManager.swift | 116 +++++++++++------- 1 file changed, 70 insertions(+), 46 deletions(-) diff --git a/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift b/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift index b6594d993..b39bceefa 100644 --- a/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift +++ b/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift @@ -45,22 +45,60 @@ class CosmosWalletManager: BaseManager, WalletManager { } func send(_ transaction: Transaction, signer: TransactionSigner) -> AnyPublisher { - sendGeneric( - transaction: transaction, - signer: signer, - buildForSign: { [weak self] in - guard let self else { throw WalletError.empty } - return try txBuilder.buildForSign(transaction: transaction) - }, - buildForSend: { [weak self] signature in - guard let self else { throw WalletError.empty } - return try txBuilder.buildForSend(transaction: transaction, signature: signature) - }, - mapToPending: { hash in + Just(()) + .receive(on: DispatchQueue.global()) + .setFailureType(to: Error.self) + .tryMap { [weak self] Void -> Data in + guard let self else { + throw WalletError.empty + } + + return try self.txBuilder.buildForSign(transaction: transaction) + } + .flatMap { [weak self] hash -> AnyPublisher in + guard let self else { + return .anyFail(error: WalletError.empty) + } + + return signer + .sign(hash: hash, walletPublicKey: self.wallet.publicKey) + .tryMap { [weak self] signature -> Data in + guard let self else { + throw WalletError.empty + } + + let signature = try Secp256k1Signature(with: signature) + return try signature.unmarshal(with: self.wallet.publicKey.blockchainKey, hash: hash).data + } + .eraseToAnyPublisher() + } + .tryMap { [weak self] signature -> Data in + guard let self else { + throw WalletError.empty + } + + return try self.txBuilder.buildForSend(transaction: transaction, signature: signature) + } + .flatMap { [weak self] transaction -> AnyPublisher in + guard let self else { + return .anyFail(error: WalletError.empty) + } + + return self.networkService + .send(transaction: transaction) + .mapSendError(tx: transaction.hexString.lowercased()) + .eraseToAnyPublisher() + } + .handleEvents(receiveOutput: { [weak self] hash in let mapper = PendingTransactionRecordMapper() - return mapper.mapToPendingTransactionRecord(transaction: transaction, hash: hash) + let record = mapper.mapToPendingTransactionRecord(transaction: transaction, hash: hash) + self?.wallet.addPendingTransaction(record) + }) + .map { + TransactionSendResult(hash: $0) } - ) + .eraseSendError() + .eraseToAnyPublisher() } func getFee(amount: Amount, destination: String) -> AnyPublisher<[Fee], Error> { @@ -187,56 +225,41 @@ extension CosmosWalletManager: StakeKitTransactionSender { func sendStakeKit( transaction: StakeKitTransaction, signer: any TransactionSigner - ) -> AnyPublisher { - sendGeneric( - transaction: transaction, - signer: signer, - buildForSign: { [weak self] in - guard let self else { throw WalletError.empty } - return try txBuilder.buildForSign(stakingTransaction: transaction) - }, - buildForSend: { [weak self] signature in - guard let self else { throw WalletError.empty } - return try txBuilder.buildForSend(stakingTransaction: transaction, signature: signature) - }, - mapToPending: { hash in - let mapper = PendingTransactionRecordMapper() - return mapper.mapToPendingTransactionRecord(stakeKitTransaction: transaction, hash: hash) - } - ) - } -} - -extension CosmosWalletManager { - func sendGeneric( - transaction: T, - signer: any TransactionSigner, - buildForSign: @escaping () throws -> Data, - buildForSend: @escaping (Data) throws -> Data, - mapToPending: @escaping (String) -> PendingTransactionRecord ) -> AnyPublisher { Just(()) .receive(on: DispatchQueue.global()) .setFailureType(to: Error.self) - .tryMap(buildForSign) + .tryMap { [weak self] Void -> Data in + guard let self else { + throw WalletError.empty + } + + return try txBuilder.buildForSign(stakingTransaction: transaction) + } .flatMap { [weak self] hash -> AnyPublisher in guard let self else { return .anyFail(error: WalletError.empty) } return signer - .sign(hash: hash, walletPublicKey: wallet.publicKey) + .sign(hash: hash, walletPublicKey: self.wallet.publicKey) .tryMap { [weak self] signature -> Data in guard let self else { throw WalletError.empty } let signature = try Secp256k1Signature(with: signature) - return try signature.unmarshal(with: wallet.publicKey.blockchainKey, hash: hash).data + return try signature.unmarshal(with: self.wallet.publicKey.blockchainKey, hash: hash).data } .eraseToAnyPublisher() } - .tryMap(buildForSend) + .tryMap { [weak self] signature -> Data in + guard let self else { + throw WalletError.empty + } + + return try txBuilder.buildForSend(stakingTransaction: transaction, signature: signature) + } .flatMap { [weak self] transaction -> AnyPublisher in guard let self else { return .anyFail(error: WalletError.empty) @@ -248,7 +271,8 @@ extension CosmosWalletManager { .eraseToAnyPublisher() } .handleEvents(receiveOutput: { [weak self] hash in - let record = mapToPending(hash) + let mapper = PendingTransactionRecordMapper() + let record = mapper.mapToPendingTransactionRecord(stakeKitTransaction: transaction, hash: hash) self?.wallet.addPendingTransaction(record) }) .map { From 1a38d7986c7007778417dfc8d2bcad9b6a33012d Mon Sep 17 00:00:00 2001 From: Dmitry Fedorov <80246944+fedorov-d@users.noreply.github.com> Date: Thu, 15 Aug 2024 17:38:15 +0400 Subject: [PATCH 4/4] IOS-7349 Refactors send and stake methods in CosmosWalletManager --- .../Cosmos/CosmosWalletManager.swift | 166 +++++++----------- 1 file changed, 60 insertions(+), 106 deletions(-) diff --git a/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift b/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift index b39bceefa..9e9b090b6 100644 --- a/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift +++ b/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift @@ -45,60 +45,37 @@ class CosmosWalletManager: BaseManager, WalletManager { } func send(_ transaction: Transaction, signer: TransactionSigner) -> AnyPublisher { - Just(()) - .receive(on: DispatchQueue.global()) - .setFailureType(to: Error.self) - .tryMap { [weak self] Void -> Data in - guard let self else { - throw WalletError.empty - } - - return try self.txBuilder.buildForSign(transaction: transaction) - } - .flatMap { [weak self] hash -> AnyPublisher in - guard let self else { - return .anyFail(error: WalletError.empty) - } - - return signer - .sign(hash: hash, walletPublicKey: self.wallet.publicKey) - .tryMap { [weak self] signature -> Data in - guard let self else { - throw WalletError.empty - } - - let signature = try Secp256k1Signature(with: signature) - return try signature.unmarshal(with: self.wallet.publicKey.blockchainKey, hash: hash).data - } - .eraseToAnyPublisher() - } - .tryMap { [weak self] signature -> Data in - guard let self else { - throw WalletError.empty - } - - return try self.txBuilder.buildForSend(transaction: transaction, signature: signature) - } - .flatMap { [weak self] transaction -> AnyPublisher in - guard let self else { - return .anyFail(error: WalletError.empty) + Result { + try txBuilder.buildForSign(transaction: transaction) + } + .publisher + .withWeakCaptureOf(self) + .flatMap { manager, hash in + signer + .sign(hash: hash, walletPublicKey: self.wallet.publicKey) + .tryMap { signature -> Data in + let signature = try Secp256k1Signature(with: signature) + return try signature.unmarshal(with: manager.wallet.publicKey.blockchainKey, hash: hash).data } - - return self.networkService - .send(transaction: transaction) - .mapSendError(tx: transaction.hexString.lowercased()) - .eraseToAnyPublisher() - } - .handleEvents(receiveOutput: { [weak self] hash in - let mapper = PendingTransactionRecordMapper() - let record = mapper.mapToPendingTransactionRecord(transaction: transaction, hash: hash) - self?.wallet.addPendingTransaction(record) - }) - .map { - TransactionSendResult(hash: $0) - } - .eraseSendError() - .eraseToAnyPublisher() + } + .withWeakCaptureOf(self) + .tryMap { manager, signature -> Data in + try manager.txBuilder.buildForSend(transaction: transaction, signature: signature) + } + .withWeakCaptureOf(self) + .flatMap { manager, transaction in + manager.networkService + .send(transaction: transaction) + .mapSendError(tx: transaction.hexString.lowercased()) + } + .handleEvents(receiveOutput: { [weak self] hash in + let mapper = PendingTransactionRecordMapper() + let record = mapper.mapToPendingTransactionRecord(transaction: transaction, hash: hash) + self?.wallet.addPendingTransaction(record) + }) + .map { TransactionSendResult(hash: $0) } + .eraseSendError() + .eraseToAnyPublisher() } func getFee(amount: Amount, destination: String) -> AnyPublisher<[Fee], Error> { @@ -226,59 +203,36 @@ extension CosmosWalletManager: StakeKitTransactionSender { transaction: StakeKitTransaction, signer: any TransactionSigner ) -> AnyPublisher { - Just(()) - .receive(on: DispatchQueue.global()) - .setFailureType(to: Error.self) - .tryMap { [weak self] Void -> Data in - guard let self else { - throw WalletError.empty - } - - return try txBuilder.buildForSign(stakingTransaction: transaction) - } - .flatMap { [weak self] hash -> AnyPublisher in - guard let self else { - return .anyFail(error: WalletError.empty) - } - - return signer - .sign(hash: hash, walletPublicKey: self.wallet.publicKey) - .tryMap { [weak self] signature -> Data in - guard let self else { - throw WalletError.empty - } - - let signature = try Secp256k1Signature(with: signature) - return try signature.unmarshal(with: self.wallet.publicKey.blockchainKey, hash: hash).data - } - .eraseToAnyPublisher() - } - .tryMap { [weak self] signature -> Data in - guard let self else { - throw WalletError.empty - } - - return try txBuilder.buildForSend(stakingTransaction: transaction, signature: signature) - } - .flatMap { [weak self] transaction -> AnyPublisher in - guard let self else { - return .anyFail(error: WalletError.empty) + Result { + try txBuilder.buildForSign(stakingTransaction: transaction) + } + .publisher + .withWeakCaptureOf(self) + .flatMap { manager, hash in + signer + .sign(hash: hash, walletPublicKey: self.wallet.publicKey) + .tryMap { signature -> Data in + let signature = try Secp256k1Signature(with: signature) + return try signature.unmarshal(with: manager.wallet.publicKey.blockchainKey, hash: hash).data } - - return self.networkService - .send(transaction: transaction) - .mapSendError(tx: transaction.hexString.lowercased()) - .eraseToAnyPublisher() - } - .handleEvents(receiveOutput: { [weak self] hash in - let mapper = PendingTransactionRecordMapper() - let record = mapper.mapToPendingTransactionRecord(stakeKitTransaction: transaction, hash: hash) - self?.wallet.addPendingTransaction(record) - }) - .map { - TransactionSendResult(hash: $0) - } - .eraseSendError() - .eraseToAnyPublisher() + } + .withWeakCaptureOf(self) + .tryMap { manager, signature -> Data in + try manager.txBuilder.buildForSend(stakingTransaction: transaction, signature: signature) + } + .withWeakCaptureOf(self) + .flatMap { manager, transaction in + manager.networkService + .send(transaction: transaction) + .mapSendError(tx: transaction.hexString.lowercased()) + } + .handleEvents(receiveOutput: { [weak self] hash in + let mapper = PendingTransactionRecordMapper() + let record = mapper.mapToPendingTransactionRecord(stakeKitTransaction: transaction, hash: hash) + self?.wallet.addPendingTransaction(record) + }) + .map { TransactionSendResult(hash: $0) } + .eraseSendError() + .eraseToAnyPublisher() } }