Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Fix #5724: Integrate solana send transaction (#5728)
Browse files Browse the repository at this point in the history
  • Loading branch information
nuo-xu authored Jul 25, 2022
1 parent 3296c54 commit d1556c9
Show file tree
Hide file tree
Showing 20 changed files with 474 additions and 100 deletions.
9 changes: 4 additions & 5 deletions BraveWallet/Crypto/BuySendSwap/SendTokenView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ struct SendTokenView: View {
return true
}

return sendAmount == 0
|| sendAmount > balance
|| sendTokenStore.sendAmount.isEmpty
|| (sendTokenStore.addressError != nil && sendTokenStore.addressError != .missingChecksum)
return sendAmount == 0 || sendAmount > balance || sendTokenStore.sendAmount.isEmpty || sendTokenStore.sendAddress.isEmpty || (sendTokenStore.addressError != nil && sendTokenStore.addressError != .missingChecksum)
}

var body: some View {
Expand Down Expand Up @@ -157,7 +154,9 @@ struct SendTokenView: View {
WalletLoadingButton(
isLoading: sendTokenStore.isMakingTx,
action: {
sendTokenStore.sendToken(amount: sendTokenStore.sendAmount) { success in
sendTokenStore.sendToken(
amount: sendTokenStore.sendAmount
) { success, _ in
isShowingError = !success
completion?(success)
}
Expand Down
2 changes: 1 addition & 1 deletion BraveWallet/Crypto/Search/AssetSearchView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct AssetSearchView: View {

var body: some View {
NavigationView {
TokenList(tokens: allTokens.filter { !$0.isErc721 || $0.symbol == cryptoStore.networkStore.selectedChain.symbol }) { token in
TokenList(tokens: allTokens.filter { !$0.isErc721 || cryptoStore.networkStore.selectedChain.isNativeAsset($0) }) { token in
NavigationLink(
destination: AssetDetailView(
assetDetailStore: cryptoStore.assetDetailStore(for: token),
Expand Down
2 changes: 1 addition & 1 deletion BraveWallet/Crypto/Search/SendTokenSearchView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct SendTokenSearchView: View {
var network: BraveWallet.NetworkInfo

var body: some View {
TokenList(tokens: sendTokenStore.userAssets.filter { !$0.isErc721 || $0.symbol == network.symbol }) { token in
TokenList(tokens: sendTokenStore.userAssets.filter { !$0.isErc721 || network.isNativeAsset($0) }) { token in
Button(action: {
sendTokenStore.selectedSendToken = token
presentationMode.dismiss()
Expand Down
49 changes: 43 additions & 6 deletions BraveWallet/Crypto/Stores/CryptoStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public class CryptoStore: ObservableObject {
let blockchainRegistry: BraveWalletBlockchainRegistry
private let txService: BraveWalletTxService
private let ethTxManagerProxy: BraveWalletEthTxManagerProxy
private let solTxManagerProxy: BraveWalletSolanaTxManagerProxy

public init(
keyringService: BraveWalletKeyringService,
Expand All @@ -76,7 +77,8 @@ public class CryptoStore: ObservableObject {
swapService: BraveWalletSwapService,
blockchainRegistry: BraveWalletBlockchainRegistry,
txService: BraveWalletTxService,
ethTxManagerProxy: BraveWalletEthTxManagerProxy
ethTxManagerProxy: BraveWalletEthTxManagerProxy,
solTxManagerProxy: BraveWalletSolanaTxManagerProxy
) {
self.keyringService = keyringService
self.rpcService = rpcService
Expand All @@ -86,6 +88,7 @@ public class CryptoStore: ObservableObject {
self.blockchainRegistry = blockchainRegistry
self.txService = txService
self.ethTxManagerProxy = ethTxManagerProxy
self.solTxManagerProxy = solTxManagerProxy

self.networkStore = .init(
keyringService: keyringService,
Expand Down Expand Up @@ -130,6 +133,7 @@ public class CryptoStore: ObservableObject {
txService: txService,
blockchainRegistry: blockchainRegistry,
ethTxManagerProxy: ethTxManagerProxy,
solTxManagerProxy: solTxManagerProxy,
prefilledToken: prefilledToken
)
sendTokenStore = store
Expand Down Expand Up @@ -258,12 +262,45 @@ public class CryptoStore: ObservableObject {

@MainActor
func fetchPendingTransactions() async -> [BraveWallet.TransactionInfo] {
let keyring = await keyringService.keyringInfo(BraveWallet.DefaultKeyringId)
var allKeyrings: [BraveWallet.KeyringInfo] = []
allKeyrings = await withTaskGroup(
of: BraveWallet.KeyringInfo.self,
returning: [BraveWallet.KeyringInfo].self,
body: { [weak keyringService] group in
guard let keyringService = keyringService else { return [] }
for coin in WalletConstants.supportedCoinTypes {
group.addTask {
await keyringService.keyringInfo(coin.keyringId)
}
}
var allKeyrings: [BraveWallet.KeyringInfo] = []
for await keyring in group {
allKeyrings.append(keyring)
}
return allKeyrings
}
)

var pendingTransactions: [BraveWallet.TransactionInfo] = []
for info in keyring.accountInfos {
let allTransactionInfo = await txService.allTransactionInfo(.eth, from: info.address)
pendingTransactions.append(contentsOf: allTransactionInfo.filter { $0.txStatus == .unapproved })
}
pendingTransactions = await withTaskGroup(
of: [BraveWallet.TransactionInfo].self,
body: { [weak txService] group in
guard let txService = txService else { return [] }
for keyring in allKeyrings {
for info in keyring.accountInfos {
group.addTask {
await txService.allTransactionInfo(info.coin, from: info.address)
}
}
}
var allPendingTx: [BraveWallet.TransactionInfo] = []
for await transactions in group {
allPendingTx.append(contentsOf: transactions.filter { $0.txStatus == .unapproved })
}
return allPendingTx
}
)

return pendingTransactions
}

Expand Down
185 changes: 133 additions & 52 deletions BraveWallet/Crypto/Stores/SendTokenStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class SendTokenStore: ObservableObject {
@Published var selectedSendToken: BraveWallet.BlockchainToken? {
didSet {
fetchAssetBalance()
validateSendAddress()
}
}
/// The current selected token balance. Default with nil value.
Expand Down Expand Up @@ -44,6 +45,7 @@ public class SendTokenStore: ObservableObject {
case notEthAddress
case missingChecksum
case invalidChecksum
case notSolAddress

var errorDescription: String? {
switch self {
Expand All @@ -57,6 +59,8 @@ public class SendTokenStore: ObservableObject {
return Strings.Wallet.sendWarningAddressMissingChecksumInfo
case .invalidChecksum:
return Strings.Wallet.sendWarningAddressInvalidChecksum
case .notSolAddress:
return Strings.Wallet.sendWarningSolAddressNotValid
}
}
}
Expand All @@ -67,6 +71,7 @@ public class SendTokenStore: ObservableObject {
private let txService: BraveWalletTxService
private let blockchainRegistry: BraveWalletBlockchainRegistry
private let ethTxManagerProxy: BraveWalletEthTxManagerProxy
private let solTxManagerProxy: BraveWalletSolanaTxManagerProxy
private var allTokens: [BraveWallet.BlockchainToken] = []
private var currentAccountAddress: String?
private var timer: Timer?
Expand All @@ -78,6 +83,7 @@ public class SendTokenStore: ObservableObject {
txService: BraveWalletTxService,
blockchainRegistry: BraveWalletBlockchainRegistry,
ethTxManagerProxy: BraveWalletEthTxManagerProxy,
solTxManagerProxy: BraveWalletSolanaTxManagerProxy,
prefilledToken: BraveWallet.BlockchainToken?
) {
self.keyringService = keyringService
Expand All @@ -86,6 +92,7 @@ public class SendTokenStore: ObservableObject {
self.txService = txService
self.blockchainRegistry = blockchainRegistry
self.ethTxManagerProxy = ethTxManagerProxy
self.solTxManagerProxy = solTxManagerProxy
self.selectedSendToken = prefilledToken

self.keyringService.add(self)
Expand Down Expand Up @@ -179,51 +186,78 @@ public class SendTokenStore: ObservableObject {
chainId: String,
baseData: BraveWallet.TxData,
from address: String,
completion: @escaping (_ success: Bool) -> Void
completion: @escaping (_ success: Bool, _ errMsg: String?) -> Void
) {
let eip1559Data = BraveWallet.TxData1559(baseData: baseData, chainId: chainId, maxPriorityFeePerGas: "", maxFeePerGas: "", gasEstimation: nil)
let txDataUnion = BraveWallet.TxDataUnion(ethTxData1559: eip1559Data)
self.txService.addUnapprovedTransaction(txDataUnion, from: address, origin: nil) { success, txMetaId, errorMessage in
completion(success)
completion(success, errorMessage)
}
}

private func validateSendAddress() {
guard !sendAddress.isEmpty else {
guard !sendAddress.isEmpty, let token = selectedSendToken else {
addressError = nil
return
}
let normalizedSendAddress = sendAddress.lowercased()
if !sendAddress.isETHAddress {
// 1. check if send address is a valid eth address
addressError = .notEthAddress
} else if currentAccountAddress?.lowercased() == normalizedSendAddress {
// 2. check if send address is the same as the from address
addressError = .sameAsFromAddress
} else if (userAssets.first(where: { $0.contractAddress.lowercased() == normalizedSendAddress }) != nil)
|| (allTokens.first(where: { $0.contractAddress.lowercased() == normalizedSendAddress }) != nil) {
// 3. check if send address is a contract address
addressError = .contractAddress
} else {
keyringService.checksumEthAddress(sendAddress) { [self] checksumAddress in
if sendAddress == checksumAddress {
// 4. check if send address is the same as the checksum address from the `KeyringService`
addressError = nil
} else if sendAddress.removingHexPrefix.lowercased() == sendAddress.removingHexPrefix || sendAddress.removingHexPrefix.uppercased() == sendAddress.removingHexPrefix {
// 5. check if send address has each of the alphabetic character as uppercase, or has each of
// the alphabeic character as lowercase
addressError = .missingChecksum
} else {
// 6. send address has mixed with uppercase and lowercase and does not match with the checksum address
addressError = .invalidChecksum
if token.coin == .eth {
if !sendAddress.isETHAddress {
// 1. check if send address is a valid eth address
addressError = .notEthAddress
} else if currentAccountAddress?.lowercased() == normalizedSendAddress {
// 2. check if send address is the same as the from address
addressError = .sameAsFromAddress
} else if (userAssets.first(where: { $0.contractAddress.lowercased() == normalizedSendAddress }) != nil)
|| (allTokens.first(where: { $0.contractAddress.lowercased() == normalizedSendAddress }) != nil) {
// 3. check if send address is a contract address
addressError = .contractAddress
} else {
keyringService.checksumEthAddress(sendAddress) { [self] checksumAddress in
if sendAddress == checksumAddress {
// 4. check if send address is the same as the checksum address from the `KeyringService`
addressError = nil
} else if sendAddress.removingHexPrefix.lowercased() == sendAddress.removingHexPrefix || sendAddress.removingHexPrefix.uppercased() == sendAddress.removingHexPrefix {
// 5. check if send address has each of the alphabetic character as uppercase, or has each of
// the alphabeic character as lowercase
addressError = .missingChecksum
} else {
// 6. send address has mixed with uppercase and lowercase and does not match with the checksum address
addressError = .invalidChecksum
}
}
}
} else if token.coin == .sol {
walletService.isBase58EncodedSolanaPubkey(sendAddress) { [self] valid in
if !valid {
addressError = .notSolAddress
} else if currentAccountAddress?.lowercased() == normalizedSendAddress {
addressError = .sameAsFromAddress
}
}
}
}

func sendToken(
amount: String,
completion: @escaping (_ success: Bool) -> Void
completion: @escaping (_ success: Bool, _ errMsg: String?) -> Void
) {
walletService.selectedCoin { [weak self] coin in
guard let self = self else { return }
switch coin {
case .eth:
self.sendTokenOnEth(amount: amount, completion: completion)
case .sol:
self.sendTokenOnSol(amount: amount, completion: completion)
default:
break
}
}
}

func sendTokenOnEth(
amount: String,
completion: @escaping (_ success: Bool, _ errMsg: String?) -> Void
) {
let weiFormatter = WeiFormatter(decimalFormatStyle: .decimals(precision: 18))
guard
Expand All @@ -233,42 +267,89 @@ public class SendTokenStore: ObservableObject {
else { return }

isMakingTx = true
walletService.selectedCoin { [weak self] coin in
rpcService.network(.eth) { [weak self] network in
guard let self = self else { return }
self.rpcService.network(coin) { network in
if token.contractAddress.isEmpty {
let baseData = BraveWallet.TxData(nonce: "", gasPrice: "", gasLimit: "", to: self.sendAddress, value: "0x\(weiHexString)", data: .init())
if network.isNativeAsset(token) {
let baseData = BraveWallet.TxData(nonce: "", gasPrice: "", gasLimit: "", to: self.sendAddress, value: "0x\(weiHexString)", data: .init())
if network.isEip1559 {
self.makeEIP1559Tx(chainId: network.chainId, baseData: baseData, from: fromAddress) { success, errorMessage in
self.isMakingTx = false
completion(success, errorMessage)
}
} else {
let txDataUnion = BraveWallet.TxDataUnion(ethTxData: baseData)
self.txService.addUnapprovedTransaction(txDataUnion, from: fromAddress, origin: nil) { success, txMetaId, errorMessage in
self.isMakingTx = false
completion(success, errorMessage)
}
}
} else {
self.ethTxManagerProxy.makeErc20TransferData(self.sendAddress, amount: "0x\(weiHexString)") { success, data in
guard success else {
completion(false, nil)
return
}
let baseData = BraveWallet.TxData(nonce: "", gasPrice: "", gasLimit: "", to: token.contractAddress, value: "0x0", data: data)
if network.isEip1559 {
self.makeEIP1559Tx(chainId: network.chainId, baseData: baseData, from: fromAddress) { success in
self.makeEIP1559Tx(chainId: network.chainId, baseData: baseData, from: fromAddress) { success, errorMessage in
self.isMakingTx = false
completion(success)
completion(success, errorMessage)
}
} else {
let txDataUnion = BraveWallet.TxDataUnion(ethTxData: baseData)
self.txService.addUnapprovedTransaction(txDataUnion, from: fromAddress, origin: nil) { success, txMetaId, errorMessage in
self.isMakingTx = false
completion(success)
completion(success, errorMessage)
}
}
} else {
self.ethTxManagerProxy.makeErc20TransferData(self.sendAddress, amount: "0x\(weiHexString)") { success, data in
guard success else {
completion(false)
return
}
let baseData = BraveWallet.TxData(nonce: "", gasPrice: "", gasLimit: "", to: token.contractAddress, value: "0x0", data: data)
if network.isEip1559 {
self.makeEIP1559Tx(chainId: network.chainId, baseData: baseData, from: fromAddress) { success in
self.isMakingTx = false
completion(success)
}
} else {
let txDataUnion = BraveWallet.TxDataUnion(ethTxData: baseData)
self.txService.addUnapprovedTransaction(txDataUnion, from: fromAddress, origin: nil) { success, txMetaId, errorMessage in
self.isMakingTx = false
completion(success)
}
}
}
}
}
}

private func sendTokenOnSol(
amount: String,
completion: @escaping (_ success: Bool, _ errMsg: String?) -> Void
) {
guard let token = selectedSendToken,
let fromAddress = currentAccountAddress,
let lamports = WeiFormatter.decimalToLamports(amount)
else {
completion(false, "An Internal Error")
return
}

rpcService.network(.sol) { [weak self] network in
guard let self = self else { return }
if network.isNativeAsset(token) {
self.solTxManagerProxy.makeSystemProgramTransferTxData(
fromAddress,
to: self.sendAddress,
lamports: lamports
) { solTxData, error, errMsg in
guard let solanaTxData = solTxData else {
completion(false, errMsg)
return
}
let txDataUnion = BraveWallet.TxDataUnion(solanaTxData: solanaTxData)
self.txService.addUnapprovedTransaction(txDataUnion, from: fromAddress, origin: nil) { success, txMetaId, errMsg in
completion(success, errMsg)
}
}
} else {
self.solTxManagerProxy.makeTokenProgramTransferTxData(
token.contractAddress,
fromWalletAddress: fromAddress,
toWalletAddress: self.sendAddress,
amount: lamports
) { solTxData, error, errMsg in
guard let solanaTxData = solTxData else {
completion(false, errMsg)
return
}
let txDataUnion = BraveWallet.TxDataUnion(solanaTxData: solanaTxData)
self.txService.addUnapprovedTransaction(txDataUnion, from: fromAddress, origin: nil) { success, txMetaId, errorMessage in
completion(success, errorMessage)
}
}
}
Expand Down
Loading

0 comments on commit d1556c9

Please sign in to comment.