Merge pull request #887 from AlphaWallet/ens-compatibility

ENS compatibility
pull/893/head
James Sangalli 6 years ago committed by GitHub
commit 10d7a1baf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      AlphaWallet.xcodeproj/project.pbxproj
  2. 4
      AlphaWallet/Info.plist
  3. 10
      AlphaWallet/RPC/Commands/web3swift-pod/GetENSNameEncode.swift
  4. 2
      AlphaWallet/RPC/Commands/web3swift-pod/GetERC875Balance.swift
  5. 10
      AlphaWallet/Settings/Types/Constants.swift
  6. 94
      AlphaWallet/Tokens/Coordinators/GetENSOwnerCoordinator.swift
  7. 1
      AlphaWallet/Tokens/Coordinators/GetIsERC875ContractCoordinator.swift
  8. 90
      AlphaWallet/Transfer/ViewControllers/SendViewController.swift
  9. 18
      AlphaWallet/UI/AddressTextField.swift

@ -493,10 +493,12 @@
76F1D137B10D8309E513BBDD /* OrderSigningTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DE8ADA3176D0277EDF20 /* OrderSigningTests.swift */; };
76F1D13FC8A41AD967C59947 /* ClaimOrderCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DACA9404AD6740BEADBB /* ClaimOrderCoordinatorTests.swift */; };
76F1D5AF727A83205BBCF0EC /* OrderHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DADFD07E2941897FD2E1 /* OrderHandler.swift */; };
76F1D5ECC391A932C96CAC13 /* GetENSOwnerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DE7BEE799DDFB68D0F54 /* GetENSOwnerCoordinator.swift */; };
76F1D74912F5D8CDA72363BD /* GetContractInteractions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DF5CF4A922E6FFCB7B2A /* GetContractInteractions.swift */; };
76F1D7F08263A663C3A67926 /* GetIsERC721ContractCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D4F77311FBF3A442E4B5 /* GetIsERC721ContractCoordinator.swift */; };
76F1D850F4F2E968CF8D9C86 /* MonkeyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B138ABCF208C2C93000FC28A /* MonkeyTest.swift */; };
76F1D91659771C9EEA7B48DC /* CreateRedeem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DF80932454E9F58B7830 /* CreateRedeem.swift */; };
76F1D9BBB4ACAA00C8391172 /* GetENSNameEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DC4B9964504DA12D8D3C /* GetENSNameEncode.swift */; };
76F1DB9E1443DCFC36228B08 /* ClaimOrderCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D419EE36261E50ABAFAE /* ClaimOrderCoordinator.swift */; };
76F1DBCA8BAAA42BAEB14719 /* GetERC721BalanceCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D473FF303828D93C95EB /* GetERC721BalanceCoordinator.swift */; };
76F1DC92CDEB695115DBC47C /* UniversalLinkHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D96298E216CBFC3DD78B /* UniversalLinkHandlerTests.swift */; };
@ -1083,7 +1085,9 @@
76F1D96298E216CBFC3DD78B /* UniversalLinkHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalLinkHandlerTests.swift; sourceTree = "<group>"; };
76F1DACA9404AD6740BEADBB /* ClaimOrderCoordinatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaimOrderCoordinatorTests.swift; sourceTree = "<group>"; };
76F1DADFD07E2941897FD2E1 /* OrderHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderHandler.swift; sourceTree = "<group>"; };
76F1DC4B9964504DA12D8D3C /* GetENSNameEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetENSNameEncode.swift; sourceTree = "<group>"; };
76F1DCD54618349AC91C6DF8 /* UniversalLinkHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalLinkHandler.swift; sourceTree = "<group>"; };
76F1DE7BEE799DDFB68D0F54 /* GetENSOwnerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetENSOwnerCoordinator.swift; sourceTree = "<group>"; };
76F1DE8ADA3176D0277EDF20 /* OrderSigningTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderSigningTests.swift; sourceTree = "<group>"; };
76F1DF5CF4A922E6FFCB7B2A /* GetContractInteractions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetContractInteractions.swift; sourceTree = "<group>"; };
76F1DF80932454E9F58B7830 /* CreateRedeem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateRedeem.swift; sourceTree = "<group>"; };
@ -2163,6 +2167,7 @@
76F1DF5CF4A922E6FFCB7B2A /* GetContractInteractions.swift */,
76F1D473FF303828D93C95EB /* GetERC721BalanceCoordinator.swift */,
76F1D4F77311FBF3A442E4B5 /* GetIsERC721ContractCoordinator.swift */,
76F1DE7BEE799DDFB68D0F54 /* GetENSOwnerCoordinator.swift */,
5E7C7322ADC54452545C345A /* CallForAssetAttributeCoordinator.swift */,
);
path = Coordinators;
@ -2740,6 +2745,7 @@
5E7C7CA7D65743AEE3411F3A /* GetIsERC721Encode.swift */,
5E7C7DCB0BDDD30D10130AE7 /* GetIsERC875Encode.swift */,
5E7C72CD0C22247A6AF7C95E /* GetERC721BalanceEncode.swift */,
76F1DC4B9964504DA12D8D3C /* GetENSNameEncode.swift */,
5E7C787CA216AFED8023A35F /* CallForAssetAttribute.swift */,
);
path = "web3swift-pod";
@ -3951,6 +3957,8 @@
5E7C7131E338A806132D989B /* DateEntryField.swift in Sources */,
5E7C7E7AEF01B9D170228342 /* TimeEntryField.swift in Sources */,
5E7C7719948721FE9120B5B2 /* PeekOpenSeaNonFungibleTokenViewController.swift in Sources */,
76F1D5ECC391A932C96CAC13 /* GetENSOwnerCoordinator.swift in Sources */,
76F1D9BBB4ACAA00C8391172 /* GetENSNameEncode.swift in Sources */,
5E7C73CA81FB2CE9BCAFC992 /* CallForAssetAttribute.swift in Sources */,
5E7C77552A957D1B144D9209 /* CallForAssetAttributeCoordinator.swift in Sources */,
5E7C7C8BC4763CFDD3EE119D /* AssetAttributeFunctionCall.swift in Sources */,

@ -28,7 +28,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.26</string>
<string>1.3</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
@ -41,7 +41,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>236</string>
<string>237</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>

@ -0,0 +1,10 @@
//
// Created by James Sangalli on 8/11/18.
//
import Foundation
struct GetENSOwnerEncode {
let abi = "{\"constant\":true,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"}"
let name = "owner"
}

@ -1,5 +1,3 @@
// Copyright SIX DAY LLC. All rights reserved.
import Foundation
import TrustKeystore

@ -2,6 +2,7 @@
import Foundation
import BigInt
import web3swift
public struct Constants {
public static let keychainKeyPrefix = "alphawallet"
@ -20,7 +21,6 @@ public struct Constants {
// fee master
public static let paymentServer = "https://app.awallet.io:80/api/claimToken"
public static let paymentServerSupportsContractEndPoint = "https://app.awallet.io:80/api/checkContractIsSupportedForFreeTransfers"
public static let getTicketInfoFromServer = "https://app.awallet.io:80/api/ecrecoverAndGetTickets"
// social
public static let website = "https://community.alphawallet.com/c/feedback/ios-feedback"
@ -55,11 +55,15 @@ public struct Constants {
//OpenSea links for erc721 assets
public static let openseaAPI = "https://api.opensea.io/"
public static let openseaAPIKEY = "11ba1b4f0c4246aeb07b1f8e5a20525f" // X-API-KEY
public static let cryptoKittiesContractAddress = "0x06012c8cf97bead5deae237070f9587f8e7a266d"
public static let etherReceivedNotificationIdentifier = "etherReceivedNotificationIdentifier"
//ENS
public static let ENSRegistrarAddress = EthereumAddress("0x314159265dD8dbb310642f98f50C066173C1259b")!
public static let ENSRegistrarRopsten = EthereumAddress("0x112234455c3a32fd11230c42e7bccd4a84e02010")!
public static let ENSRegistrarRinkeby = EthereumAddress("0xe7410170f87102df0055eb195163a03b7f2bff4a")!
//Misc
public static let etherReceivedNotificationIdentifier = "etherReceivedNotificationIdentifier"
public static let legacy875Addresses = ["830e1650a87a754e37ca7ed76b700395a7c61614", "a66a3f08068174e8f005112a8b2c7a507a822335"]
}

@ -0,0 +1,94 @@
//
// Created by James Sangalli on 8/11/18.
//
import Foundation
import Result
import web3swift
import CryptoSwift
//https://github.com/ethereum/EIPs/blob/master/EIPS/eip-137.md
extension String {
public var nameHash: String {
var node = Array<UInt8>.init(repeating: 0x0, count: 32)
if !self.isEmpty {
node = self.split(separator: ".")
.map { Array($0.utf8).sha3(.keccak256) }
.reversed()
.reduce(node) { return ($0 + $1).sha3(.keccak256) }
}
return "0x" + node.toHexString()
}
}
class GetENSOwnerCoordinator {
private let config: Config
init(config: Config) {
self.config = config
}
func getENSOwner(
for input: String,
completion: @escaping (Result<EthereumAddress, AnyError>) -> Void
) {
//if already an address, send back the address
if let ethAddress = EthereumAddress(input) {
completion(.success(ethAddress))
return
}
//if it does not contain .eth, then it is not a valid ens name
if !input.contains(".") {
completion(.failure(AnyError(Web3Error(description: "Invalid ENS Name"))))
return
}
let node = input.nameHash
guard let webProvider = Web3HttpProvider(config.rpcURL, network: config.server.web3Network) else {
completion(.failure(AnyError(Web3Error(description: "Error creating web provider for: \(config.rpcURL) + \(config.server.web3Network)"))))
return
}
let web3 = web3swift.web3(provider: webProvider)
let function = GetENSOwnerEncode()
let contractAddress = getENSAddress(networkId: config.chainID)
guard let contractInstance = web3swift.web3.web3contract(web3: web3, abiString: "[\(function.abi)]", at: contractAddress, options: web3.options) else {
completion(.failure(AnyError(Web3Error(description: "Error creating web3swift contract instance to call \(function.name)()"))))
return
}
guard let promise = contractInstance.method(function.name, parameters: [node] as [AnyObject], options: nil) else {
completion(.failure(AnyError(Web3Error(description: "Error calling \(function.name)() on \(contractAddress.address)"))))
return
}
promise.callPromise(options: nil).done { result in
//if null address is returned (as 0) we count it as invalid
//this is because it is not assigned to an ENS and puts the user in danger of sending funds to null
if let owner = result["0"] as? EthereumAddress {
if owner.address == Constants.nullAddress {
completion(.failure(AnyError(Web3Error(description: "Null address returned"))))
} else {
completion(.success(owner))
}
} else {
completion(.failure(AnyError(Web3Error(description: "Error extracting result from \(contractAddress.address).\(function.name)()"))))
}
}.catch { error in
completion(.failure(AnyError(Web3Error(description: "Error extracting result from \(contractAddress.address).\(function.name)(): \(error)"))))
}
}
private func getENSAddress(networkId: Int) -> EthereumAddress {
switch networkId {
case 1: return Constants.ENSRegistrarAddress
case 3: return Constants.ENSRegistrarRopsten
case 4: return Constants.ENSRegistrarRinkeby
default: return Constants.ENSRegistrarAddress
}
}
}

@ -1,4 +1,3 @@
// Copyright SIX DAY LLC. All rights reserved.
import Foundation
import Result

@ -281,50 +281,56 @@ class SendViewController: UIViewController, CanScanQRCode, TokenVerifiableStatus
}
@objc func send() {
let addressString = targetAddressTextField.value
let amountString = amountTextField.ethCost
guard let address = Address(string: addressString) else {
return displayError(error: Errors.invalidAddress)
}
let parsedValue: BigInt? = {
switch transferType {
case .ether, .dapp:
return EtherNumberFormatter.full.number(from: amountString, units: .ether)
case .ERC20Token(let token):
return EtherNumberFormatter.full.number(from: amountString, decimals: token.decimals)
case .ERC875Token(let token):
return EtherNumberFormatter.full.number(from: amountString, decimals: token.decimals)
case .ERC875TokenOrder(let token):
return EtherNumberFormatter.full.number(from: amountString, decimals: token.decimals)
case .ERC721Token(let token):
return EtherNumberFormatter.full.number(from: amountString, decimals: token.decimals)
}
}()
guard let value = parsedValue else {
return displayError(error: SendInputErrors.wrongInput)
}
let input = targetAddressTextField.value
GetENSOwnerCoordinator(config: self.config).getENSOwner(for: input) { result in
if let addr = result.value {
let amountString = self.amountTextField.ethCost
guard let address = Address(string: addr.address) else {
return self.displayError(error: Errors.invalidAddress)
}
let parsedValue: BigInt? = {
switch self.transferType {
case .ether, .dapp:
return EtherNumberFormatter.full.number(from: amountString, units: .ether)
case .ERC20Token(let token):
return EtherNumberFormatter.full.number(from: amountString, decimals: token.decimals)
case .ERC875Token(let token):
return EtherNumberFormatter.full.number(from: amountString, decimals: token.decimals)
case .ERC875TokenOrder(let token):
return EtherNumberFormatter.full.number(from: amountString, decimals: token.decimals)
case .ERC721Token(let token):
return EtherNumberFormatter.full.number(from: amountString, decimals: token.decimals)
}
}()
guard let value = parsedValue else {
return self.displayError(error: SendInputErrors.wrongInput)
}
if case .ether = transferType, let balance = session.balance, balance.value < value {
return displayError(title: R.string.localizable.aSendBalanceInsufficient(), error: Errors.invalidAmount)
}
if case .ether = self.transferType, let balance = self.session.balance, balance.value < value {
return self.displayError(title: R.string.localizable.aSendBalanceInsufficient(), error: Errors.invalidAmount)
}
let transaction = UnconfirmedTransaction(
transferType: transferType,
value: value,
to: address,
data: data,
gasLimit: .none,
tokenId: .none,
gasPrice: gasPrice,
nonce: .none,
v: .none,
r: .none,
s: .none,
expiry: .none,
indices: .none,
tokenIds: .none
)
delegate?.didPressConfirm(transaction: transaction, transferType: transferType, in: self)
let transaction = UnconfirmedTransaction(
transferType: self.transferType,
value: value,
to: address,
data: self.data,
gasLimit: .none,
tokenId: .none,
gasPrice: self.gasPrice,
nonce: .none,
v: .none,
r: .none,
s: .none,
expiry: .none,
indices: .none,
tokenIds: .none
)
self.delegate?.didPressConfirm(transaction: transaction, transferType: self.transferType, in: self)
} else {
return self.displayError(error: Errors.invalidAddress)
}
}
}
@objc func copyAddress() {

@ -111,13 +111,19 @@ class AddressTextField: UIControl {
delegate?.displayError(error: SendInputErrors.emptyClipBoard, for: self)
return
}
guard CryptoAddressValidator.isValidAddress(value) else {
delegate?.displayError(error: Errors.invalidAddress, for: self)
return
//if address is pasted, the GetENSOwnerCoordinator will simply return it back in EthereumAddress format
GetENSOwnerCoordinator(config: Config()).getENSOwner(for: value) { result in
if let address = result.value {
guard CryptoAddressValidator.isValidAddress(address.address) else {
self.delegate?.displayError(error: Errors.invalidAddress, for: self)
return
}
self.value = address.address
self.delegate?.didPaste(in: self)
} else {
self.delegate?.displayError(error: result.error?.error ?? Errors.invalidAddress, for: self)
}
}
self.value = value
delegate?.didPaste(in: self)
}
@objc func openReader() {

Loading…
Cancel
Save