Skip to content

Commit

Permalink
Add spam addresses manager
Browse files Browse the repository at this point in the history
  • Loading branch information
esen committed Dec 30, 2024
1 parent 457c4e1 commit 8e1c643
Show file tree
Hide file tree
Showing 28 changed files with 615 additions and 103 deletions.
44 changes: 43 additions & 1 deletion UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ public extension Kit {
accountStatePublisher.asObservable()
}

var allTransactionsObservable: Observable<([FullTransaction], Bool)> {
allTransactionsPublisher.asObservable()
}

func transactionSingle(hash: Data) -> Single<FullTransaction> {
Single<FullTransaction>.create { [weak self] observer in
guard let strongSelf = self else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ class Eip20Adapter: BaseEvmAdapter {
private let contractAddress: EvmKit.Address
private let transactionConverter: EvmTransactionConverter

init(evmKitWrapper: EvmKitWrapper, contractAddress: String, wallet: Wallet, baseToken: Token, coinManager: CoinManager, evmLabelManager: EvmLabelManager) throws {
init(evmKitWrapper: EvmKitWrapper, contractAddress: String, wallet: Wallet, baseToken: Token, coinManager: CoinManager, evmLabelManager: EvmLabelManager, spamAddressManager: SpamAddressManager) throws {
let address = try EvmKit.Address(hex: contractAddress)
eip20Kit = try Eip20Kit.Kit.instance(evmKit: evmKitWrapper.evmKit, contractAddress: address)
self.contractAddress = address

transactionConverter = EvmTransactionConverter(source: wallet.transactionSource, baseToken: baseToken, coinManager: coinManager, evmKitWrapper: evmKitWrapper, evmLabelManager: evmLabelManager)
transactionConverter = EvmTransactionConverter(
source: wallet.transactionSource, baseToken: baseToken, coinManager: coinManager, blockchainType: evmKitWrapper.blockchainType,
userAddress: evmKitWrapper.evmKit.address, evmLabelManager: evmLabelManager, spamAddressManager: spamAddressManager
)

super.init(evmKitWrapper: evmKitWrapper, decimals: wallet.decimals)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,23 @@ import UniswapKit

class EvmTransactionConverter {
private let coinManager: CoinManager
private let evmKitWrapper: EvmKitWrapper
private let blockchainType: BlockchainType
private let userAddress: EvmKit.Address
private let evmLabelManager: EvmLabelManager
private let spamAddressManager: SpamAddressManager
private let source: TransactionSource
private let baseToken: MarketKit.Token

init(source: TransactionSource, baseToken: MarketKit.Token, coinManager: CoinManager, evmKitWrapper: EvmKitWrapper, evmLabelManager: EvmLabelManager) {
init(source: TransactionSource, baseToken: MarketKit.Token, coinManager: CoinManager, blockchainType: BlockchainType, userAddress: EvmKit.Address, evmLabelManager: EvmLabelManager, spamAddressManager: SpamAddressManager) {
self.coinManager = coinManager
self.evmKitWrapper = evmKitWrapper
self.blockchainType = blockchainType
self.userAddress = userAddress
self.evmLabelManager = evmLabelManager
self.spamAddressManager = spamAddressManager
self.source = source
self.baseToken = baseToken
}

private var evmKit: EvmKit.Kit {
evmKitWrapper.evmKit
}

private func convertAmount(amount: BigUInt, decimals: Int, sign: FloatingPointSign) -> Decimal {
guard let significand = Decimal(string: amount.description), significand != 0 else {
return 0
Expand All @@ -40,7 +40,7 @@ class EvmTransactionConverter {
}

private func eip20Value(tokenAddress: EvmKit.Address, value: BigUInt, sign: FloatingPointSign, tokenInfo: Eip20Kit.TokenInfo?) -> AppValue {
let query = TokenQuery(blockchainType: evmKitWrapper.blockchainType, tokenType: .eip20(address: tokenAddress.hex))
let query = TokenQuery(blockchainType: blockchainType, tokenType: .eip20(address: tokenAddress.hex))

if let token = try? coinManager.token(query: query) {
let value = convertAmount(amount: value, decimals: token.decimals, sign: sign)
Expand Down Expand Up @@ -181,6 +181,7 @@ class EvmTransactionConverter {
extension EvmTransactionConverter {
func transactionRecord(fromTransaction fullTransaction: FullTransaction) -> EvmTransactionRecord {
let transaction = fullTransaction.transaction
let spam = spamAddressManager.isSpam(transactionHash: transaction.hash)

switch fullTransaction.decoration {
case is ContractCreationDecoration:
Expand All @@ -196,7 +197,8 @@ extension EvmTransactionConverter {
transaction: transaction,
baseToken: baseToken,
from: decoration.from.eip55,
value: baseAppValue(value: decoration.value, sign: .plus)
value: baseAppValue(value: decoration.value, sign: .plus),
spam: spam
)

case let decoration as OutgoingDecoration:
Expand Down Expand Up @@ -302,23 +304,21 @@ extension EvmTransactionConverter {
)

case let decoration as UnknownTransactionDecoration:
let address = evmKit.address

let internalTransactions = decoration.internalTransactions.filter { $0.to == address }
let internalTransactions = decoration.internalTransactions.filter { $0.to == userAddress }

let eip20Transfers = decoration.eventInstances.compactMap { $0 as? TransferEventInstance }
let incomingEip20Transfers = eip20Transfers.filter { $0.to == address && $0.from != address }
let outgoingEip20Transfers = eip20Transfers.filter { $0.from == address }
let incomingEip20Transfers = eip20Transfers.filter { $0.to == userAddress && $0.from != userAddress }
let outgoingEip20Transfers = eip20Transfers.filter { $0.from == userAddress }

let eip721Transfers = decoration.eventInstances.compactMap { $0 as? Eip721TransferEventInstance }
let incomingEip721Transfers = eip721Transfers.filter { $0.to == address && $0.from != address }
let outgoingEip721Transfers = eip721Transfers.filter { $0.from == address }
let incomingEip721Transfers = eip721Transfers.filter { $0.to == userAddress && $0.from != userAddress }
let outgoingEip721Transfers = eip721Transfers.filter { $0.from == userAddress }

let eip1155Transfers = decoration.eventInstances.compactMap { $0 as? Eip1155TransferEventInstance }
let incomingEip1155Transfers = eip1155Transfers.filter { $0.to == address && $0.from != address }
let outgoingEip1155Transfers = eip1155Transfers.filter { $0.from == address }
let incomingEip1155Transfers = eip1155Transfers.filter { $0.to == userAddress && $0.from != userAddress }
let outgoingEip1155Transfers = eip1155Transfers.filter { $0.from == userAddress }

if transaction.from == address, let contractAddress = transaction.to, let value = transaction.value {
if transaction.from == userAddress, let contractAddress = transaction.to, let value = transaction.value {
return ContractCallTransactionRecord(
source: source,
transaction: transaction,
Expand All @@ -330,15 +330,16 @@ extension EvmTransactionConverter {
outgoingEvents: transferEvents(contractAddress: contractAddress, value: value) + transferEvents(outgoingEip20Transfers: outgoingEip20Transfers) +
transferEvents(outgoingEip721Transfers: outgoingEip721Transfers) + transferEvents(outgoingEip1155Transfers: outgoingEip1155Transfers)
)
} else if transaction.from != address, transaction.to != address {
} else if transaction.from != userAddress, transaction.to != userAddress {
return ExternalContractCallTransactionRecord(
source: source,
transaction: transaction,
baseToken: baseToken,
incomingEvents: transferEvents(internalTransactions: internalTransactions) + transferEvents(incomingEip20Transfers: incomingEip20Transfers) +
transferEvents(incomingEip721Transfers: incomingEip721Transfers) + transferEvents(incomingEip1155Transfers: incomingEip1155Transfers),
outgoingEvents: transferEvents(outgoingEip20Transfers: outgoingEip20Transfers) +
transferEvents(outgoingEip721Transfers: outgoingEip721Transfers) + transferEvents(outgoingEip1155Transfers: outgoingEip1155Transfers)
transferEvents(outgoingEip721Transfers: outgoingEip721Transfers) + transferEvents(outgoingEip1155Transfers: outgoingEip1155Transfers),
spam: spam
)
}

Expand All @@ -349,7 +350,7 @@ extension EvmTransactionConverter {
source: source,
transaction: transaction,
baseToken: baseToken,
ownTransaction: transaction.from == evmKit.address
ownTransaction: transaction.from == userAddress
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ class EvmTransactionsAdapter: BaseEvmAdapter {
private let evmTransactionSource: EvmKit.TransactionSource
private let transactionConverter: EvmTransactionConverter

init(evmKitWrapper: EvmKitWrapper, source: TransactionSource, baseToken: MarketKit.Token, evmTransactionSource: EvmKit.TransactionSource, coinManager: CoinManager, evmLabelManager: EvmLabelManager) {
init(evmKitWrapper: EvmKitWrapper, source: TransactionSource, baseToken: MarketKit.Token, evmTransactionSource: EvmKit.TransactionSource, coinManager: CoinManager, evmLabelManager: EvmLabelManager, spamAddressManager: SpamAddressManager) {
self.evmTransactionSource = evmTransactionSource
transactionConverter = EvmTransactionConverter(source: source, baseToken: baseToken, coinManager: coinManager, evmKitWrapper: evmKitWrapper, evmLabelManager: evmLabelManager)
transactionConverter = EvmTransactionConverter(source: source, baseToken: baseToken, coinManager: coinManager, blockchainType: evmKitWrapper.blockchainType, userAddress: evmKitWrapper.evmKit.address, evmLabelManager: evmLabelManager, spamAddressManager: spamAddressManager)

super.init(evmKitWrapper: evmKitWrapper, decimals: EvmAdapter.decimals)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation
import MarketKit
import RxCocoa
import RxRelay
import RxSwift

protocol IAddressSecurityCheckerItem: AnyObject {
func handle(address: Address) -> Single<AddressSecurityCheckerChain.SecurityCheckResult>
}

class AddressSecurityCheckerChain {
private let disposeBag = DisposeBag()
private var handlers = [IAddressSecurityCheckerItem]()
}

extension AddressSecurityCheckerChain {
@discardableResult func append(handlers: [IAddressSecurityCheckerItem]) -> Self {
self.handlers.append(contentsOf: handlers)
return self
}

@discardableResult func append(handler: IAddressSecurityCheckerItem) -> Self {
handlers.append(handler)
return self
}

func handle(address: Address) -> Single<[SecurityCheckResult]> {
Single.zip(handlers.map { handler -> Single<SecurityCheckResult> in
handler.handle(address: address)
})
}
}

extension AddressSecurityCheckerChain {
public enum SecurityCheckResult {
case valid
case spam(transactionHash: String)
case sanctioned(description: String)

public var description: String? {
switch self {
case .valid: return nil
case let .spam(transactionHash): return "Possibly phishing address. Transaction hash: \(transactionHash)"
case let .sanctioned(description): return description
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import RxSwift

class SpamAddressDetector {
private let spamAddressManager: SpamAddressManager

init() {
spamAddressManager = App.shared.spamAddressManager
}
}

extension SpamAddressDetector: IAddressSecurityCheckerItem {
func handle(address: Address) -> Single<AddressSecurityCheckerChain.SecurityCheckResult> {
let result: AddressSecurityCheckerChain.SecurityCheckResult

let spamAddress = spamAddressManager.find(address: address.raw.uppercased())
if let spamAddress {
result = .spam(transactionHash: spamAddress.transactionHash.hs.hexString)
} else {
result = .valid
}

return Single.just(result)
}
}
14 changes: 10 additions & 4 deletions UnstoppableWallet/UnstoppableWallet/Core/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class App {
let statManager: StatManager

let tonConnectManager: TonConnectManager
let spamAddressManager: SpamAddressManager

let purchaseManager: PurchaseManager

Expand Down Expand Up @@ -200,8 +201,11 @@ class App {
let restoreStateStorage = RestoreStateStorage(dbPool: dbPool)
restoreStateManager = RestoreStateManager(storage: restoreStateStorage)

let spamAddressStorage = try SpamAddressStorage(dbPool: dbPool)
spamAddressManager = SpamAddressManager(storage: spamAddressStorage, marketKit: marketKit, coinManager: coinManager)

let evmAccountManagerFactory = EvmAccountManagerFactory(accountManager: accountManager, walletManager: walletManager, restoreStateManager: restoreStateManager, marketKit: marketKit)
evmBlockchainManager = EvmBlockchainManager(syncSourceManager: evmSyncSourceManager, testNetManager: testNetManager, marketKit: marketKit, accountManagerFactory: evmAccountManagerFactory)
evmBlockchainManager = EvmBlockchainManager(syncSourceManager: evmSyncSourceManager, testNetManager: testNetManager, marketKit: marketKit, accountManagerFactory: evmAccountManagerFactory, spamAddressManager: spamAddressManager)

let hsLabelProvider = HsLabelProvider(networkManager: networkManager)
let evmLabelStorage = EvmLabelStorage(dbPool: dbPool)
Expand Down Expand Up @@ -266,7 +270,8 @@ class App {
tonKitManager: tonKitManager,
restoreSettingsManager: restoreSettingsManager,
coinManager: coinManager,
evmLabelManager: evmLabelManager
evmLabelManager: evmLabelManager,
spamAddressManager: spamAddressManager
)
adapterManager = AdapterManager(
adapterFactory: adapterFactory,
Expand Down Expand Up @@ -342,11 +347,12 @@ class App {
statManager: statManager,
walletConnectSocketConnectionService: walletConnectSocketConnectionService,
nftMetadataSyncer: nftMetadataSyncer,
tonKitManager: tonKitManager
tonKitManager: tonKitManager,
spamAddressManager: spamAddressManager
)
}

func newSendEnabled(wallet: Wallet) -> Bool {
func newSendEnabled(wallet _: Wallet) -> Bool {
true
// switch wallet.token.blockchainType {
// case .ton: return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ class AdapterFactory {
private let restoreSettingsManager: RestoreSettingsManager
private let coinManager: CoinManager
private let evmLabelManager: EvmLabelManager

private let spamAddressManager: SpamAddressManager
init(evmBlockchainManager: EvmBlockchainManager, evmSyncSourceManager: EvmSyncSourceManager, binanceKitManager: BinanceKitManager,
btcBlockchainManager: BtcBlockchainManager, tronKitManager: TronKitManager, tonKitManager: TonKitManager,
restoreSettingsManager: RestoreSettingsManager, coinManager: CoinManager, evmLabelManager: EvmLabelManager)
restoreSettingsManager: RestoreSettingsManager, coinManager: CoinManager, evmLabelManager: EvmLabelManager, spamAddressManager: SpamAddressManager)
{
self.evmBlockchainManager = evmBlockchainManager
self.evmSyncSourceManager = evmSyncSourceManager
Expand All @@ -28,6 +28,7 @@ class AdapterFactory {
self.restoreSettingsManager = restoreSettingsManager
self.coinManager = coinManager
self.evmLabelManager = evmLabelManager
self.spamAddressManager = spamAddressManager
}

private func evmAdapter(wallet: Wallet) -> IAdapter? {
Expand All @@ -52,7 +53,7 @@ class AdapterFactory {
return nil
}

return try? Eip20Adapter(evmKitWrapper: evmKitWrapper, contractAddress: address, wallet: wallet, baseToken: baseToken, coinManager: coinManager, evmLabelManager: evmLabelManager)
return try? Eip20Adapter(evmKitWrapper: evmKitWrapper, contractAddress: address, wallet: wallet, baseToken: baseToken, coinManager: coinManager, evmLabelManager: evmLabelManager, spamAddressManager: spamAddressManager)
}

private func tronAdapter(wallet: Wallet) -> IAdapter? {
Expand Down Expand Up @@ -80,7 +81,7 @@ extension AdapterFactory {
let baseToken = evmBlockchainManager.baseToken(blockchainType: blockchainType)
{
let syncSource = evmSyncSourceManager.syncSource(blockchainType: blockchainType)
return EvmTransactionsAdapter(evmKitWrapper: evmKitWrapper, source: transactionSource, baseToken: baseToken, evmTransactionSource: syncSource.transactionSource, coinManager: coinManager, evmLabelManager: evmLabelManager)
return EvmTransactionsAdapter(evmKitWrapper: evmKitWrapper, source: transactionSource, baseToken: baseToken, evmTransactionSource: syncSource.transactionSource, coinManager: coinManager, evmLabelManager: evmLabelManager, spamAddressManager: spamAddressManager)
}

return nil
Expand Down Expand Up @@ -154,11 +155,13 @@ extension AdapterFactory {
if let tonKit = try? tonKitManager.tonKit(account: wallet.account) {
return TonAdapter(tonKit: tonKit)
}

case let (.jetton(address), .ton):
do {
let tonKit = try tonKitManager.tonKit(account: wallet.account)
return try JettonAdapter(tonKit: tonKit, address: address)
} catch {}

default: ()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import MarketKit

enum AddressSecurityCheckerFactory {
static func securityCheckerChainHandlers(blockchainType: BlockchainType) -> [IAddressSecurityCheckerItem] {
switch blockchainType {
case .ethereum, .gnosis, .fantom, .polygon, .arbitrumOne, .avalanche, .optimism, .binanceSmartChain, .base:
let evmAddressSecurityCheckerItem = SpamAddressDetector()

var handlers = [IAddressSecurityCheckerItem]()
handlers.append(evmAddressSecurityCheckerItem)

return handlers
default:
return []
}
}

static func securityCheckerChain(blockchainType: BlockchainType?) -> AddressSecurityCheckerChain {
if let blockchainType {
return AddressSecurityCheckerChain().append(handlers: securityCheckerChainHandlers(blockchainType: blockchainType))
}

var handlers = [IAddressSecurityCheckerItem]()
for blockchainType in BlockchainType.supported {
handlers.append(contentsOf: securityCheckerChainHandlers(blockchainType: blockchainType))
}

return AddressSecurityCheckerChain().append(handlers: handlers)
}
}
Loading

0 comments on commit 8e1c643

Please sign in to comment.