Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }
Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ fun List<Chain>.filter(query: String): List<Chain> {
}
}

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
Expand Down Expand Up @@ -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,
)
}
Expand All @@ -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,
)
Expand Down
10 changes: 10 additions & 0 deletions ios/Packages/GemstonePrimitives/Sources/Chain+Checksum.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import Components
import Foundation
import GemstonePrimitives
import Localization
import Primitives
import Style
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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)?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}