diff --git a/android/features/recipient/viewmodels/src/main/kotlin/com/gemwallet/android/features/recipient/viewmodel/RecipientViewModel.kt b/android/features/recipient/viewmodels/src/main/kotlin/com/gemwallet/android/features/recipient/viewmodel/RecipientViewModel.kt index 53ded39f2..edd44a28d 100644 --- a/android/features/recipient/viewmodels/src/main/kotlin/com/gemwallet/android/features/recipient/viewmodel/RecipientViewModel.kt +++ b/android/features/recipient/viewmodels/src/main/kotlin/com/gemwallet/android/features/recipient/viewmodel/RecipientViewModel.kt @@ -7,6 +7,7 @@ import com.gemwallet.android.application.recipient.coordinators.GetRecipientAsse import com.gemwallet.android.application.recipient.coordinators.GetWallets import com.gemwallet.android.application.session.coordinators.GetSession import com.gemwallet.android.blockchain.operators.ValidateAddressOperator +import com.gemwallet.android.ext.checksumAddress import com.gemwallet.android.cases.nft.GetAssetNft import com.gemwallet.android.domains.asset.chain import com.gemwallet.android.ext.asset @@ -161,6 +162,9 @@ class RecipientViewModel @Inject constructor( amountAction: AmountTransactionAction, confirmAction: ConfirmTransactionAction, ) { + val destination = destination.copy( + address = type.assetInfo.asset.chain.checksumAddress(destination.address), + ) val validation = validateDestination(type.assetInfo.asset.chain, destination) if (validation != RecipientError.None) { addressError.update { validation } @@ -175,8 +179,7 @@ class RecipientViewModel @Inject constructor( } fun onAddress(input: String, record: NameRecord?) { - _address.value = input - nameRecord.value = record + setAddress(input, record) } fun onMemo(input: String) { @@ -190,15 +193,15 @@ class RecipientViewModel @Inject constructor( } catch (_: Throwable) { null } - val address = paymentWrapper.address - val memo = paymentWrapper.memo val assetInfo = type.assetInfo - + val address = assetInfo.asset.chain.checksumAddress(paymentWrapper.address) + val memo = paymentWrapper.memo val owner = assetInfo.owner + if ( - address.isNotEmpty() + owner != null + && address.isNotEmpty() && amount != null - && owner != null && (assetInfo.asset.chain.isMemoSupport() || !memo.isNullOrEmpty()) ) { val params = ConfirmParams.Builder(assetInfo.asset, owner, amount, false).transfer(DestinationAddress(address), memo) @@ -209,16 +212,28 @@ class RecipientViewModel @Inject constructor( when (field) { QrScanField.None -> Unit QrScanField.Address -> { - _address.value = address.ifEmpty { data } + setAddress(address.ifEmpty { data }) _memo.value = memo?.ifEmpty { _memo.value } ?: _memo.value } QrScanField.Memo -> { - _address.value = address.ifEmpty { _address.value } _memo.value = paymentWrapper.memo ?: data } } } + private fun setAddress(input: String, record: NameRecord? = null) { + val chain = when (val state = state.value) { + is RecipientState.Ready -> state.type.assetInfo.asset.chain + RecipientState.Loading -> null + } + _address.value = chain?.checksumAddress(input) ?: input + nameRecord.value = if (chain != null && record != null) { + record.copy(address = chain.checksumAddress(record.address)) + } else { + record + } + } + private fun onNftConfirm(nftAsset: NFTAsset, destination: DestinationAddress, confirmAction: ConfirmTransactionAction) { val params = ConfirmParams.NftParams( asset = nftAsset.chain.asset(), diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/Chain.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/Chain.kt index ed7f955dc..e7e28927b 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/Chain.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/Chain.kt @@ -141,6 +141,10 @@ fun List.filter(query: String): List { } } +fun Chain.checksumAddress(address: String): String = + runCatching { uniffi.gemstone.GemChainAddress(address, string).address() } + .getOrDefault(address) + fun Chain.toChainType(): ChainType { return when (this) { Chain.HyperCore -> ChainType.HyperCore diff --git a/core b/core index 9eb209cc6..1f9a1f5f9 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 9eb209cc6d568b1882d668a56e27afae9d9c8df6 +Subproject commit 1f9a1f5f9ba5fa84c6de13b5f1586eefaf5c17c8 diff --git a/ios/Features/Contacts/Sources/ViewModels/ManageContactAddressViewModel.swift b/ios/Features/Contacts/Sources/ViewModels/ManageContactAddressViewModel.swift index d903f6de0..063fa8aa3 100644 --- a/ios/Features/Contacts/Sources/ViewModels/ManageContactAddressViewModel.swift +++ b/ios/Features/Contacts/Sources/ViewModels/ManageContactAddressViewModel.swift @@ -103,7 +103,7 @@ public final class ManageContactAddressViewModel { ContactAddress.new( contactId: contactId, chain: chain, - address: chain.checksumAddress(addressInputModel.resolvedAddress), + address: addressInputModel.address, memo: memo.isEmpty ? nil : memo, ) } diff --git a/ios/Features/Transfer/Sources/ViewModels/RecipientSceneViewModel.swift b/ios/Features/Transfer/Sources/ViewModels/RecipientSceneViewModel.swift index 62a8f4423..71d8933f6 100644 --- a/ios/Features/Transfer/Sources/ViewModels/RecipientSceneViewModel.swift +++ b/ios/Features/Transfer/Sources/ViewModels/RecipientSceneViewModel.swift @@ -129,7 +129,7 @@ extension RecipientSceneViewModel { handle( recipientData: makeRecipientData( name: addressInputModel.nameResolveState.result, - address: addressInputModel.text, + address: addressInputModel.address, memo: memo, amount: amount.isEmpty ? .none : amount, ), @@ -171,15 +171,8 @@ extension RecipientSceneViewModel { extension RecipientSceneViewModel { private func makeRecipientData(name: NameRecord?, address: String, memo: String?, amount: String?) -> RecipientData { - let recipient: Recipient = { - if let result = name { - return Recipient(name: result.name, address: result.address, memo: memo) - } - return Recipient(name: .none, address: address, memo: memo) - }() - - return RecipientData( - recipient: recipient, + RecipientData( + recipient: Recipient(name: name?.name, address: address, memo: memo), amount: amount, ) } @@ -200,7 +193,7 @@ extension RecipientSceneViewModel { let payment = try PaymentURLDecoder.decode(string) return PaymentScanResult( - address: payment.address, + address: asset.chain.checksumAddress(payment.address), amount: payment.amount, memo: payment.memo, ) diff --git a/ios/Packages/GemstonePrimitives/Sources/Chain+Checksum.swift b/ios/Packages/GemstonePrimitives/Sources/Chain+Checksum.swift new file mode 100644 index 000000000..10e4fa6f4 --- /dev/null +++ b/ios/Packages/GemstonePrimitives/Sources/Chain+Checksum.swift @@ -0,0 +1,10 @@ +// Copyright (c). Gem Wallet. All rights reserved. + +import Gemstone +import Primitives + +public extension Primitives.Chain { + func checksumAddress(_ address: String) -> String { + (try? GemChainAddress(address: address, chain: rawValue))?.address() ?? address + } +} diff --git a/ios/Packages/GemstonePrimitives/Tests/GemstonePrimitivesTests/Chain+ChecksumTests.swift b/ios/Packages/GemstonePrimitives/Tests/GemstonePrimitivesTests/Chain+ChecksumTests.swift new file mode 100644 index 000000000..b7096037f --- /dev/null +++ b/ios/Packages/GemstonePrimitives/Tests/GemstonePrimitivesTests/Chain+ChecksumTests.swift @@ -0,0 +1,20 @@ +// Copyright (c). Gem Wallet. All rights reserved. + +import GemstonePrimitives +import Primitives +import PrimitivesTestKit +import Testing + +struct Chain_ChecksumTests { + @Test + func testChecksumAddress() { + let bitcoinAddress = "bc1qr6f065nr70x4gl6ja9lm5wfj7xkhdv2sq04q23" + let evmAddress = "0xd41fdb03ba84762dd66a0af1a6c8540ff1ba5dfb" + let evmChecksumAddress = "0xD41FDb03Ba84762dD66a0af1a6C8540FF1ba5dfb" + + #expect(Chain.mock(.ethereum).checksumAddress(evmAddress) == evmChecksumAddress) + #expect(Chain.mock(.smartChain).checksumAddress(evmAddress) == evmChecksumAddress) + #expect(Chain.mock(.ethereum).checksumAddress(evmChecksumAddress) == evmChecksumAddress) + #expect(Chain.mock(.bitcoin).checksumAddress(bitcoinAddress) == bitcoinAddress) + } +} diff --git a/ios/Packages/PrimitivesComponents/Sources/ViewModels/AddressInputViewModel.swift b/ios/Packages/PrimitivesComponents/Sources/ViewModels/AddressInputViewModel.swift index e50ca741c..6a2b12066 100644 --- a/ios/Packages/PrimitivesComponents/Sources/ViewModels/AddressInputViewModel.swift +++ b/ios/Packages/PrimitivesComponents/Sources/ViewModels/AddressInputViewModel.swift @@ -2,6 +2,7 @@ import Components import Foundation +import GemstonePrimitives import Localization import Primitives import Style @@ -37,7 +38,7 @@ public final class AddressInputViewModel { public var text: String { get { inputModel.text } - set { inputModel.text = newValue } + set { inputModel.text = chain.checksumAddress(newValue) } } public var nameResolveState: NameRecordState { @@ -52,11 +53,8 @@ public final class AddressInputViewModel { } } - public var resolvedAddress: String { - if let resolved = nameResolveState.result { - return resolved.address - } - return inputModel.text.trim() + public var address: String { + chain.checksumAddress(nameResolveState.result?.address ?? inputModel.text.trim()) } @discardableResult @@ -65,7 +63,7 @@ public final class AddressInputViewModel { } public func update(text: String) { - inputModel.update(text: text) + inputModel.update(text: chain.checksumAddress(text)) } public func update(error: (any Error)?) { diff --git a/ios/Packages/WalletCore/Sources/WalletCorePrimitives/Chain+WalletCorePrimitives.swift b/ios/Packages/WalletCore/Sources/WalletCorePrimitives/Chain+WalletCorePrimitives.swift index d969fcfac..a7633695b 100644 --- a/ios/Packages/WalletCore/Sources/WalletCorePrimitives/Chain+WalletCorePrimitives.swift +++ b/ios/Packages/WalletCore/Sources/WalletCorePrimitives/Chain+WalletCorePrimitives.swift @@ -65,11 +65,4 @@ public extension Chain { func isValidAddress(_ address: String) -> Bool { AnyAddress.isValid(string: address, coin: coinType) } - - func checksumAddress(_ address: String) -> String { - if let chain = EVMChain(rawValue: rawValue), let address = AnyAddress(string: address, coin: chain.chain.coinType) { - return address.description - } - return address - } } diff --git a/ios/Packages/WalletCore/Tests/WalletCorePrimitivesTests/Chain+WalletCorePrimitiveTests.swift b/ios/Packages/WalletCore/Tests/WalletCorePrimitivesTests/Chain+WalletCorePrimitiveTests.swift index 90c3825d1..01e7298ed 100644 --- a/ios/Packages/WalletCore/Tests/WalletCorePrimitivesTests/Chain+WalletCorePrimitiveTests.swift +++ b/ios/Packages/WalletCore/Tests/WalletCorePrimitivesTests/Chain+WalletCorePrimitiveTests.swift @@ -77,15 +77,4 @@ final class Chain_WalletCorePrimitiveTests { #expect(!Chain.mock(.ethereum).isValidAddress("0x123")) } - @Test - func testChecksumAddress() { - let bitocoinAddress = "bc1qr6f065nr70x4gl6ja9lm5wfj7xkhdv2sq04q23" - let evmAddress = "0xd41fdb03ba84762dd66a0af1a6c8540ff1ba5dfb" - let evmChecksumAddress = "0xD41FDb03Ba84762dD66a0af1a6C8540FF1ba5dfb" - - #expect(Chain.mock(.ethereum).checksumAddress(evmAddress) == evmChecksumAddress) - #expect(Chain.mock(.smartChain).checksumAddress(evmAddress) == evmChecksumAddress) - #expect(Chain.mock(.ethereum).checksumAddress(evmChecksumAddress) == evmChecksumAddress) - #expect(Chain.mock(.bitcoin).checksumAddress(bitocoinAddress) == bitocoinAddress) - } }