Skip to content

Commit

Permalink
MOB-2078 - Support sending to none EVM addresses like SOL and BTC for…
Browse files Browse the repository at this point in the history
… ULW (#586)
  • Loading branch information
Oleg-Pecheneg authored Jun 25, 2024
1 parent 03d3939 commit e93f88c
Show file tree
Hide file tree
Showing 12 changed files with 104 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ final class CoinRecordsService: CoinRecordsServiceProtocol {
[.init(ticker: "ETH",
version: nil,
expandedTicker: "crypto.ETH",
regexPattern: Constants.ETHRegexPattern,
regexPattern: CoinRegexPattern.eth.regex,
isDeprecated: false),
.init(ticker: "MATIC",
version: nil,
expandedTicker: "crypto.MATIC",
regexPattern: Constants.ETHRegexPattern,
regexPattern: CoinRegexPattern.eth.regex,
isDeprecated: false),
.init(ticker: "BTC",
version: nil,
expandedTicker: "crypto.BTC.address",
regexPattern: "^bc1[ac-hj-np-z02-9]{6,87}$|^[13][a-km-zA-HJ-NP-Z1-9]{25,39}$",
regexPattern: CoinRegexPattern.btc.regex,
isDeprecated: false)]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@
C6011AB628F4022300342666 /* DomainProfileCryptoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6011AB528F4022300342666 /* DomainProfileCryptoSection.swift */; };
C6011ABD28F4045000342666 /* DomainProfileTopInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6011ABB28F4045000342666 /* DomainProfileTopInfoCell.swift */; };
C6011AC128F4045000342666 /* DomainProfileTopInfoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C6011ABC28F4045000342666 /* DomainProfileTopInfoCell.xib */; };
C60319922C29775E00A77109 /* CoinRegexPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60319912C29775E00A77109 /* CoinRegexPattern.swift */; };
C60319932C29775E00A77109 /* CoinRegexPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60319912C29775E00A77109 /* CoinRegexPattern.swift */; };
C603A53628522C0D003962E3 /* IgnoreFailureArrayElement+PropertyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C603A53528522C0D003962E3 /* IgnoreFailureArrayElement+PropertyWrapper.swift */; };
C603D79A2A56B11800CEC696 /* MessagingChatMessageUnknownTypeDisplayInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C603D7992A56B11800CEC696 /* MessagingChatMessageUnknownTypeDisplayInfo.swift */; };
C603E8C9285859560028C21C /* CollectionViewHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C603E8C7285859560028C21C /* CollectionViewHeaderCell.swift */; };
Expand Down Expand Up @@ -2765,6 +2767,7 @@
C6011AB528F4022300342666 /* DomainProfileCryptoSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainProfileCryptoSection.swift; sourceTree = "<group>"; };
C6011ABB28F4045000342666 /* DomainProfileTopInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainProfileTopInfoCell.swift; sourceTree = "<group>"; };
C6011ABC28F4045000342666 /* DomainProfileTopInfoCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DomainProfileTopInfoCell.xib; sourceTree = "<group>"; };
C60319912C29775E00A77109 /* CoinRegexPattern.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinRegexPattern.swift; sourceTree = "<group>"; };
C603A53528522C0D003962E3 /* IgnoreFailureArrayElement+PropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IgnoreFailureArrayElement+PropertyWrapper.swift"; sourceTree = "<group>"; };
C603D7992A56B11800CEC696 /* MessagingChatMessageUnknownTypeDisplayInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingChatMessageUnknownTypeDisplayInfo.swift; sourceTree = "<group>"; };
C603E8C7285859560028C21C /* CollectionViewHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewHeaderCell.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -7501,6 +7504,7 @@
C61DB10D2B95925C00CDA243 /* BalanceStringFormatter.swift */,
C63B1F332A4595D000B1D7D1 /* Base64DataTransformer.swift */,
C6EECE962833A3E400978ED5 /* CoinRecord.swift */,
C60319912C29775E00A77109 /* CoinRegexPattern.swift */,
C6C9DEAD2834BD9300BAC36F /* CopyWalletAddressPullUpHandler.swift */,
C6D5DABB2836021300379C38 /* CryptoRecord.swift */,
C68017852886730300524712 /* CurrencyImageLoader.swift */,
Expand Down Expand Up @@ -9366,6 +9370,7 @@
C64939FC2B637DDB00457363 /* ReverseResolutionSelectionView.swift in Sources */,
C69F99412A9F1478004B1958 /* DomainProfileBadgeDisplayInfo.swift in Sources */,
C69F99242A9F1264004B1958 /* ClearListBackground.swift in Sources */,
C60319922C29775E00A77109 /* CoinRegexPattern.swift in Sources */,
C6456F0B27FF299800A517D5 /* UIView.swift in Sources */,
C6FFBADE2B833C3600CB442D /* ChatListChatRowView.swift in Sources */,
C6D3B9D92A6ECC9C0091B279 /* XMTPEntitiesTransformer.swift in Sources */,
Expand Down Expand Up @@ -10902,6 +10907,7 @@
C6102FC82B6A339A0098AF75 /* HomeWebAccountView.swift in Sources */,
C6B761E02BB3D78F00773943 /* SerializedWalletTransaction.swift in Sources */,
C6BF6BDD2B8F11CC006CC2BD /* TaskWithDeadline.swift in Sources */,
C60319932C29775E00A77109 /* CoinRegexPattern.swift in Sources */,
C6534A922BBFBA10008EEBB5 /* HomeExploreFollowersSectionView.swift in Sources */,
C61807BE2B19A0CB0032E543 /* Extension-String+Preview.swift in Sources */,
C621A4402BB10EFD00CB5CB9 /* QRScannerPreviewView.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ struct CoinRecord: Hashable, Comparable, CustomStringConvertible, Codable {
self.ticker = ticker
self.version = version
self.expandedTicker = expandedTicker
self.regexPattern = regexPattern ?? Constants.ETHRegexPattern
if let version = version {
self.regexPattern = regexPattern ?? CoinRegexPattern.ETH.regex
if let version = version {
self.isPrimaryChain = CoinRecord.primaryChainsMap[ticker] == version
} else {
self.isPrimaryChain = true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// CoinRegexPattern.swift
// domains-manager-ios
//
// Created by Oleg Kuplin on 24.06.2024.
//

import Foundation

enum CoinRegexPattern: String, CaseIterable {
case ETH
case SOL
case BTC
}

extension CoinRegexPattern {
var regex: String {
switch self {
case .ETH: "^0x[a-fA-F0-9]{40}$"
case .SOL: "^[1-9A-HJ-NP-Za-km-z]{32,44}$"
case .BTC: "^bc1[ac-hj-np-z02-9]{6,87}$|^[13][a-km-zA-HJ-NP-Z1-9]{25,39}$"
}
}

func isStringMatchingRegex(_ string: String) -> Bool {
string.isMatchingRegexPattern(regex)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ extension MockEntitiesFabric {
}

static func mockReceiver() -> SendCryptoAsset.AssetReceiver {
.init(walletAddress: Wallet.mockEntities()[1].address)
.init(walletAddress: Wallet.mockEntities()[1].address,
regexPattern: .ETH)
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,15 @@ private extension SelectCryptoAssetToSendView {

switch token.blockchainType {
case .Ethereum, .Matic:
guard receiver.regexPattern == .ETH else { return nil }
return BalanceTokenToSend(token: token, address: receiver.walletAddress)
case .none:
/// As we don't currently support Base chain but MPC does
if token.chain == Constants.baseChainSymbol {
if token.symbol == receiver.regexPattern.rawValue,
token.parent == nil {
return BalanceTokenToSend(token: token, address: receiver.walletAddress)
} /// As we don't currently support Base chain but MPC does
else if token.chain == Constants.baseChainSymbol,
receiver.regexPattern == .ETH {
return BalanceTokenToSend(token: token, address: receiver.walletAddress)
}
return nil
Expand Down Expand Up @@ -117,10 +122,12 @@ private extension SelectCryptoAssetToSendView {

@ViewBuilder
func assetTypePickerView() -> some View {
VStack(alignment: .leading, spacing: 20) {
UDTabsPickerView(selectedTab: $selectedType,
tabs: SendCryptoAsset.AssetType.allCases)
HomeExploreSeparatorView()
if case .ETH = receiver.regexPattern {
VStack(alignment: .leading, spacing: 20) {
UDTabsPickerView(selectedTab: $selectedType,
tabs: SendCryptoAsset.AssetType.allCases)
HomeExploreSeparatorView()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,14 +194,14 @@ private extension SendCryptoAssetSelectReceiverView {

enum GlobalSearchResult {
case profiles([SearchDomainProfile])
case fullAddress(HexAddress)
case fullAddress(SendCryptoAsset.WalletAddressDetails)
}

func getCurrentGlobalSearchResult() -> GlobalSearchResult? {
if !globalProfiles.isEmpty {
return .profiles(globalProfiles)
} else if inputText.isValidAddress() {
return .fullAddress(inputText)
} else if let addressDetails = viewModel.getWalletAddressDetailsFor(address: inputText) {
return .fullAddress(addressDetails)
}
return nil
}
Expand All @@ -227,9 +227,9 @@ private extension SendCryptoAssetSelectReceiverView {
}

@ViewBuilder
func selectableGlobalAddressRowView(_ address: HexAddress) -> some View {
func selectableGlobalAddressRowView(_ addressDetails: SendCryptoAsset.WalletAddressDetails) -> some View {
selectableRowView({
UDListItemView(title: address.walletAddressTruncated,
UDListItemView(title: addressDetails.address.walletAddressTruncated,
titleColor: .foregroundDefault,
subtitle: nil,
subtitleStyle: .default,
Expand All @@ -241,8 +241,9 @@ private extension SendCryptoAssetSelectReceiverView {
bordered: true),
rightViewStyle: nil)
}, callback: {
logAnalytic(event: .searchWalletAddressPressed, parameters: [.wallet : address])
viewModel.handleAction(.globalWalletAddressSelected(address))
logAnalytic(event: .searchWalletAddressPressed, parameters: [.wallet : addressDetails.address,
.coin : addressDetails.regexPattern.rawValue])
viewModel.handleAction(.globalWalletAddressSelected(addressDetails))
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ extension SendCryptoAsset {
case userWalletSelected(WalletEntity)
case followingDomainSelected(DomainProfileDisplayInfo)
case globalProfileSelected(SearchDomainProfile)
case globalWalletAddressSelected(HexAddress)
case globalWalletAddressSelected(SendCryptoAsset.WalletAddressDetails)

case userTokenToSendSelected(SelectTokenAmountToSendData)
case userTokenValueSelected(SendTokenAssetData)
Expand All @@ -56,6 +56,7 @@ extension SendCryptoAsset {
extension SendCryptoAsset {
struct AssetReceiver: Hashable {
let walletAddress: String
let regexPattern: CoinRegexPattern
let domainName: DomainName?
private(set) var pfpURL: URL?
private var records: [String: String] = [:]
Expand Down Expand Up @@ -88,13 +89,15 @@ extension SendCryptoAsset {
self.walletAddress = wallet.address
self.domainName = wallet.rrDomain?.name
self.pfpURL = wallet.rrDomain?.pfpSource.value.asURL
self.regexPattern = .ETH
try await loadRecords()
}

init(followingDomain profile: DomainProfileDisplayInfo) async throws {
self.walletAddress = profile.ownerWallet
self.domainName = profile.domainName
self.pfpURL = profile.pfpURL
self.regexPattern = .ETH
try await loadRecords()
}

Expand All @@ -106,13 +109,16 @@ extension SendCryptoAsset {
self.walletAddress = walletAddress
self.domainName = globalProfile.name
self.pfpURL = globalProfile.imagePath?.asURL
self.regexPattern = .ETH
try await loadRecords()
}

init(walletAddress: HexAddress) {
init(walletAddress: HexAddress,
regexPattern: CoinRegexPattern) {
self.walletAddress = walletAddress
self.domainName = nil
self.pfpURL = nil
self.regexPattern = regexPattern
}

mutating private func loadRecords() async throws {
Expand Down Expand Up @@ -194,6 +200,13 @@ extension SendCryptoAsset {
}
}

extension SendCryptoAsset {
struct WalletAddressDetails {
let address: String
let regexPattern: CoinRegexPattern
}
}

extension SendCryptoAsset {
enum TokenAssetAmountInputType {
case usdAmount
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ final class SendCryptoAssetViewModel: ObservableObject {
try await SendCryptoAsset.AssetReceiver(globalProfile: profile)
}
navPath.append(.selectAssetToSend(receiver))
case .globalWalletAddressSelected(let address):
navPath.append(.selectAssetToSend(.init(walletAddress: address)))
case .globalWalletAddressSelected(let addressDetails):
navPath.append(.selectAssetToSend(.init(walletAddress: addressDetails.address,
regexPattern: addressDetails.regexPattern)))

// Send crypto
case .userTokenToSendSelected(let data):
Expand Down Expand Up @@ -174,4 +175,17 @@ final class SendCryptoAssetViewModel: ObservableObject {
}
}

func getWalletAddressDetailsFor(address: String) -> SendCryptoAsset.WalletAddressDetails? {
let availableCoins: [CoinRegexPattern]
if sourceWallet.displayInfo.source == .mpc {
availableCoins = CoinRegexPattern.allCases
} else {
availableCoins = [.ETH]
}

if let coin = availableCoins.first(where: { $0.isStringMatchingRegex(address) }) {
return .init(address: address, regexPattern: coin)
}
return nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,27 @@ private extension SendCryptoQRWalletAddressScannerView {
isTorchAvailable = capabilities.isTorchAvailable
}
case .didRecognizeQRCodes(let codes):
if let walletAddress = codes.first(where: { $0.isValidAddress() }) {
didRecognizeWalletAddress(walletAddress)
for code in codes {
if let addressDetails = viewModel.getWalletAddressDetailsFor(address: code) {
didRecognizeWalletAddress(addressDetails)
return
}
}
case .didFailToSetupCaptureSession:
return
}
}

func didRecognizeWalletAddress(_ walletAddress: HexAddress) {
func didRecognizeWalletAddress(_ addressDetails: SendCryptoAsset.WalletAddressDetails) {
guard !didRecognizeAddress else { return }

didRecognizeAddress = true
logAnalytic(event: .didRecognizeQRWalletAddress, parameters: [.wallet: walletAddress])
logAnalytic(event: .didRecognizeQRWalletAddress, parameters: [.wallet: addressDetails.address,
.coin: addressDetails.regexPattern.rawValue])
dismiss()
Vibration.success.vibrate()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
viewModel.handleAction(.globalWalletAddressSelected(walletAddress))
viewModel.handleAction(.globalWalletAddressSelected(addressDetails))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,6 @@ private extension SendCryptoReceiverInfoTitleView {
}

#Preview {
SendCryptoReceiverInfoTitleView(receiver: .init(walletAddress: "0x1234567890abcdef1234567890abcdef12345678"))
SendCryptoReceiverInfoTitleView(receiver: .init(walletAddress: "0x1234567890abcdef1234567890abcdef12345678",
regexPattern: .ETH))
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ struct Constants {

static let distanceFromButtonToKeyboard: CGFloat = 16
static let scrollableContentBottomOffset: CGFloat = 32
static let ETHRegexPattern = "^0x[a-fA-F0-9]{40}$"
static let UnstoppableSupportMail = "support@unstoppabledomains.com"
static let UnstoppableTwitterName = "unstoppableweb"
static let UnstoppableGroupIdentifier = "group.unstoppabledomains.manager.extensions"
Expand Down

0 comments on commit e93f88c

Please sign in to comment.