@ -21,7 +21,7 @@ public enum SmartContractError: Error {
public protocol ENSDelegate : AnyObject {
public protocol ENSDelegate : AnyObject {
func callSmartContract ( withChainId chainId : ChainId , contract : AlphaWallet . Address , functionName : String , abiString : String , parameters : [ AnyObject ] ) -> AnyPublisher < [ String : Any ] , SmartContractError >
func callSmartContract ( withChainId chainId : ChainId , contract : AlphaWallet . Address , functionName : String , abiString : String , parameters : [ AnyObject ] ) -> AnyPublisher < [ String : Any ] , SmartContractError >
func getSmartContractCallData ( withChainId chainId : ChainId , contract : AlphaWallet . Address , functionName : String , abiString : String , parameters : [ AnyObject ] ) -> Data ?
func getSmartContractCallData ( withChainId chainId : ChainId , contract : AlphaWallet . Address , functionName : String , abiString : String , parameters : [ AnyObject ] ) -> Data ?
func getInterfaceSupported165 ( chainId : Int , hash : String , contract : AlphaWallet . Address ) -> AnyPublisher < Bool , SmartContractError >
func getInterfaceSupported165Async ( chainId : Int , hash : String , contract : AlphaWallet . Address ) async throws -> Bool
}
}
extension ENSDelegate {
extension ENSDelegate {
@ -32,6 +32,10 @@ extension ENSDelegate {
func getSmartContractCallData ( withChainId chainId : ChainId , contract : AlphaWallet . Address , functionName : String , abiString : String , parameters : [ AnyObject ] = [ ] ) -> Data ? {
func getSmartContractCallData ( withChainId chainId : ChainId , contract : AlphaWallet . Address , functionName : String , abiString : String , parameters : [ AnyObject ] = [ ] ) -> Data ? {
getSmartContractCallData ( withChainId : chainId , contract : contract , functionName : functionName , abiString : abiString , parameters : parameters )
getSmartContractCallData ( withChainId : chainId , contract : contract , functionName : functionName , abiString : abiString , parameters : parameters )
}
}
func callSmartContractAsync ( withChainId chainId : ChainId , contract : AlphaWallet . Address , functionName : String , abiString : String , parameters : [ AnyObject ] ) async throws -> [ String : Any ] {
try await callSmartContract ( withChainId : chainId , contract : contract , functionName : functionName , abiString : abiString , parameters : parameters ) . async ( )
}
}
}
public class ENS {
public class ENS {
@ -48,154 +52,137 @@ public class ENS {
self . chainId = chainId
self . chainId = chainId
}
}
public func getENSAddress ( fromName name : String ) -> AnyPublisher < AlphaWallet . Address , SmartContractError > {
public func getENSAddress ( fromName name : String ) async throws -> AlphaWallet . Address {
// i f a l r e a d y a n a d d r e s s , s e n d b a c k t h e a d d r e s s
// i f a l r e a d y a n a d d r e s s , s e n d b a c k t h e a d d r e s s
if let ethAddress = AlphaWallet . Address ( string : name ) { return . just ( ethAddress ) }
if let ethAddress = AlphaWallet . Address ( string : name ) { return ethAddress }
// i f i t d o e s n o t c o n t a i n . e t h , t h e n i t i s n o t a v a l i d e n s n a m e
// i f i t d o e s n o t c o n t a i n . e t h , t h e n i t i s n o t a v a l i d e n s n a m e
if ! name . contains ( " . " ) {
if ! name . contains ( " . " ) { throw SmartContractError . embedded ( ENSError ( description : " Invalid ENS Name " ) ) }
return . fail ( . embedded ( ENSError ( description : " Invalid ENS Name " ) ) )
}
return getResolver ( forName : name )
let resolver = try await getResolver ( forName : name )
. flatMap { resolver -> AnyPublisher < ( AlphaWallet . Address , Bool ) , SmartContractError > in
let supportsEnsIp10 = try await isSupportEnsIp10 ( resolver : resolver )
self . isSupportEnsIp10 ( resolver : resolver ) . map { ( resolver , $0 ) } . eraseToAnyPublisher ( )
verboseLog ( " [ENS] Fetch resolver: \( resolver . eip55String ) supports ENSIP-10? \( supportsEnsIp10 ) for name: \( name ) " )
} . flatMap { resolver , supportsEnsIp10 -> AnyPublisher < AlphaWallet . Address , SmartContractError > in
let node = name . lowercased ( ) . nameHash
verboseLog ( " [ENS] Fetch resolver: \( resolver . eip55String ) supports ENSIP-10? \( supportsEnsIp10 ) for name: \( name ) " )
if supportsEnsIp10 {
let node = name . lowercased ( ) . nameHash
return try await getENSAddressFromResolverUsingResolve ( forName : name , node : node , resolver : resolver )
if supportsEnsIp10 {
} else {
return self . getENSAddressFromResolverUsingResolve ( forName : name , node : node , resolver : resolver )
return try await getENSAddressFromResolverUsingAddr ( forName : name , node : node , resolver : resolver )
} else {
}
return self . getENSAddressFromResolverUsingAddr ( forName : name , node : node , resolver : resolver )
}
} . eraseToAnyPublisher ( )
}
}
// P e r f o r m s a E N S r e v e r s e l o o k u p — f i g u r e o u t E N S n a m e f r o m a g i v e n E t h e r e u m a d d r e s s — a n d t h e n f o r w a r d r e s o l v e s t h e E N S n a m e ( l o o k u p E t h e r e u m a d d r e s s f r o m E N S n a m e ) t o v e r i f y i t . T h i s i s n e c e s s a r y b e c a u s e :
// P e r f o r m s a E N S r e v e r s e l o o k u p — f i g u r e o u t E N S n a m e f r o m a g i v e n E t h e r e u m a d d r e s s — a n d t h e n f o r w a r d r e s o l v e s t h e E N S n a m e ( l o o k u p E t h e r e u m a d d r e s s f r o m E N S n a m e ) t o v e r i f y i t . T h i s i s n e c e s s a r y b e c a u s e :
// ( q u o t e d f r o m h t t p s : / / d o c s . e n s . d o m a i n s / d a p p - d e v e l o p e r - g u i d e / r e s o l v i n g - n a m e s )
// ( q u o t e d f r o m h t t p s : / / d o c s . e n s . d o m a i n s / d a p p - d e v e l o p e r - g u i d e / r e s o l v i n g - n a m e s )
// > " E N S d o e s n o t e n f o r c e t h e a c c u r a c y o f r e v e r s e r e c o r d s - f o r i n s t a n c e , a n y o n e m a y c l a i m t h a t t h e n a m e f o r t h e i r a d d r e s s i s ' a l i c e . e t h ' . T o b e c e r t a i n t h a t t h e c l a i m i s a c c u r a t e , y o u m u s t a l w a y s p e r f o r m a f o r w a r d r e s o l u t i o n f o r t h e r e t u r n e d n a m e a n d c h e c k i t m a t c h e s t h e o r i g i n a l a d d r e s s . "
// > " E N S d o e s n o t e n f o r c e t h e a c c u r a c y o f r e v e r s e r e c o r d s - f o r i n s t a n c e , a n y o n e m a y c l a i m t h a t t h e n a m e f o r t h e i r a d d r e s s i s ' a l i c e . e t h ' . T o b e c e r t a i n t h a t t h e c l a i m i s a c c u r a t e , y o u m u s t a l w a y s p e r f o r m a f o r w a r d r e s o l u t i o n f o r t h e r e t u r n e d n a m e a n d c h e c k i t m a t c h e s t h e o r i g i n a l a d d r e s s . "
public func getName ( fromAddress address : AlphaWallet . Address ) -> AnyPublisher < String , SmartContractError > {
public func getName ( fromAddress address : AlphaWallet . Address ) async throws -> String {
// T O D O i m p r o v e i f d e l e g a t e i s n i l
// T O D O i m p r o v e i f d e l e g a t e i s n i l
guard let delegate = delegate else { return . fail ( SmartContractError . delegateNotFound ) }
guard let delegate = delegate else { throw SmartContractError . delegateNotFound }
// T O D O e x t r a c t g e t r e s o l v e r a n d r e v e r s e l o o k u p f u n c t i o n s
let node = address . nameHash
let node = address . nameHash
let f unction = GetENSResolverEncode ( )
let resolverF unction = GetENSResolverEncode ( )
let chainId = chainId
let chainId = chainId
return delegate . callSmartContract ( withChainId : chainId , contract : Self . registrarContract , functionName : function . name , abiString : function . abi , parameters : [ node ] as [ AnyObject ] ) . flatMap { result -> AnyPublisher < [ String : Any ] , SmartContractError > in
let resolverResult = try await delegate . callSmartContractAsync ( withChainId : chainId , contract : Self . registrarContract , functionName : resolverFunction . name , abiString : resolverFunction . abi , parameters : [ node ] as [ AnyObject ] )
guard let resolverEthereumAddress = result [ " 0 " ] as ? EthereumAddress else {
guard let resolverEthereumAddress = resolverResult [ " 0 " ] as ? EthereumAddress else {
let error = ENSError ( description : " Error extracting result from \( Self . registrarContract ) . \( function . name ) () " )
let error = ENSError ( description : " Error extracting result from \( Self . registrarContract ) . \( resolverFunction . name ) () " )
return . fail ( . embedded ( error ) )
throw SmartContractError . embedded ( error )
}
}
let resolver = AlphaWallet . Address ( address : resolverEthereumAddress )
let resolver = AlphaWallet . Address ( address : resolverEthereumAddress )
guard ! resolver . isNull else {
guard ! resolver . isNull else {
let error = ENSError ( description : " Null address returned " )
let error = ENSError ( description : " Null address returned " )
return . fail ( . embedded ( error ) )
throw SmartContractError . embedded ( error )
}
}
let function = ENSReverseLookupEncode ( )
let reverseLookupFunction = ENSReverseLookupEncode ( )
return delegate . callSmartContract ( withChainId : chainId , contract : resolver , functionName : function . name , abiString : function . abi , parameters : [ node ] as [ AnyObject ] )
let reverseLookupResult = try await delegate . callSmartContractAsync ( withChainId : chainId , contract : resolver , functionName : reverseLookupFunction . name , abiString : reverseLookupFunction . abi , parameters : [ node ] as [ AnyObject ] )
} . flatMap { result -> AnyPublisher < ( String , AlphaWallet . Address ) , SmartContractError > in
guard let ensName = reverseLookupResult [ " 0 " ] as ? String , ensName . contains ( " . " ) else {
guard let ensName = result [ " 0 " ] as ? String , ensName . contains ( " . " ) else {
let error = ENSError ( description : " Incorrect data output from ENS resolver " )
let error = ENSError ( description : " Incorrect data output from ENS resolver " )
throw SmartContractError . embedded ( error )
return . fail ( . embedded ( error ) )
}
}
let resolvedAddress = try await getENSAddress ( fromName : ensName )
return self . getENSAddress ( fromName : ensName ) . map { ( ensName , $0 ) } . eraseToAnyPublisher ( )
if address = = resolvedAddress {
} . tryMap { ensName , resolvedAddress -> String in
return ensName
if address = = resolvedAddress {
} else {
return ensName
throw ENSError ( description : " Forward resolution of ENS name found by reverse look up doesn't match " )
} else {
}
throw ENSError ( description : " Forward resolution of ENS name found by reverse look up doesn't match " )
}
} . mapError { e in
SmartContractError . embedded ( e )
} . eraseToAnyPublisher ( )
}
}
public func getTextRecord ( forName name : String , recordKey : EnsTextRecordKey ) -> AnyPublisher < String , SmartContractError > {
public func getTextRecord ( forName name : String , recordKey : EnsTextRecordKey ) async throws -> String {
// T O D O i m p r o v e i f d e l e g a t e i s n i l
// T O D O i m p r o v e i f d e l e g a t e i s n i l
guard let delegate = delegate else { return . fail ( . delegateNotFound ) }
guard let delegate = delegate else { throw SmartContractError . delegateNotFound }
guard ! name . components ( separatedBy : " . " ) . isEmpty else {
guard ! name . components ( separatedBy : " . " ) . isEmpty else {
return . fail ( . embedded ( ENSError ( description : " \( name ) is invalid ENS name " ) ) )
throw SmartContractError . embedded ( ENSError ( description : " \( name ) is invalid ENS name " ) )
}
}
let addr = name . lowercased ( ) . nameHash
let addr = name . lowercased ( ) . nameHash
let function = GetEnsTextRecord ( )
let function = GetEnsTextRecord ( )
let chainId = chainId
let chainId = chainId
return delegate . callSmartContract ( withChainId : chainId , contract : getENSRecordsContract ( forChainId : chainId ) , functionName : function . name , abiString : function . abi , parameters : [ addr as AnyObject , recordKey . rawValue as AnyObject ] ) . tryMap { result -> String in
let result = try await delegate . callSmartContractAsync ( withChainId : chainId , contract : getENSRecordsContract ( forChainId : chainId ) , functionName : function . name , abiString : function . abi , parameters : [ addr as AnyObject , recordKey . rawValue as AnyObject ] )
guard let record = result [ " 0 " ] as ? String else { throw ENSError ( description : " interface doesn't support for chainId \( chainId ) " ) }
guard let record = result [ " 0 " ] as ? String else { throw ENSError ( description : " interface doesn't support for chainId \( chainId ) " ) }
guard ! record . isEmpty else { throw ENSError ( description : " ENS text record not found for record: \( record ) for chainId: \( chainId ) " ) }
guard ! record . isEmpty else { throw ENSError ( description : " ENS text record not found for record: \( record ) for chainId: \( chainId ) " ) }
return record
return record
} . mapError { e in
SmartContractError . embedded ( e )
} . eraseToAnyPublisher ( )
}
}
private func isSupportEnsIp10 ( resolver : AlphaWallet . Address ) -> AnyPublisher < Bool , SmartContractError > {
private func isSupportEnsIp10 ( resolver : AlphaWallet . Address ) async throws -> Bool {
// T O D O i m p r o v e i f d e l e g a t e i s n i l
// T O D O i m p r o v e i f d e l e g a t e i s n i l
guard let delegate = delegate else { return . fail ( . delegateNotFound ) }
guard let delegate = delegate else { throw SmartContractError . delegateNotFound }
let hash = " 0x9061b923 " // E N S I P - 1 0 r e s o l v e ( b y t e s , b y t e s ) "
let hash = " 0x9061b923 " // E N S I P - 1 0 r e s o l v e ( b y t e s , b y t e s ) "
return delegate . getInterfaceSupported165 ( chainId : chainId , hash : hash , contract : resolver )
return try await delegate . getInterfaceSupported165Async ( chainId : chainId , hash : hash , contract : resolver )
}
}
private func getResolver ( forName name : String ) -> AnyPublisher < AlphaWallet . Address , SmartContractError > {
private func getResolver ( forName name : String ) async throws -> AlphaWallet . Address {
// T O D O i m p r o v e i f d e l e g a t e i s n i l
// T O D O i m p r o v e i f d e l e g a t e i s n i l
guard let delegate = delegate else { return . fail ( . delegateNotFound ) }
guard let delegate = delegate else { throw SmartContractError . delegateNotFound }
let function = GetENSResolverEncode ( )
let function = GetENSResolverEncode ( )
let chainId = chainId
let chainId = chainId
let node = name . lowercased ( ) . nameHash
let node = name . lowercased ( ) . nameHash
return delegate . callSmartContract ( withChainId : chainId , contract : Self . registrarContract , functionName : function . name , abiString : function . abi , parameters : [ node ] as [ AnyObject ] ) . flatMap { result -> AnyPublisher < AlphaWallet . Address , SmartContractError > in
let result = try await delegate . callSmartContractAsync ( withChainId : chainId , contract : Self . registrarContract , functionName : function . name , abiString : function . abi , parameters : [ node ] as [ AnyObject ] )
if let resolver = ( result [ " 0 " ] as ? EthereumAddress ) . flatMap ( { AlphaWallet . Address ( address : $0 ) } ) {
if let resolver = ( result [ " 0 " ] as ? EthereumAddress ) . flatMap ( { AlphaWallet . Address ( address : $0 ) } ) {
verboseLog ( " [ENS] fetched resolver: \( resolver ) for: \( name ) arg: \( node ) " )
verboseLog ( " [ENS] fetched resolver: \( resolver ) for: \( name ) arg: \( node ) " )
if resolver . isNull && name != " " {
if resolver . isNull && name != " " {
// W i l d c a r d r e s o l u t i o n h t t p s : / / d o c s . e n s . d o m a i n s / e n s - i m p r o v e m e n t - p r o p o s a l s / e n s i p - 1 0 - w i l d c a r d - r e s o l u t i o n
// W i l d c a r d r e s o l u t i o n h t t p s : / / d o c s . e n s . d o m a i n s / e n s - i m p r o v e m e n t - p r o p o s a l s / e n s i p - 1 0 - w i l d c a r d - r e s o l u t i o n
let parentName = name . split ( separator : " . " ) . dropFirst ( ) . joined ( separator : " . " )
let parentName = name . split ( separator : " . " ) . dropFirst ( ) . joined ( separator : " . " )
verboseLog ( " [ENS] fetching parent \( parentName ) resolver again for ENSIP-10. Was: \( resolver ) for: \( name ) arg: \( node ) " )
verboseLog ( " [ENS] fetching parent \( parentName ) resolver again for ENSIP-10. Was: \( resolver ) for: \( name ) arg: \( node ) " )
return self . getResolver ( forName : parentName )
return try await getResolver ( forName : parentName )
} else {
if resolver . isNull {
let error = ENSError ( description : " Null address returned " )
throw SmartContractError . embedded ( error )
} else {
} else {
if resolver . isNull {
return resolver
let error = ENSError ( description : " Null address returned " )
return . fail ( . embedded ( error ) )
} else {
return . just ( resolver )
}
}
}
} else {
let error = ENSError ( description : " Error extracting result from \( Self . registrarContract ) . \( function . name ) () " )
return . fail ( . embedded ( error ) )
}
}
} . eraseToAnyPublisher ( )
} else {
let error = ENSError ( description : " Error extracting result from \( Self . registrarContract ) . \( function . name ) () " )
throw SmartContractError . embedded ( error )
}
}
}
private func getENSAddressFromResolverUsingAddr ( forName name : String , node : String , resolver : AlphaWallet . Address ) -> AnyPublisher < AlphaWallet . Address , SmartContractError > {
private func getENSAddressFromResolverUsingAddr ( forName name : String , node : String , resolver : AlphaWallet . Address ) async throws -> AlphaWallet . Address {
// T O D O i m p r o v e i f d e l e g a t e i s n i l
// T O D O i m p r o v e i f d e l e g a t e i s n i l
guard let delegate = delegate else { return . fail ( . delegateNotFound ) }
guard let delegate = delegate else { throw SmartContractError . delegateNotFound }
let function = GetENSRecordWithResolverAddrEncode ( )
let function = GetENSRecordWithResolverAddrEncode ( )
let chainId = chainId
let chainId = chainId
verboseLog ( " [ENS] calling function \( function . name ) for name: \( name ) … " )
verboseLog ( " [ENS] calling function \( function . name ) for name: \( name ) … " )
return delegate . callSmartContract ( withChainId : chainId , contract : resolver , functionName : function . name , abiString : function . abi , parameters : [ node ] as [ AnyObject ] ) . tryMap { result in
let result = try await delegate . callSmartContractAsync ( withChainId : chainId , contract : resolver , functionName : function . name , abiString : function . abi , parameters : [ node ] as [ AnyObject ] )
guard let ensAddressEthereumAddress = result [ " 0 " ] as ? EthereumAddress else { throw ENSError ( description : " Incorrect data output from ENS resolver " ) }
guard let ensAddressEthereumAddress = result [ " 0 " ] as ? EthereumAddress else { throw ENSError ( description : " Incorrect data output from ENS resolver " ) }
let ensAddress = AlphaWallet . Address ( address : ensAddressEthereumAddress )
let ensAddress = AlphaWallet . Address ( address : ensAddressEthereumAddress )
verboseLog ( " [ENS] called function \( function . name ) for name: \( name ) result: \( ensAddress . eip55String ) " )
verboseLog ( " [ENS] called function \( function . name ) for name: \( name ) result: \( ensAddress . eip55String ) " )
guard ! ensAddress . isNull else { throw ENSError ( description : " Null address returned " ) }
guard ! ensAddress . isNull else { throw ENSError ( description : " Null address returned " ) }
return ensAddress
return ensAddress
} . mapError { e in
SmartContractError . embedded ( e )
} . eraseToAnyPublisher ( )
}
}
private func getENSAddressFromResolverUsingResolve ( forName name : String , node : String , resolver : AlphaWallet . Address ) -> AnyPublisher < AlphaWallet . Address , SmartContractError > {
private func getENSAddressFromResolverUsingResolve ( forName name : String , node : String , resolver : AlphaWallet . Address ) async throws -> AlphaWallet . Address {
// T O D O i m p r o v e i f d e l e g a t e i s n i l
// T O D O i m p r o v e i f d e l e g a t e i s n i l
guard let delegate = delegate else { return . fail ( . delegateNotFound ) }
guard let delegate = delegate else { throw SmartContractError . delegateNotFound }
let addrFunction = GetENSRecordWithResolverAddrEncode ( )
let addrFunction = GetENSRecordWithResolverAddrEncode ( )
let resolveFunction = GetENSRecordWithResolverResolveEncode ( )
let resolveFunction = GetENSRecordWithResolverResolveEncode ( )
let dnsEncodedName = functional . dnsEncode ( name : name )
let dnsEncodedName = functional . dnsEncode ( name : name )
guard let callData = delegate . getSmartContractCallData ( withChainId : chainId , contract : resolver , functionName : addrFunction . name , abiString : addrFunction . abi , parameters : [ node ] as [ AnyObject ] ) else {
guard let callData = try delegate . getSmartContractCallData ( withChainId : chainId , contract : resolver , functionName : addrFunction . name , abiString : addrFunction . abi , parameters : [ node ] as [ AnyObject ] ) else {
struct FailedToBuildCallDataForEnsIp10 : Error { }
struct FailedToBuildCallDataForEnsIp10 : Error { }
return Fail ( error : SmartContractError . embedded ( FailedToBuildCallDataForEnsIp10 ( ) ) )
throw SmartContractError . embedded ( FailedToBuildCallDataForEnsIp10 ( ) )
. eraseToAnyPublisher ( )
}
}
verboseLog ( " [ENS] addr data calldata: \( callData . hexString ) " )
verboseLog ( " [ENS] addr data calldata: \( callData . hexString ) " )
let parameters : [ AnyObject ] = [
let parameters : [ AnyObject ] = [
@ -204,17 +191,14 @@ public class ENS {
]
]
let chainId = chainId
let chainId = chainId
verboseLog ( " [ENS] calling function \( resolveFunction . name ) for name: \( name ) DNS-encoded name: \( dnsEncodedName . hex ( ) ) callData: \( callData . hex ( ) ) … " )
verboseLog ( " [ENS] calling function \( resolveFunction . name ) for name: \( name ) DNS-encoded name: \( dnsEncodedName . hex ( ) ) callData: \( callData . hex ( ) ) … " )
return delegate . callSmartContract ( withChainId : chainId , contract : resolver , functionName : resolveFunction . name , abiString : resolveFunction . abi , parameters : parameters ) . tryMap { result in
let result = try await delegate . callSmartContractAsync ( withChainId : chainId , contract : resolver , functionName : resolveFunction . name , abiString : resolveFunction . abi , parameters : parameters )
guard let addressStringAsData = result [ " 0 " ] as ? Data else { throw ENSError ( description : " Incorrect data output from ENS resolver " ) }
guard let addressStringAsData = result [ " 0 " ] as ? Data else { throw ENSError ( description : " Incorrect data output from ENS resolver " ) }
let addressStringLeftPaddedWithZeros = addressStringAsData . hexString
let addressStringLeftPaddedWithZeros = addressStringAsData . hexString
let addressString = String ( addressStringLeftPaddedWithZeros . dropFirst ( addressStringLeftPaddedWithZeros . count - 40 ) )
let addressString = String ( addressStringLeftPaddedWithZeros . dropFirst ( addressStringLeftPaddedWithZeros . count - 40 ) )
verboseLog ( " [ENS] called function \( resolveFunction . name ) for name: \( name ) result: \( addressString ) " )
verboseLog ( " [ENS] called function \( resolveFunction . name ) for name: \( name ) result: \( addressString ) " )
guard let address = AlphaWallet . Address ( uncheckedAgainstNullAddress : addressString ) else { throw ENSError ( description : " Incorrect data output from ENS resolver " ) }
guard let address = AlphaWallet . Address ( uncheckedAgainstNullAddress : addressString ) else { throw ENSError ( description : " Incorrect data output from ENS resolver " ) }
guard ! address . isNull else { throw ENSError ( description : " Null address returned " ) }
guard ! address . isNull else { throw ENSError ( description : " Null address returned " ) }
return address
return address
} . mapError { e in
SmartContractError . embedded ( e )
} . eraseToAnyPublisher ( )
}
}
private func getENSRecordsContract ( forChainId chainId : ChainId ) -> AlphaWallet . Address {
private func getENSRecordsContract ( forChainId chainId : ChainId ) -> AlphaWallet . Address {