mirror of
https://github.com/lil-org/tokenary.git
synced 2024-12-15 06:56:34 +03:00
176 lines
7.1 KiB
Swift
176 lines
7.1 KiB
Swift
// Copyright © 2021 Encrypted Ink. All rights reserved.
|
|
|
|
import Foundation
|
|
import Web3Swift
|
|
import CryptoSwift
|
|
|
|
struct Ethereum {
|
|
|
|
enum Errors: Error {
|
|
case invalidInputData
|
|
case failedToSendTransaction
|
|
}
|
|
|
|
private static let queue = DispatchQueue(label: "Ethereum", qos: .default)
|
|
|
|
private static let network: Network = AlchemyNetwork(
|
|
chain: "mainnet",
|
|
apiKey: Secrets.alchemy
|
|
)
|
|
|
|
static func sign(message: String, account: Account) throws -> String {
|
|
let ethPrivateKey = EthPrivateKey(hex: account.privateKey)
|
|
|
|
let signature = SECP256k1Signature(
|
|
privateKey: ethPrivateKey,
|
|
message: UTF8StringBytes(string: message),
|
|
hashFunction: SHA3(variant: .keccak256).calculate
|
|
)
|
|
let data = try ConcatenatedBytes(
|
|
bytes: [
|
|
signature.r(),
|
|
signature.s(),
|
|
EthNumber(value: signature.recoverID().value() + 27)
|
|
]
|
|
).value()
|
|
return data.toPrefixedHexString()
|
|
}
|
|
|
|
static func signPersonal(message: String, account: Account) throws -> String {
|
|
let ethPrivateKey = EthPrivateKey(hex: account.privateKey)
|
|
let signed = SignedPersonalMessageBytes(message: message, signerKey: ethPrivateKey)
|
|
let data = try signed.value().toPrefixedHexString()
|
|
return data
|
|
}
|
|
|
|
static func sign(typedData: String, account: Account) throws -> String {
|
|
let data = try EIP712TypedData(jsonString: typedData)
|
|
let hash = EIP712Hash(domain: data.domain, typedData: data)
|
|
let privateKey = EthPrivateKey(hex: account.privateKey)
|
|
let signer = EIP712Signer(privateKey: privateKey)
|
|
return try signer.signatureData(hash: hash).toPrefixedHexString()
|
|
}
|
|
|
|
static func send(transaction: Transaction, account: Account) throws -> String {
|
|
let bytes = signedTransactionBytes(transaction: transaction, account: account)
|
|
let response = try SendRawTransactionProcedure(network: network, transactionBytes: bytes).call()
|
|
guard let hash = response["result"].string else {
|
|
throw Errors.failedToSendTransaction
|
|
}
|
|
return hash
|
|
}
|
|
|
|
private static func signedTransactionBytes(transaction: Transaction, account: Account) -> EthContractCallBytes {
|
|
let senderKey = EthPrivateKey(hex: account.privateKey)
|
|
let contractAddress = EthAddress(hex: transaction.to)
|
|
let functionCall = BytesFromHexString(hex: transaction.data)
|
|
let bytes: EthContractCallBytes
|
|
if let gasPriceString = transaction.gasPrice {
|
|
let gasPrice = EthNumber(hex: gasPriceString)
|
|
if let gasEstimateString = transaction.gas,
|
|
let transctionCountString = transaction.nonce {
|
|
let gasEstimate = EthNumber(hex: gasEstimateString)
|
|
let transactionCount = EthNumber(hex: transctionCountString)
|
|
|
|
bytes = EthContractCallBytes(networkID: NetworkID(network: network),
|
|
transactionsCount: transactionCount,
|
|
gasPrice: gasPrice,
|
|
gasEstimate: gasEstimate,
|
|
senderKey: senderKey,
|
|
contractAddress: contractAddress,
|
|
weiAmount: transaction.weiAmount,
|
|
functionCall: functionCall)
|
|
} else {
|
|
bytes = EthContractCallBytes(network: network,
|
|
gasPrice: gasPrice,
|
|
senderKey: senderKey,
|
|
contractAddress: contractAddress,
|
|
weiAmount: transaction.weiAmount,
|
|
functionCall: functionCall)
|
|
}
|
|
} else {
|
|
bytes = EthContractCallBytes(network: network,
|
|
senderKey: senderKey,
|
|
contractAddress: contractAddress,
|
|
weiAmount: transaction.weiAmount,
|
|
functionCall: functionCall)
|
|
}
|
|
return bytes
|
|
}
|
|
|
|
static func prepareTransaction(_ transaction: Transaction, completion: @escaping (Transaction) -> Void) {
|
|
var transaction = transaction
|
|
|
|
if transaction.nonce == nil {
|
|
getNonce(from: transaction.from) { nonce in
|
|
transaction.nonce = nonce
|
|
completion(transaction)
|
|
}
|
|
}
|
|
|
|
func getGasIfNeeded(gasPrice: String) {
|
|
guard transaction.gas == nil else { return }
|
|
getGas(from: transaction.from, to: transaction.to, gasPrice: gasPrice, weiAmount: transaction.weiAmount, data: transaction.data) { gas in
|
|
transaction.gas = gas
|
|
completion(transaction)
|
|
}
|
|
}
|
|
|
|
if let gasPrice = transaction.gasPrice {
|
|
getGasIfNeeded(gasPrice: gasPrice)
|
|
} else {
|
|
getGasPrice { gasPrice in
|
|
transaction.gasPrice = gasPrice
|
|
completion(transaction)
|
|
if let gasPrice = gasPrice {
|
|
getGasIfNeeded(gasPrice: gasPrice)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private static func getGas(from: String, to: String, gasPrice: String, weiAmount: EthNumber, data: String, completion: @escaping (String?) -> Void) {
|
|
queue.async {
|
|
let gas = try? EthGasEstimate(
|
|
network: network,
|
|
senderAddress: EthAddress(hex: from),
|
|
recipientAddress: EthAddress(hex: to),
|
|
gasEstimate: EthGasEstimate(
|
|
network: network,
|
|
senderAddress: EthAddress(hex: from),
|
|
recipientAddress: EthAddress(hex: to),
|
|
gasPrice: EthNumber(hex: gasPrice),
|
|
weiAmount: weiAmount,
|
|
contractCall: BytesFromHexString(hex: data)
|
|
),
|
|
gasPrice: EthNumber(hex: gasPrice),
|
|
weiAmount: weiAmount,
|
|
contractCall: BytesFromHexString(hex: data)
|
|
).value().toHexString()
|
|
DispatchQueue.main.async {
|
|
completion(gas)
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func getGasPrice(completion: @escaping (String?) -> Void) {
|
|
queue.async {
|
|
let gasPrice = try? EthGasPrice(network: network).value().toHexString()
|
|
DispatchQueue.main.async {
|
|
completion(gasPrice)
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func getNonce(from: String, completion: @escaping (String?) -> Void) {
|
|
queue.async {
|
|
let nonce = try? EthTransactions(network: network, address: EthAddress(hex: from), blockChainState: PendingBlockChainState()).count().value().toHexString()
|
|
DispatchQueue.main.async {
|
|
completion(nonce)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|