-
Notifications
You must be signed in to change notification settings - Fork 9
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
Changes from 13 commits
7516e93
bb770e1
b81a6ce
43be0dc
bf272b3
c43e5b0
f897f62
503be34
69202d6
66bba5b
490d283
bc59262
1462f26
e8b538f
e0b4aa8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
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 | ||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Прикольно еще было бы поменять |
||
} | ||
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А там в API прям json будет уходить? обычно просто длинная hex строка There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. и тут тоже, ну во всех местах лучше бы |
||
} | ||
|
||
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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Лучше декомпресснуть один раз и положить в параметры класса There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Пофиксил в следующем PR |
||
} | ||
} | ||
} |
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() | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
По поводу типов данных - я провалидирую корректность в следующем PRе (про WalletManager)