diff --git a/BlockchainSdk.xcodeproj/project.pbxproj b/BlockchainSdk.xcodeproj/project.pbxproj index aebb43fda..8277068a5 100644 --- a/BlockchainSdk.xcodeproj/project.pbxproj +++ b/BlockchainSdk.xcodeproj/project.pbxproj @@ -559,12 +559,9 @@ DAD156592C7DCF6600DE52B3 /* FilecoinNetworkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD156582C7DCF6600DE52B3 /* FilecoinNetworkProvider.swift */; }; DAD1565C2C7DCFBD00DE52B3 /* FilecoinTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD1565B2C7DCFBD00DE52B3 /* FilecoinTarget.swift */; }; DAD1565F2C7DD16B00DE52B3 /* FilecoinResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD1565E2C7DD16B00DE52B3 /* FilecoinResponse.swift */; }; - DAD156632C7DD21B00DE52B3 /* FilecoinTransactionBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD156622C7DD21B00DE52B3 /* FilecoinTransactionBody.swift */; }; - DAD156652C7DD25800DE52B3 /* FilecoinSignedTransactionBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD156642C7DD25800DE52B3 /* FilecoinSignedTransactionBody.swift */; }; + DAD156632C7DD21B00DE52B3 /* FilecoinMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD156622C7DD21B00DE52B3 /* FilecoinMessage.swift */; }; + DAD156652C7DD25800DE52B3 /* FilecoinSignedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD156642C7DD25800DE52B3 /* FilecoinSignedMessage.swift */; }; DAD156692C7DD65400DE52B3 /* FilecoinAccountInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD156682C7DD65400DE52B3 /* FilecoinAccountInfo.swift */; }; - DAD1566B2C7DD67F00DE52B3 /* FilecoinTxGasInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD1566A2C7DD67F00DE52B3 /* FilecoinTxGasInfo.swift */; }; - DAD1566E2C7DD6AC00DE52B3 /* FilecoinTxInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD1566D2C7DD6AC00DE52B3 /* FilecoinTxInfo.swift */; }; - DAD156702C7DE0D000DE52B3 /* FilecoinDTOMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD1566F2C7DE0D000DE52B3 /* FilecoinDTOMapper.swift */; }; DAD555292BFB4110000030E5 /* KoinosTransactionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD555282BFB4110000030E5 /* KoinosTransactionBuilder.swift */; }; DAD555382BFB463C000030E5 /* KoinosAccountNonce.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD555372BFB463C000030E5 /* KoinosAccountNonce.swift */; }; DAD5CDF72C0F3A8900DC4909 /* KoinosWalletManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD5CDF62C0F3A8900DC4909 /* KoinosWalletManagerTests.swift */; }; @@ -586,11 +583,13 @@ DAE657E72BFC732400D7D63A /* token.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE657E32BFC732400D7D63A /* token.pb.swift */; }; DAE657E92BFCA3E400D7D63A /* KoinosTransactionBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE657E82BFCA3E400D7D63A /* KoinosTransactionBuilderTests.swift */; }; DAE864BB2C81CF1700A2D51A /* FilecoinFeeParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE864BA2C81CF1700A2D51A /* FilecoinFeeParameters.swift */; }; + DAE864BD2C81D13F00A2D51A /* FilecoinWalletManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE864BC2C81D13F00A2D51A /* FilecoinWalletManager.swift */; }; DAED18A22C7DF3D900522056 /* FilecoinNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAED18A12C7DF3D900522056 /* FilecoinNetworkService.swift */; }; DAED921F27A150E500F188D7 /* PolkadotAddressService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAED921E27A150E500F188D7 /* PolkadotAddressService.swift */; }; DAF0866E27A942D60024312E /* PolkadotWalletManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF0866D27A942D60024312E /* PolkadotWalletManager.swift */; }; DAF0867027A9438C0024312E /* PolkadotNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF0866F27A9438C0024312E /* PolkadotNetworkService.swift */; }; DAF3AD4629E916D300E057FA /* CosmosFeeParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF3AD4529E916D300E057FA /* CosmosFeeParameters.swift */; }; + DAF81A762C873CA600B83CB0 /* SignatureUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF81A752C873CA600B83CB0 /* SignatureUtils.swift */; }; DAFE0D082BB168D8005CBD9C /* MoonriverExternalLinkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAFE0D072BB168D8005CBD9C /* MoonriverExternalLinkProvider.swift */; }; DAFE0D0A2BB1840E005CBD9C /* MantleExternalLinkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAFE0D092BB1840E005CBD9C /* MantleExternalLinkProvider.swift */; }; DAFE0D0C2BB1841B005CBD9C /* FlareExternalLinkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAFE0D0B2BB1841B005CBD9C /* FlareExternalLinkProvider.swift */; }; @@ -1501,12 +1500,9 @@ DAD156582C7DCF6600DE52B3 /* FilecoinNetworkProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FilecoinNetworkProvider.swift; path = ../FilecoinNetworkProvider.swift; sourceTree = ""; }; DAD1565B2C7DCFBD00DE52B3 /* FilecoinTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinTarget.swift; sourceTree = ""; }; DAD1565E2C7DD16B00DE52B3 /* FilecoinResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinResponse.swift; sourceTree = ""; }; - DAD156622C7DD21B00DE52B3 /* FilecoinTransactionBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinTransactionBody.swift; sourceTree = ""; }; - DAD156642C7DD25800DE52B3 /* FilecoinSignedTransactionBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinSignedTransactionBody.swift; sourceTree = ""; }; - DAD156682C7DD65400DE52B3 /* FilecoinAccountInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinAccountInfo.swift; sourceTree = ""; }; - DAD1566A2C7DD67F00DE52B3 /* FilecoinTxGasInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinTxGasInfo.swift; sourceTree = ""; }; - DAD1566D2C7DD6AC00DE52B3 /* FilecoinTxInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinTxInfo.swift; sourceTree = ""; }; - DAD1566F2C7DE0D000DE52B3 /* FilecoinDTOMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FilecoinDTOMapper.swift; path = DTO/FilecoinDTOMapper.swift; sourceTree = ""; }; + DAD156622C7DD21B00DE52B3 /* FilecoinMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinMessage.swift; sourceTree = ""; }; + DAD156642C7DD25800DE52B3 /* FilecoinSignedMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinSignedMessage.swift; sourceTree = ""; }; + DAD156682C7DD65400DE52B3 /* FilecoinAccountInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FilecoinAccountInfo.swift; path = ../Network/DTO/FilecoinAccountInfo.swift; sourceTree = ""; }; DAD555282BFB4110000030E5 /* KoinosTransactionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KoinosTransactionBuilder.swift; sourceTree = ""; }; DAD555372BFB463C000030E5 /* KoinosAccountNonce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KoinosAccountNonce.swift; sourceTree = ""; }; DAD5CDF62C0F3A8900DC4909 /* KoinosWalletManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KoinosWalletManagerTests.swift; sourceTree = ""; }; @@ -1529,11 +1525,13 @@ DAE657E32BFC732400D7D63A /* token.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = token.pb.swift; sourceTree = ""; }; DAE657E82BFCA3E400D7D63A /* KoinosTransactionBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KoinosTransactionBuilderTests.swift; sourceTree = ""; }; DAE864BA2C81CF1700A2D51A /* FilecoinFeeParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinFeeParameters.swift; sourceTree = ""; }; + DAE864BC2C81D13F00A2D51A /* FilecoinWalletManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinWalletManager.swift; sourceTree = ""; }; DAED18A12C7DF3D900522056 /* FilecoinNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinNetworkService.swift; sourceTree = ""; }; DAED921E27A150E500F188D7 /* PolkadotAddressService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolkadotAddressService.swift; sourceTree = ""; }; DAF0866D27A942D60024312E /* PolkadotWalletManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PolkadotWalletManager.swift; sourceTree = ""; }; DAF0866F27A9438C0024312E /* PolkadotNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolkadotNetworkService.swift; sourceTree = ""; }; DAF3AD4529E916D300E057FA /* CosmosFeeParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CosmosFeeParameters.swift; sourceTree = ""; }; + DAF81A752C873CA600B83CB0 /* SignatureUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignatureUtils.swift; sourceTree = ""; }; DAFE0D072BB168D8005CBD9C /* MoonriverExternalLinkProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoonriverExternalLinkProvider.swift; sourceTree = ""; }; DAFE0D092BB1840E005CBD9C /* MantleExternalLinkProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MantleExternalLinkProvider.swift; sourceTree = ""; }; DAFE0D0B2BB1841B005CBD9C /* FlareExternalLinkProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlareExternalLinkProvider.swift; sourceTree = ""; }; @@ -2912,6 +2910,7 @@ B0A1D39C2625A1150013F0BF /* EthereumUtils.swift */, B64B728B2AEC9C7B005C8C7C /* Lock.swift */, EF74D63F2BA3526D000550F3 /* ObjectDescription.swift */, + DAF81A752C873CA600B83CB0 /* SignatureUtils.swift */, ); path = Utils; sourceTree = ""; @@ -3405,9 +3404,11 @@ DA15D1E82C7782F300FD733B /* Filecoin */ = { isa = PBXGroup; children = ( + DA63E5FE2C836ADF007690B9 /* Model */, DAD1565A2C7DCFAD00DE52B3 /* Network */, DA15D1E92C77830E00FD733B /* FilecoinExternalLinkProvider.swift */, DA20BD692C7BC5E9000F02DF /* FilecoinWalletAssembly.swift */, + DAE864BC2C81D13F00A2D51A /* FilecoinWalletManager.swift */, DA9F15F12C80B3B800EA7FAF /* FilecoinTransactionBuilder.swift */, DAE864BA2C81CF1700A2D51A /* FilecoinFeeParameters.swift */, ); @@ -3581,6 +3582,14 @@ path = Address; sourceTree = ""; }; + DA63E5FE2C836ADF007690B9 /* Model */ = { + isa = PBXGroup; + children = ( + DAD156682C7DD65400DE52B3 /* FilecoinAccountInfo.swift */, + ); + path = Model; + sourceTree = ""; + }; DA9F76E727EC8AD900F0665C /* Tron */ = { isa = PBXGroup; children = ( @@ -3626,10 +3635,9 @@ isa = PBXGroup; children = ( DAD1565D2C7DD15E00DE52B3 /* DTO */, - DAD1565B2C7DCFBD00DE52B3 /* FilecoinTarget.swift */, - DAD1566F2C7DE0D000DE52B3 /* FilecoinDTOMapper.swift */, - DAD156582C7DCF6600DE52B3 /* FilecoinNetworkProvider.swift */, DAED18A12C7DF3D900522056 /* FilecoinNetworkService.swift */, + DAD156582C7DCF6600DE52B3 /* FilecoinNetworkProvider.swift */, + DAD1565B2C7DCFBD00DE52B3 /* FilecoinTarget.swift */, ); path = Network; sourceTree = ""; @@ -3638,11 +3646,8 @@ isa = PBXGroup; children = ( DAD1565E2C7DD16B00DE52B3 /* FilecoinResponse.swift */, - DAD156622C7DD21B00DE52B3 /* FilecoinTransactionBody.swift */, - DAD156642C7DD25800DE52B3 /* FilecoinSignedTransactionBody.swift */, - DAD156682C7DD65400DE52B3 /* FilecoinAccountInfo.swift */, - DAD1566A2C7DD67F00DE52B3 /* FilecoinTxGasInfo.swift */, - DAD1566D2C7DD6AC00DE52B3 /* FilecoinTxInfo.swift */, + DAD156622C7DD21B00DE52B3 /* FilecoinMessage.swift */, + DAD156642C7DD25800DE52B3 /* FilecoinSignedMessage.swift */, ); path = DTO; sourceTree = ""; @@ -4832,6 +4837,7 @@ DC5E65072B1650F400E81AA5 /* OP_2DIV.swift in Sources */, 2DA4A4422BB5431700E55526 /* RadiantTransactionBuilder.swift in Sources */, 0A3BF8EA2C69F5D900163492 /* CosmosProtoMessage.swift in Sources */, + DAE864BD2C81D13F00A2D51A /* FilecoinWalletManager.swift in Sources */, EF3B19342AA85CE90084AA1C /* DogecoinExternalLinkProvider.swift in Sources */, EF2D9DA52BC3F6770055C485 /* EthereumTransactionParams.swift in Sources */, EF0DA78C285246A90081092A /* DashMainNetworkParams.swift in Sources */, @@ -4872,6 +4878,7 @@ 0AEFB5392B7656EC007519F9 /* NodeRequest.swift in Sources */, EF3B19642AA85F280084AA1C /* ChiaExternalLinkProvider.swift in Sources */, EF32FEBF2A306E51002ED43F /* AddressService.swift in Sources */, + DAF81A762C873CA600B83CB0 /* SignatureUtils.swift in Sources */, EF34FCF72A41CA8700E18670 /* CardanoTransactionBuilder.swift in Sources */, 5D7D243625136254001B9A4F /* XRPAccount.swift in Sources */, B6BA93712AEA0E8000F84E36 /* NEARNetworkParams.ViewAccessKey.swift in Sources */, @@ -4977,7 +4984,7 @@ EF3B194A2AA85E7B0084AA1C /* DashExternalLinkProvider.swift in Sources */, 2DDE5BA229C4F8D200A5B708 /* BinanceWalletAssembly.swift in Sources */, B00DF9912BBEA9DA004397CB /* APIModels.swift in Sources */, - DAD156632C7DD21B00DE52B3 /* FilecoinTransactionBody.swift in Sources */, + DAD156632C7DD21B00DE52B3 /* FilecoinMessage.swift in Sources */, DC5E64F62B1650F400E81AA5 /* OP_LESSTHAN.swift in Sources */, 5DEAFA90244473460032E316 /* Transaction.swift in Sources */, 5D977BB623FAEB4500575BE4 /* DucatusNetworkService.swift in Sources */, @@ -5169,7 +5176,6 @@ EF2D9DA22BC3F6770055C485 /* EthereumAdditionalInfoProvider.swift in Sources */, EF2D9DA12BC3F6770055C485 /* EthereumTransactionDataBuilder.swift in Sources */, B6F89EAD2BB20DB50009A453 /* SubscanAPIResult.Error.swift in Sources */, - DAD156702C7DE0D000DE52B3 /* FilecoinDTOMapper.swift in Sources */, 2DCAEE4F2B147E2800C87E09 /* DecimalPlainAddress.swift in Sources */, DC5E652C2B1650F400E81AA5 /* OP_RETURN.swift in Sources */, EF3B19382AA85D090084AA1C /* PolygonExternalLinkProvider.swift in Sources */, @@ -5256,7 +5262,7 @@ 0A158C052B74E4680004DC23 /* BitcoinCashNowNodesNetworkProvider.swift in Sources */, DC3550F12B57013F00A93DBA /* XDCExternalLinkProvider.swift in Sources */, EFAD40A52A965BA800364D65 /* BlockBookNode.swift in Sources */, - DAD156652C7DD25800DE52B3 /* FilecoinSignedTransactionBody.swift in Sources */, + DAD156652C7DD25800DE52B3 /* FilecoinSignedMessage.swift in Sources */, DA5339A52BFCBE8600BA3D80 /* KoinosProtocol.swift in Sources */, DC5E65012B1650F400E81AA5 /* OP_NEGATE.swift in Sources */, EF3B191E2AA85C150084AA1C /* CardanoExternalLinkProvider.swift in Sources */, @@ -5355,9 +5361,7 @@ DAE657E72BFC732400D7D63A /* token.pb.swift in Sources */, B6417DDC2BA2354D00B9B61D /* PolygonTransactionHistoryProvider.swift in Sources */, DAE657E52BFC732400D7D63A /* protocol.pb.swift in Sources */, - DAD1566B2C7DD67F00DE52B3 /* FilecoinTxGasInfo.swift in Sources */, B6BA93792AEA0F0C00F84E36 /* NEARNetworkResult.GasPrice.swift in Sources */, - DAD1566E2C7DD6AC00DE52B3 /* FilecoinTxInfo.swift in Sources */, B6B2EB6D2B56AAB9005FBE8E /* VeChainNetworkResult.ContractCall.swift in Sources */, EF57BEC82A1E13BE00C2A493 /* DerivationConfig.swift in Sources */, DA0E98D62C04755C00C92985 /* KoinosMethod.swift in Sources */, diff --git a/BlockchainSdk/Blockchains/Filecoin/FilecoinExternalLinkProvider.swift b/BlockchainSdk/Blockchains/Filecoin/FilecoinExternalLinkProvider.swift index 3f6b0987d..500703105 100644 --- a/BlockchainSdk/Blockchains/Filecoin/FilecoinExternalLinkProvider.swift +++ b/BlockchainSdk/Blockchains/Filecoin/FilecoinExternalLinkProvider.swift @@ -16,11 +16,7 @@ struct FilecoinExternalLinkProvider: ExternalLinkProvider { } func url(transaction hash: String) -> URL? { - /// This method returns `nil` because Filecoin does not use transaction hashes as message identifiers. - /// In other blockchains, a transaction hash can be directly used to generate a URL to explore the transaction details. - /// However, in Filecoin, message IDs (which are used to identify transactions) are not derived from transaction hashes. - /// Therefore, constructing a URL in the format `"\(baseExplorerUrl)/message/\(hash)"` is not applicable. - nil + URL(string: "\(baseExplorerUrl)/message/\(hash)") } func url(address: String, contractAddress: String?) -> URL? { diff --git a/BlockchainSdk/Blockchains/Filecoin/FilecoinFeeParameters.swift b/BlockchainSdk/Blockchains/Filecoin/FilecoinFeeParameters.swift index 876b7de3c..cdb162315 100644 --- a/BlockchainSdk/Blockchains/Filecoin/FilecoinFeeParameters.swift +++ b/BlockchainSdk/Blockchains/Filecoin/FilecoinFeeParameters.swift @@ -9,7 +9,7 @@ import BigInt struct FilecoinFeeParameters: FeeParameters { - let gasUnitPrice: BigUInt let gasLimit: Int64 + let gasFeeCap: BigUInt let gasPremium: BigUInt } diff --git a/BlockchainSdk/Blockchains/Filecoin/FilecoinNetworkProvider.swift b/BlockchainSdk/Blockchains/Filecoin/FilecoinNetworkProvider.swift index 84136696d..415e23a49 100644 --- a/BlockchainSdk/Blockchains/Filecoin/FilecoinNetworkProvider.swift +++ b/BlockchainSdk/Blockchains/Filecoin/FilecoinNetworkProvider.swift @@ -29,22 +29,18 @@ final class FilecoinNetworkProvider: HostProvider { requestPublisher(for: .getActorInfo(address: address)) } - func getGasUnitPrice(transactionInfo: FilecoinTxInfo) -> AnyPublisher { - requestPublisher(for: .getGasUnitPrice(transactionInfo: transactionInfo)) + func getEstimateMessageGas(message: FilecoinMessage) -> AnyPublisher { + requestPublisher(for: .getEstimateMessageGas(message: message)) } - func getGasLimit(transactionInfo: FilecoinTxInfo) -> AnyPublisher { - requestPublisher(for: .getGasLimit(transactionInfo: transactionInfo)) - } - - func submitTransaction(signedTransactionBody: FilecoinSignedTransactionBody) -> AnyPublisher { - requestPublisher(for: .submitTransaction(signedTransactionBody: signedTransactionBody)) + func submitTransaction(signedMessage: FilecoinSignedMessage) -> AnyPublisher { + requestPublisher(for: .submitTransaction(signedMessage: signedMessage)) } private func requestPublisher(for target: FilecoinTarget.FilecoinTargetType) -> AnyPublisher { provider.requestPublisher(FilecoinTarget(node: node, target)) .filterSuccessfulStatusAndRedirectCodes() - .map(JSONRPC.Response.self, using: .withSnakeCaseStrategy) + .map(JSONRPC.Response.self) .tryMap { try $0.result.get() } .eraseToAnyPublisher() } diff --git a/BlockchainSdk/Blockchains/Filecoin/FilecoinTransactionBuilder.swift b/BlockchainSdk/Blockchains/Filecoin/FilecoinTransactionBuilder.swift index 534b9e274..5e25aaccd 100644 --- a/BlockchainSdk/Blockchains/Filecoin/FilecoinTransactionBuilder.swift +++ b/BlockchainSdk/Blockchains/Filecoin/FilecoinTransactionBuilder.swift @@ -6,6 +6,7 @@ // Copyright © 2024 Tangem AG. All rights reserved. // +import BigInt import TangemSdk import WalletCore @@ -16,10 +17,10 @@ enum FilecoinTransactionBuilderError: Error { } final class FilecoinTransactionBuilder { - private let wallet: Wallet + private let decompressedPublicKey: Data - init(wallet: Wallet) { - self.wallet = wallet + init(publicKey: Wallet.PublicKey) throws { + self.decompressedPublicKey = try Secp256k1Key(with: publicKey.blockchainKey).decompress() } func buildForSign(transaction: Transaction, nonce: UInt64) throws -> Data { @@ -40,16 +41,22 @@ final class FilecoinTransactionBuilder { transaction: Transaction, nonce: UInt64, signatureInfo: SignatureInfo - ) throws -> FilecoinSignedTransactionBody { + ) throws -> FilecoinSignedMessage { guard let feeParameters = transaction.fee.parameters as? FilecoinFeeParameters else { throw FilecoinTransactionBuilderError.filecoinFeeParametersNotFound } + let unmarshalledSignature = try SignatureUtils.unmarshalledSignature( + from: signatureInfo.signature, + publicKey: decompressedPublicKey, + hash: signatureInfo.hash + ) + let signatures = DataVector() - signatures.add(data: signatureInfo.signature) + signatures.add(data: unmarshalledSignature) let publicKeys = DataVector() - publicKeys.add(data: try Secp256k1Key(with: signatureInfo.publicKey).decompress()) + publicKeys.add(data: decompressedPublicKey) let input = try makeSigningInput(transaction: transaction, nonce: nonce, feeParameters: feeParameters) let txInputData = try input.serializedData() @@ -67,7 +74,7 @@ final class FilecoinTransactionBuilder { throw FilecoinTransactionBuilderError.failedToGetDataFromJSON } - return try JSONDecoder().decode(FilecoinSignedTransactionBody.self, from: jsonData) + return try JSONDecoder().decode(FilecoinSignedMessage.self, from: jsonData) } private func makeSigningInput( @@ -79,17 +86,17 @@ final class FilecoinTransactionBuilder { throw FilecoinTransactionBuilderError.failedToConvertAmountToBigUInt } - return try FilecoinSigningInput.with { input in + return FilecoinSigningInput.with { input in input.to = transaction.destinationAddress input.nonce = nonce input.value = value.serialize() - input.gasFeeCap = feeParameters.gasUnitPrice.serialize() input.gasLimit = feeParameters.gasLimit + input.gasFeeCap = feeParameters.gasFeeCap.serialize() input.gasPremium = feeParameters.gasPremium.serialize() - input.publicKey = try Secp256k1Key(with: wallet.publicKey.blockchainKey).decompress() + input.publicKey = decompressedPublicKey } } } diff --git a/BlockchainSdk/Blockchains/Filecoin/FilecoinWalletAssembly.swift b/BlockchainSdk/Blockchains/Filecoin/FilecoinWalletAssembly.swift index e5a4f4a0e..33d2ed2e8 100644 --- a/BlockchainSdk/Blockchains/Filecoin/FilecoinWalletAssembly.swift +++ b/BlockchainSdk/Blockchains/Filecoin/FilecoinWalletAssembly.swift @@ -10,7 +10,18 @@ import Foundation struct FilecoinWalletAssembly: WalletManagerAssembly { func make(with input: WalletManagerAssemblyInput) throws -> WalletManager { - // TODO: [FILECOIN] https://tangem.atlassian.net/browse/IOS-7738 - fatalError("Not implemented") + FilecoinWalletManager( + wallet: input.wallet, + networkService: FilecoinNetworkService( + providers: APIResolver(blockchain: input.blockchain, config: input.blockchainSdkConfig) + .resolveProviders(apiInfos: input.apiInfo) { nodeInfo, _ in + FilecoinNetworkProvider( + node: nodeInfo, + configuration: input.networkConfig + ) + } + ), + transactionBuilder: try FilecoinTransactionBuilder(publicKey: input.wallet.publicKey) + ) } } diff --git a/BlockchainSdk/Blockchains/Filecoin/FilecoinWalletManager.swift b/BlockchainSdk/Blockchains/Filecoin/FilecoinWalletManager.swift new file mode 100644 index 000000000..beb8744e9 --- /dev/null +++ b/BlockchainSdk/Blockchains/Filecoin/FilecoinWalletManager.swift @@ -0,0 +1,161 @@ +// +// FilecoinWalletManager.swift +// BlockchainSdk +// +// Created by Aleksei Muraveinik on 30.08.24. +// Copyright © 2024 Tangem AG. All rights reserved. +// + +import BigInt +import Combine + +class FilecoinWalletManager: BaseManager, WalletManager { + var currentHost: String { + networkService.host + } + + var allowsFeeSelection: Bool { + false + } + + private let networkService: FilecoinNetworkService + private let transactionBuilder: FilecoinTransactionBuilder + + private var nonce: UInt64 = 0 + + init( + wallet: Wallet, + networkService: FilecoinNetworkService, + transactionBuilder: FilecoinTransactionBuilder + ) { + self.networkService = networkService + self.transactionBuilder = transactionBuilder + super.init(wallet: wallet) + } + + override func update(completion: @escaping (Result) -> Void) { + cancellable = networkService + .getAccountInfo(address: wallet.address) + .withWeakCaptureOf(self) + .sink( + receiveCompletion: { [weak self] result in + switch result { + case .failure(let error): + self?.wallet.clearAmounts() + completion(.failure(error)) + case .finished: + completion(.success(())) + } + }, + receiveValue: { walletManager, accountInfo in + if accountInfo.nonce != walletManager.nonce { + walletManager.wallet.clearPendingTransaction() + } + + walletManager.wallet.add( + amount: Amount( + with: .filecoin, + type: .coin, + value: accountInfo.balance / walletManager.wallet.blockchain.decimalValue + ) + ) + + walletManager.nonce = accountInfo.nonce + } + ) + } + + func getFee(amount: Amount, destination: String) -> AnyPublisher<[Fee], any Error> { + guard let bigUIntValue = amount.bigUIntValue else { + return .anyFail(error: WalletError.failedToGetFee) + } + + return networkService + .getAccountInfo(address: wallet.address) + .map { [address = wallet.address] accountInfo in + FilecoinMessage( + from: address, + to: destination, + value: String(bigUIntValue, radix: 10), + nonce: accountInfo.nonce, + gasLimit: nil, + gasFeeCap: nil, + gasPremium: nil + ) + } + .withWeakCaptureOf(networkService) + .flatMap { networkService, message in + networkService.getEstimateMessageGas(message: message) + } + .withWeakCaptureOf(self) + .tryMap { (walletManager: FilecoinWalletManager, gasInfo) -> [Fee] in + guard let gasFeeCapDecimal = Decimal(stringValue: gasInfo.gasFeeCap) else { + throw WalletError.failedToGetFee + } + + let gasLimitDecimal = Decimal(gasInfo.gasLimit) + + return [ + Fee( + Amount( + with: .filecoin, + type: .coin, + value: gasLimitDecimal * gasFeeCapDecimal / walletManager.wallet.blockchain.decimalValue + ), + parameters: FilecoinFeeParameters( + gasLimit: gasInfo.gasLimit, + gasFeeCap: BigUInt(stringLiteral: gasInfo.gasFeeCap), + gasPremium: BigUInt(stringLiteral: gasInfo.gasPremium) + ) + ) + ] + } + .eraseToAnyPublisher() + } + + func send(_ transaction: Transaction, signer: any TransactionSigner) -> AnyPublisher { + networkService + .getAccountInfo(address: wallet.address) + .withWeakCaptureOf(transactionBuilder) + .tryMap { transactionBuilder, accountInfo in + let hashToSign = try transactionBuilder.buildForSign( + transaction: transaction, + nonce: accountInfo.nonce + ) + return (hashToSign, accountInfo.nonce) + } + .withWeakCaptureOf(self) + .flatMap { walletManager, args in + let (hashToSign, nonce) = args + return signer + .sign(hash: hashToSign, walletPublicKey: walletManager.wallet.publicKey) + .withWeakCaptureOf(walletManager) + .tryMap { walletManager, signature in + try walletManager.transactionBuilder.buildForSend( + transaction: transaction, + nonce: nonce, + signatureInfo: SignatureInfo( + signature: signature, + publicKey: walletManager.wallet.publicKey.blockchainKey, + hash: hashToSign + ) + ) + } + } + .withWeakCaptureOf(self) + .flatMap { walletManager, message in + walletManager.networkService + .submitTransaction(signedMessage: message) + .mapSendError(tx: try? JSONEncoder().encode(message).utf8String) + } + .withWeakCaptureOf(self) + .map { walletManager, txId in + let mapper = PendingTransactionRecordMapper() + let record = mapper.mapToPendingTransactionRecord(transaction: transaction, hash: txId) + walletManager.wallet.addPendingTransaction(record) + return TransactionSendResult(hash: txId) + } + .eraseSendError() + .eraseToAnyPublisher() + } +} diff --git a/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinDTOMapper.swift b/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinDTOMapper.swift deleted file mode 100644 index a10f60eaf..000000000 --- a/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinDTOMapper.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// FilecoinTransactionBodyConverter.swift -// BlockchainSdk -// -// Created by Aleksei Muraveinik on 27.08.24. -// Copyright © 2024 Tangem AG. All rights reserved. -// - -import Foundation - -enum FilecoinDTOMapper { - static func convertTransactionBody(from transactionInfo: FilecoinTxInfo) -> FilecoinTransactionBody { - FilecoinTransactionBody( - sourceAddress: transactionInfo.sourceAddress, - destinationAddress: transactionInfo.destinationAddress, - amount: "\(transactionInfo.amount)", - nonce: transactionInfo.nonce, - gasUnitPrice: nil, - gasLimit: nil, - gasPremium: nil - ) - } -} diff --git a/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinTransactionBody.swift b/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinMessage.swift similarity index 52% rename from BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinTransactionBody.swift rename to BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinMessage.swift index f0127535e..ae587f7ba 100644 --- a/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinTransactionBody.swift +++ b/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinMessage.swift @@ -1,5 +1,5 @@ // -// FilecoinTransactionBody.swift +// FilecoinMessage.swift // BlockchainSdk // // Created by Aleksei Muraveinik on 27.08.24. @@ -8,22 +8,22 @@ import Foundation -struct FilecoinTransactionBody: Codable, Equatable { - let sourceAddress: String - let destinationAddress: String - let amount: String +struct FilecoinMessage: Codable, Equatable { + let from: String + let to: String + let value: String let nonce: UInt64 - let gasUnitPrice: String? let gasLimit: UInt64? + let gasFeeCap: String? let gasPremium: String? enum CodingKeys: String, CodingKey { - case sourceAddress = "From" - case destinationAddress = "To" - case amount = "Value" + case from = "From" + case to = "To" + case value = "Value" case nonce = "Nonce" - case gasUnitPrice = "GasFeeCap" case gasLimit = "GasLimit" + case gasFeeCap = "GasFeeCap" case gasPremium = "GasPremium" } } diff --git a/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinResponse.swift b/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinResponse.swift index 944d90894..bafca1a4b 100644 --- a/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinResponse.swift +++ b/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinResponse.swift @@ -1,5 +1,5 @@ // -// FilecoinRpcResponseResult.swift +// FilecoinResponse.swift // BlockchainSdk // // Created by Aleksei Muraveinik on 27.08.24. @@ -19,6 +19,18 @@ enum FilecoinResponse { } } + struct GetEstimateMessageGas: Decodable { + let gasLimit: Int64 + let gasFeeCap: String + let gasPremium: String + + enum CodingKeys: String, CodingKey { + case gasLimit = "GasLimit" + case gasFeeCap = "GasFeeCap" + case gasPremium = "GasPremium" + } + } + struct SubmitTransaction: Decodable { let hash: String diff --git a/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinSignedTransactionBody.swift b/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinSignedMessage.swift similarity index 63% rename from BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinSignedTransactionBody.swift rename to BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinSignedMessage.swift index e58d36ddb..dc65a4077 100644 --- a/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinSignedTransactionBody.swift +++ b/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinSignedMessage.swift @@ -1,5 +1,5 @@ // -// FilecoinSignedTransactionBody.swift +// FilecoinSignedMessage.swift // BlockchainSdk // // Created by Aleksei Muraveinik on 27.08.24. @@ -8,22 +8,22 @@ import Foundation -struct FilecoinSignedTransactionBody: Codable, Equatable { +struct FilecoinSignedMessage: Codable, Equatable { struct Signature: Codable, Equatable { let type: Int - let signature: String + let data: String enum CodingKeys: String, CodingKey { case type = "Type" - case signature = "Data" + case data = "Data" } } - let transactionBody: FilecoinTransactionBody + let message: FilecoinMessage let signature: Signature enum CodingKeys: String, CodingKey { - case transactionBody = "Message" + case message = "Message" case signature = "Signature" } } diff --git a/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinTxGasInfo.swift b/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinTxGasInfo.swift deleted file mode 100644 index b32ef09d5..000000000 --- a/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinTxGasInfo.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// FilecoinTxGasInfo.swift -// BlockchainSdk -// -// Created by Aleksei Muraveinik on 27.08.24. -// Copyright © 2024 Tangem AG. All rights reserved. -// - -import Foundation - -struct FilecoinTxGasInfo { - let gasUnitPrice: UInt64 - let gasLimit: UInt64 - let gasPremium: UInt64 -} diff --git a/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinTxInfo.swift b/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinTxInfo.swift deleted file mode 100644 index 7b19fcf45..000000000 --- a/BlockchainSdk/Blockchains/Filecoin/Network/DTO/FilecoinTxInfo.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// FilecoinTxInfo.swift -// BlockchainSdk -// -// Created by Aleksei Muraveinik on 27.08.24. -// Copyright © 2024 Tangem AG. All rights reserved. -// - -import Foundation - -struct FilecoinTxInfo { - let sourceAddress: String - let destinationAddress: String - let amount: UInt64 - let nonce: UInt64 -} diff --git a/BlockchainSdk/Blockchains/Filecoin/Network/FilecoinNetworkService.swift b/BlockchainSdk/Blockchains/Filecoin/Network/FilecoinNetworkService.swift index cdcab3155..72aedf1ce 100644 --- a/BlockchainSdk/Blockchains/Filecoin/Network/FilecoinNetworkService.swift +++ b/BlockchainSdk/Blockchains/Filecoin/Network/FilecoinNetworkService.swift @@ -16,7 +16,9 @@ class FilecoinNetworkService: MultiNetworkProvider { self.providers = providers } - func getAccountInfo(address: String) -> AnyPublisher { + func getAccountInfo( + address: String + ) -> AnyPublisher { providerPublisher { provider in provider .getActorInfo(address: address) @@ -34,31 +36,20 @@ class FilecoinNetworkService: MultiNetworkProvider { } } - func getGasUnitPrice(transactionInfo: FilecoinTxInfo) -> AnyPublisher { + func getEstimateMessageGas( + message: FilecoinMessage + ) -> AnyPublisher { providerPublisher { provider in - provider - .getGasUnitPrice(transactionInfo: transactionInfo) - .tryMap { response in - guard let price = UInt64(response) else { - throw WalletError.failedToParseNetworkResponse() - } - return price - } - .eraseToAnyPublisher() - } - } - - func getGasLimit(transactionInfo: FilecoinTxInfo) -> AnyPublisher { - providerPublisher { provider in - provider - .getGasLimit(transactionInfo: transactionInfo) + provider.getEstimateMessageGas(message: message) } } - func submitTransaction(signedTransactionBody: FilecoinSignedTransactionBody) -> AnyPublisher { + func submitTransaction( + signedMessage: FilecoinSignedMessage + ) -> AnyPublisher { providerPublisher { provider in provider - .submitTransaction(signedTransactionBody: signedTransactionBody) + .submitTransaction(signedMessage: signedMessage) .map(\.hash) .eraseToAnyPublisher() } diff --git a/BlockchainSdk/Blockchains/Filecoin/Network/FilecoinTarget.swift b/BlockchainSdk/Blockchains/Filecoin/Network/FilecoinTarget.swift index 3a8814443..adab54fc0 100644 --- a/BlockchainSdk/Blockchains/Filecoin/Network/FilecoinTarget.swift +++ b/BlockchainSdk/Blockchains/Filecoin/Network/FilecoinTarget.swift @@ -12,18 +12,15 @@ import Moya struct FilecoinTarget: TargetType { enum FilecoinTargetType { case getActorInfo(address: String) - case getGasUnitPrice(transactionInfo: FilecoinTxInfo) - case getGasLimit(transactionInfo: FilecoinTxInfo) - case submitTransaction(signedTransactionBody: FilecoinSignedTransactionBody) + case getEstimateMessageGas(message: FilecoinMessage) + case submitTransaction(signedMessage: FilecoinSignedMessage) var method: String { switch self { case .getActorInfo: "Filecoin.StateGetActor" - case .getGasUnitPrice: - "Filecoin.GasEstimateFeeCap" - case .getGasLimit: - "Filecoin.GasEstimateGasLimit" + case .getEstimateMessageGas: + "Filecoin.GasEstimateMessageGas" case .submitTransaction: "Filecoin.MpoolPush" } @@ -62,33 +59,23 @@ struct FilecoinTarget: TargetType { ] ) - case .getGasUnitPrice(let transactionInfo): + case .getEstimateMessageGas(let message): .requestJSONRPC( id: Constants.jsonRPCMethodId, method: type.method, params: [ - FilecoinDTOMapper.convertTransactionBody(from: transactionInfo), + message, nil, nil ] ) - case .getGasLimit(let transactionInfo): + case .submitTransaction(let signedMessage): .requestJSONRPC( id: Constants.jsonRPCMethodId, method: type.method, params: [ - FilecoinDTOMapper.convertTransactionBody(from: transactionInfo), - nil - ] - ) - - case .submitTransaction(let signedTransactionBody): - .requestJSONRPC( - id: Constants.jsonRPCMethodId, - method: type.method, - params: [ - signedTransactionBody + signedMessage ] ) } diff --git a/BlockchainSdk/Blockchains/VeChain/VeChainTransactionBuilder.swift b/BlockchainSdk/Blockchains/VeChain/VeChainTransactionBuilder.swift index 54e644e75..377c2b366 100644 --- a/BlockchainSdk/Blockchains/VeChain/VeChainTransactionBuilder.swift +++ b/BlockchainSdk/Blockchains/VeChain/VeChainTransactionBuilder.swift @@ -60,7 +60,11 @@ final class VeChainTransactionBuilder { } let publicKey = try Secp256k1Key(with: transactionParams.publicKey.blockchainKey).decompress() - let unmarshalledSignature = try unmarshalledSignature(from: signature, publicKey: publicKey, hash: hash) + let unmarshalledSignature = try SignatureUtils.unmarshalledSignature( + from: signature, + publicKey: publicKey, + hash: hash + ) let compiledTransaction = TransactionCompiler.compileWithSignatures( coinType: coinType, @@ -154,25 +158,6 @@ final class VeChainTransactionBuilder { return bigUIntValue } - - /// VeChain is a fork of `Geth Classic`, so it expects the secp256k1's `recid` to have values in the 0...3 range. - /// Therefore we have to convert value of the standard secp256k1's `recid` to match this expectation. - private func unmarshalledSignature(from originalSignature: Data, publicKey: Data, hash: Data) throws -> Data { - let signature = try Secp256k1Signature(with: originalSignature) - let unmarshalledSignature = try signature.unmarshal(with: publicKey, hash: hash) - - guard unmarshalledSignature.v.count == Constants.recoveryIdLength else { - throw WalletError.failedToBuildTx - } - - let recoveryId = unmarshalledSignature.v[0] - Constants.recoveryIdDiff - - guard recoveryId >= Constants.recoveryIdLowerBound, recoveryId <= Constants.recoveryIdUpperBound else { - throw WalletError.failedToBuildTx - } - - return unmarshalledSignature.r + unmarshalledSignature.s + Data(recoveryId) - } } // MARK: - Convenience extensions @@ -189,9 +174,5 @@ private extension VeChainTransactionBuilder { enum Constants { /// `18` is the value used by the official `VeWorld` wallet app, multiplying it by 10 just in case. static let transactionExpiration = 18 * 10 - static let recoveryIdLength = 1 - static let recoveryIdDiff: UInt8 = 27 - static let recoveryIdLowerBound: UInt8 = 0 - static let recoveryIdUpperBound: UInt8 = 3 } } diff --git a/BlockchainSdk/Common/API/APIResolvers/GetBlock/GetBlockAPIResolver.swift b/BlockchainSdk/Common/API/APIResolvers/GetBlock/GetBlockAPIResolver.swift index 77a9cb10b..9efb25ec4 100644 --- a/BlockchainSdk/Common/API/APIResolvers/GetBlock/GetBlockAPIResolver.swift +++ b/BlockchainSdk/Common/API/APIResolvers/GetBlock/GetBlockAPIResolver.swift @@ -32,7 +32,7 @@ struct GetBlockAPIResolver { switch blockchain { case .cosmos, .tron, .algorand, .aptos: return credentials.credential(for: blockchain, type: .rest) - case .near, .ton, .ethereum, .ethereumClassic, .rsk, .bsc, .polygon, .fantom, .gnosis, .cronos, .zkSync, .moonbeam, .polygonZkEVM, .avalanche, .base, .xrp, .blast: + case .near, .ton, .ethereum, .ethereumClassic, .rsk, .bsc, .polygon, .fantom, .gnosis, .cronos, .zkSync, .moonbeam, .polygonZkEVM, .avalanche, .base, .xrp, .blast, .filecoin: return credentials.credential(for: blockchain, type: .jsonRpc) case .cardano: return credentials.credential(for: blockchain, type: .rosetta) diff --git a/BlockchainSdk/Common/API/APIResolvers/NowNodes/NowNodesAPIResolver.swift b/BlockchainSdk/Common/API/APIResolvers/NowNodes/NowNodesAPIResolver.swift index a861efd5f..095a603d0 100644 --- a/BlockchainSdk/Common/API/APIResolvers/NowNodes/NowNodesAPIResolver.swift +++ b/BlockchainSdk/Common/API/APIResolvers/NowNodes/NowNodesAPIResolver.swift @@ -68,6 +68,8 @@ struct NowNodesAPIResolver { link = "https://base.nownodes.io/\(apiKey)" case .blast: link = "https://blast.nownodes.io/\(apiKey)" + case .filecoin: + link = "https://fil.nownodes.io/\(apiKey)" default: return nil } diff --git a/BlockchainSdk/Common/Utils/SignatureUtils.swift b/BlockchainSdk/Common/Utils/SignatureUtils.swift new file mode 100644 index 000000000..8c98ee75f --- /dev/null +++ b/BlockchainSdk/Common/Utils/SignatureUtils.swift @@ -0,0 +1,56 @@ +// +// SignatureUtils.swift +// BlockchainSdk +// +// Created by Aleksei Muraveinik on 03.09.24. +// Copyright © 2024 Tangem AG. All rights reserved. +// + +import TangemSdk + +enum SignatureUtils { + /// Unmarshals a signature using the provided original signature, public key, and hash. + /// This function is essential for certain blockchains that require signature unmarshalling to correctly + /// reconstruct the signature for verification. + /// + /// - Parameters: + /// - originalSignature: The original `Data` object representing the signature to be unmarshalled. + /// - publicKey: The `Data` object representing the public key associated with the signature. + /// - hash: The `Data` object representing the hash of the message or transaction. + /// - Returns: A `Data` object containing the unmarshalled signature, which includes the `r` and `s` values + /// concatenated with the adjusted recovery ID. + /// - Throws: `WalletError.failedToBuildTx` if the unmarshalling process fails due to an invalid recovery ID. + /// + /// - Important: For certain blockchains, especially those using the Secp256k1 curve, signature unmarshalling + /// is required to correctly reconstruct the signature from the original data. This process involves verifying + /// and adjusting the recovery ID, which is essential for ensuring the signature is valid and can be used for + /// transaction verification. The function checks the length of the recovery ID and adjusts it within valid bounds + /// before concatenating it with the `r` and `s` components of the signature. + + static func unmarshalledSignature(from originalSignature: Data, publicKey: Data, hash: Data) throws -> Data { + let signature = try Secp256k1Signature(with: originalSignature) + let unmarshalledSignature = try signature.unmarshal(with: publicKey, hash: hash) + + guard unmarshalledSignature.v.count == Constants.recoveryIdLength else { + throw WalletError.failedToBuildTx + } + + let recoveryId = unmarshalledSignature.v[0] - Constants.recoveryIdDiff + + guard recoveryId >= Constants.recoveryIdLowerBound, recoveryId <= Constants.recoveryIdUpperBound else { + throw WalletError.failedToBuildTx + } + + return unmarshalledSignature.r + unmarshalledSignature.s + Data(recoveryId) + } +} + +private extension SignatureUtils { + enum Constants { + static let recoveryIdLength = 1 + static let recoveryIdDiff: UInt8 = 27 + static let recoveryIdLowerBound: UInt8 = 0 + static let recoveryIdUpperBound: UInt8 = 3 + } + +} diff --git a/BlockchainSdkExample/config b/BlockchainSdkExample/config index b5726a45b..57484a013 160000 --- a/BlockchainSdkExample/config +++ b/BlockchainSdkExample/config @@ -1 +1 @@ -Subproject commit b5726a45bf51b7763c5967afae4d3d687676240b +Subproject commit 57484a0139299d1229872c626b2c9cb34442038c diff --git a/BlockchainSdkTests/Filecoin/FilecoinTransactionBuilderTests.swift b/BlockchainSdkTests/Filecoin/FilecoinTransactionBuilderTests.swift index 9ca0e5564..3d2fbcbdb 100644 --- a/BlockchainSdkTests/Filecoin/FilecoinTransactionBuilderTests.swift +++ b/BlockchainSdkTests/Filecoin/FilecoinTransactionBuilderTests.swift @@ -10,39 +10,31 @@ import XCTest @testable import BlockchainSdk final class FilecoinTransactionBuilderTests: XCTestCase { - private let transactionBuilder = FilecoinTransactionBuilder( - wallet: Wallet( - blockchain: .filecoin, - addresses: [ - .default: PlainAddress( - value: Constants.sourceAddress, - publicKey: Wallet.PublicKey( - seedKey: Constants.publicKey, - derivationType: nil - ), - type: .default - ) - ] + private let transactionBuilder: FilecoinTransactionBuilder = try! FilecoinTransactionBuilder( + publicKey: Wallet.PublicKey( + seedKey: Constants.publicKey, + derivationType: nil ) ) + private let sizeTester = TransactionSizeTesterUtility() private var transaction: Transaction { Transaction( amount: Amount( with: .filecoin, type: .coin, - value: 0.01 + value: 1 ), fee: Fee( Amount( with: .filecoin, type: .coin, - value: (101225 * 1526328) / Blockchain.filecoin.decimalValue + value: (100704 * 1527953) / Blockchain.filecoin.decimalValue ), parameters: FilecoinFeeParameters( - gasUnitPrice: 101225, - gasLimit: 1526328, - gasPremium: 50612 + gasLimit: 1527953, + gasFeeCap: 100704, + gasPremium: 99503 ) ), sourceAddress: Constants.sourceAddress, @@ -52,27 +44,28 @@ final class FilecoinTransactionBuilderTests: XCTestCase { } func testBuildForSign() throws { - let expected = Data(hex: "BEB93CCF5C85273B327AC5DCDD58CBF3066F57FC84B87CD20DC67DF69EC2D0A9") - let actual = try transactionBuilder.buildForSign(transaction: transaction, nonce: 2) - + let expected = Data(hex: "0beac3427b81d6fa6e93a05a0b64fcc3c7ce4af9d05af31ee343bcc527ae8b18") + let actual = try transactionBuilder.buildForSign(transaction: transaction, nonce: 1) + + sizeTester.testTxSize(actual) XCTAssertEqual(expected, actual) } func testBuildForSend() throws { - let nonce: UInt64 = 2 - let expected = FilecoinSignedTransactionBody( - transactionBody: FilecoinTransactionBody( - sourceAddress: Constants.sourceAddress, - destinationAddress: Constants.destinationAddress, - amount: "10000000000000000", + let nonce: UInt64 = 1 + let expected = FilecoinSignedMessage( + message: FilecoinMessage( + from: Constants.sourceAddress, + to: Constants.destinationAddress, + value: "1000000000000000000", nonce: nonce, - gasUnitPrice: "101225", - gasLimit: 1526328, - gasPremium: "50612" + gasLimit: 1527953, + gasFeeCap: "100704", + gasPremium: "99503" ), - signature: FilecoinSignedTransactionBody.Signature( + signature: FilecoinSignedMessage.Signature( type: 1, - signature: "Bogel9o9zvXUT+sC+nVpciGyHfBxWG6V4+xOawP6YrAU1OIbifvEHpRT/Elakv2X6mfUkbQzparvc2HyJBbXRwE=" + data: "weMNBBonfukL/wkGAb6z8ZM8c5Op5BuFPMvQAVZvdJkU0/HdRX+DEPV+A4x5sWKmWbZzyIgNyGhxpbD2yO3vkgA=" ) ) @@ -94,10 +87,10 @@ final class FilecoinTransactionBuilderTests: XCTestCase { private extension FilecoinTransactionBuilderTests { enum Constants { - static let publicKey = Data(hex: "0374D0F81F42DDFE34114D533E95E6AE5FE6EA271C96F1FA505199FDC365AE9720") - static let signature = Data(hex: "06881E97DA3DCEF5D44FEB02FA75697221B21DF071586E95E3EC4E6B03FA62B014D4E21B89FBC41E9453FC495A92FD97EA67D491B433A5AAEF7361F22416D74701") + static let publicKey = Data(hex: "02a1f09e4d91756b9f1d4f96c2c71d09178e3850a70703c3d089dad84f3870b3c6") + static let signature = Data(hex: "c1e30d041a277ee90bff090601beb3f1933c7393a9e41b853ccbd001566f749914d3f1dd457f8310f57e038c79b162a659b673c8880dc86871a5b0f6c8edef92") - static let sourceAddress = "f1flbddhx4vwox3y3ux5bwgsgq2frzeiuvvdrjo7i" - static let destinationAddress = "f1rluskhwvv5b3z36skltu4noszbc5stfihevbf2i" + static let sourceAddress = "f1kub5b7ekrwn7vykavn7owjuff7kqcoa4g4fgriq" + static let destinationAddress = "f1ufoxbvz637fkjbrk2d4cktqsgjwsqwm4woa7pda" } }