Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IOS-7779 Add TransactionBuilder #817

Merged
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions BlockchainSdk.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions BlockchainSdk/Blockchains/Filecoin/FilecoinFeeParameters.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// FilecoinFeeParameters.swift
// BlockchainSdk
//
// Created by Aleksei Muraveinik on 30.08.24.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import BigInt

struct FilecoinFeeParameters: FeeParameters {
let gasUnitPrice: BigUInt
let gasLimit: Int64
let gasPremium: BigUInt
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

По поводу типов данных - я провалидирую корректность в следующем PRе (про WalletManager)

}
51 changes: 51 additions & 0 deletions BlockchainSdk/Blockchains/Filecoin/FilecoinNetworkProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// FilecoinNetworkProvider.swift
// BlockchainSdk
//
// Created by Aleksei Muraveinik on 27.08.24.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Combine
import Foundation

final class FilecoinNetworkProvider: HostProvider {
var host: String {
node.url.absoluteString
}

private let node: NodeInfo
private let provider: NetworkProvider<FilecoinTarget>

init(
node: NodeInfo,
configuration: NetworkProviderConfiguration
) {
self.node = node
provider = NetworkProvider<FilecoinTarget>(configuration: configuration)
}

func getActorInfo(address: String) -> AnyPublisher<FilecoinResponse.GetActorInfo, Error> {
requestPublisher(for: .getActorInfo(address: address))
}

func getGasUnitPrice(transactionInfo: FilecoinTxInfo) -> AnyPublisher<String, Error> {
requestPublisher(for: .getGasUnitPrice(transactionInfo: transactionInfo))
}

func getGasLimit(transactionInfo: FilecoinTxInfo) -> AnyPublisher<UInt64, Error> {
requestPublisher(for: .getGasLimit(transactionInfo: transactionInfo))
}

func submitTransaction(signedTransactionBody: FilecoinSignedTransactionBody) -> AnyPublisher<FilecoinResponse.SubmitTransaction, Error> {
requestPublisher(for: .submitTransaction(signedTransactionBody: signedTransactionBody))
}

private func requestPublisher<T: Decodable>(for target: FilecoinTarget.FilecoinTargetType) -> AnyPublisher<T, Error> {
provider.requestPublisher(FilecoinTarget(node: node, target))
.filterSuccessfulStatusAndRedirectCodes()
.map(JSONRPC.Response<T, JSONRPC.APIError>.self, using: .withSnakeCaseStrategy)
.tryMap { try $0.result.get() }
.eraseToAnyPublisher()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// FilecoinTransactionBuilder.swift
// BlockchainSdk
//
// Created by Aleksei Muraveinik on 29.08.24.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import TangemSdk
import WalletCore

final class FilecoinTransactionBuilder {
private let wallet: Wallet

init(wallet: Wallet) {
self.wallet = wallet
}
m3g0byt3 marked this conversation as resolved.
Show resolved Hide resolved

func buildForSign(transaction: Transaction, nonce: UInt64) throws -> Data {
guard let feeParameters = transaction.fee.parameters as? FilecoinFeeParameters else {
throw WalletError.failedToBuildTx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Прикольно еще было бы поменять FilecoinTransactionBuilderError.filecoinFeeParametersNotFound что бы если что проще искать было, в чем проблема, а то WalletError.failedToBuildTx много где используется

}

let input = try makeSigningInput(transaction: transaction, nonce: nonce, feeParameters: feeParameters)
let txInputData = try input.serializedData()

let preImageHashes = TransactionCompiler.preImageHashes(coinType: .filecoin, txInputData: txInputData)
let preSigningOutput = try TxCompilerPreSigningOutput(serializedData: preImageHashes)

return preSigningOutput.dataHash
}

func buildForSend(
transaction: Transaction,
nonce: UInt64,
signatureInfo: SignatureInfo
) throws -> FilecoinSignedTransactionBody {
guard let feeParameters = transaction.fee.parameters as? FilecoinFeeParameters else {
throw WalletError.failedToBuildTx
}

let signatures = DataVector()
signatures.add(data: signatureInfo.signature)

let publicKeys = DataVector()
publicKeys.add(data: try Secp256k1Key(with: signatureInfo.publicKey).decompress())

let input = try makeSigningInput(transaction: transaction, nonce: nonce, feeParameters: feeParameters)
let txInputData = try input.serializedData()

let compiledWithSignatures = TransactionCompiler.compileWithSignatures(
coinType: .filecoin,
txInputData: txInputData,
signatures: signatures,
publicKeys: publicKeys
)

let signingOutput = try FilecoinSigningOutput(serializedData: compiledWithSignatures)

guard let jsonData = signingOutput.json.data(using: .utf8) else {
throw WalletError.failedToBuildTx
}

return try JSONDecoder().decode(FilecoinSignedTransactionBody.self, from: jsonData)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А там в API прям json будет уходить? обычно просто длинная hex строка

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

private func makeSigningInput(
transaction: Transaction,
nonce: UInt64,
feeParameters: FilecoinFeeParameters
) throws -> FilecoinSigningInput {
guard let value = transaction.amount.bigUIntValue else {
throw WalletError.failedToBuildTx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

и тут тоже, ну во всех местах лучше бы FilecoinTransactionBuilderError юзать)

}

return try 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.gasPremium = feeParameters.gasPremium.serialize()

input.publicKey = try Secp256k1Key(with: wallet.publicKey.blockchainKey).decompress()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше декомпресснуть один раз и положить в параметры класса

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пофиксил в следующем PR
#818 (comment)

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// FilecoinAccountInfo.swift
// BlockchainSdk
//
// Created by Aleksei Muraveinik on 27.08.24.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation

struct FilecoinAccountInfo {
let balance: Decimal
let nonce: UInt64
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// 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
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// FilecoinRpcResponseResult.swift
// BlockchainSdk
//
// Created by Aleksei Muraveinik on 27.08.24.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation

enum FilecoinResponse {
struct GetActorInfo: Decodable {
let balance: String
let nonce: UInt64

enum CodingKeys: String, CodingKey {
case balance = "Balance"
case nonce = "Nonce"
}
}

struct SubmitTransaction: Decodable {
let hash: String

enum CodingKeys: String, CodingKey {
case hash = "/"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// FilecoinSignedTransactionBody.swift
// BlockchainSdk
//
// Created by Aleksei Muraveinik on 27.08.24.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation

struct FilecoinSignedTransactionBody: Codable, Equatable {
tureck1y marked this conversation as resolved.
Show resolved Hide resolved
struct Signature: Codable, Equatable {
let type: Int
let signature: String

enum CodingKeys: String, CodingKey {
case type = "Type"
case signature = "Data"
}
}

let transactionBody: FilecoinTransactionBody
let signature: Signature

enum CodingKeys: String, CodingKey {
case transactionBody = "Message"
case signature = "Signature"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// FilecoinTransactionBody.swift
// BlockchainSdk
//
// Created by Aleksei Muraveinik on 27.08.24.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation

struct FilecoinTransactionBody: Codable, Equatable {
let sourceAddress: String
let destinationAddress: String
let amount: String
let nonce: UInt64
let gasUnitPrice: String?
let gasLimit: UInt64?
let gasPremium: String?

enum CodingKeys: String, CodingKey {
case sourceAddress = "From"
case destinationAddress = "To"
case amount = "Value"
case nonce = "Nonce"
case gasUnitPrice = "GasFeeCap"
case gasLimit = "GasLimit"
case gasPremium = "GasPremium"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// 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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// 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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// FilecoinNetworkService.swift
// BlockchainSdk
//
// Created by Aleksei Muraveinik on 27.08.24.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Combine

class FilecoinNetworkService: MultiNetworkProvider {
let providers: [FilecoinNetworkProvider]
var currentProviderIndex = 0

init(providers: [FilecoinNetworkProvider]) {
self.providers = providers
}

func getAccountInfo(address: String) -> AnyPublisher<FilecoinAccountInfo, Error> {
providerPublisher { provider in
provider
.getActorInfo(address: address)
.tryMap { response in
guard let balance = Decimal(stringValue: response.balance) else {
throw WalletError.failedToParseNetworkResponse()
}

return FilecoinAccountInfo(
balance: balance,
nonce: response.nonce
)
}
.eraseToAnyPublisher()
}
}

func getGasUnitPrice(transactionInfo: FilecoinTxInfo) -> AnyPublisher<UInt64, Error> {
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<UInt64, Error> {
providerPublisher { provider in
provider
.getGasLimit(transactionInfo: transactionInfo)
}
}

func submitTransaction(signedTransactionBody: FilecoinSignedTransactionBody) -> AnyPublisher<String, Error> {
providerPublisher { provider in
provider
.submitTransaction(signedTransactionBody: signedTransactionBody)
.map(\.hash)
.eraseToAnyPublisher()
}
}
}
Loading