Skip to content

Commit

Permalink
Improve ENS experience on other networks (#4209)
Browse files Browse the repository at this point in the history
* Use mainnet for name lookups

* Try name resolution on other networks first

* Fix after rebase

* Fix test

* Rename function

* Add tests
  • Loading branch information
FrederikBolding authored Nov 24, 2021
1 parent ec45ee0 commit 78d3b79
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 35 deletions.
8 changes: 4 additions & 4 deletions jest_config/__fixtures__/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,16 @@ export const xDAI: Network = {
}),
dPaths: {
TREZOR: {
name: 'Trezor (ETH)',
path: "m/44'/60'/0'/0/<account>"
name: 'Trezor (XDAI)',
path: "m/44'/700'/0'/0/<account>"
},
LEDGER_NANO_S: {
name: 'Ledger (ETH)',
path: "m/44'/60'/0'/<account>"
},
default: {
name: 'Default (ETH)',
path: "m/44'/60'/0'/0/<account>"
name: 'Default (XDAI)',
path: "m/44'/700'/0'/0/<account>"
}
},
gasPriceSettings: {
Expand Down
6 changes: 4 additions & 2 deletions src/components/GeneralLookupField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ProviderHandler
} from '@services/EthService';
import { createENSResolutionError, isResolutionError } from '@services/EthService/ens';
import { selectDefaultNetwork, useSelector } from '@store';
import { ErrorObject, IReceiverAddress, Network } from '@types';

import AddressLookupSelector, { LabeledAddress } from './AddressLookupSelector';
Expand Down Expand Up @@ -51,6 +52,7 @@ const GeneralLookupField = ({
setFieldTouched,
setFieldError
}: IGeneralLookupFieldComponentProps) => {
const ethNetwork = useSelector(selectDefaultNetwork);
const errorMessage =
error && Object.prototype.hasOwnProperty.call(error, 'message')
? (error as ErrorObject).message
Expand Down Expand Up @@ -137,8 +139,8 @@ const GeneralLookupField = ({
setIsResolvingDomain(true);
setResolutionError(undefined);
try {
const provider = new ProviderHandler(network);
const resolvedAddress = await provider.resolveENSName(domain);
const provider = new ProviderHandler(ethNetwork);
const resolvedAddress = await provider.resolveName(domain, network);
if (!resolvedAddress) {
throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain, { domain });
}
Expand Down
4 changes: 1 addition & 3 deletions src/components/__tests__/ContactLookupField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ const mockMappedContacts: ExtendedContact[] = Object.entries(fContacts).map(([ke
}));

// mock domain resolving function
ProviderHandler.prototype.resolveENSName = jest
.fn()
.mockResolvedValue(mockMappedContacts[0].address);
ProviderHandler.prototype.resolveName = jest.fn().mockResolvedValue(mockMappedContacts[0].address);

describe('ContactLookupField', () => {
test('it renders the placeholder when no value', async () => {
Expand Down
4 changes: 1 addition & 3 deletions src/components/__tests__/ContractLookupField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ const mockMappedContacts: ExtendedContact[] = Object.entries(fContacts).map(([ke
}));

// mock domain resolving function
ProviderHandler.prototype.resolveENSName = jest
.fn()
.mockResolvedValue(mockMappedContacts[0].address);
ProviderHandler.prototype.resolveName = jest.fn().mockResolvedValue(mockMappedContacts[0].address);

describe('ContractLookupField', () => {
test('it renders the placeholder when no value', async () => {
Expand Down
13 changes: 6 additions & 7 deletions src/components/__tests__/GeneralLookupField.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import Resolution, { ResolutionError, ResolutionErrorCode } from '@unstoppabledomains/resolution';
import { ResolutionError, ResolutionErrorCode } from '@unstoppabledomains/resolution';
import { fireEvent, simpleRender, waitFor } from 'test-utils';

import GeneralLookupField from '@components/GeneralLookupField';
import { fContacts, fNetwork } from '@fixtures';
import { ProviderHandler } from '@services/EthService';
import { translateRaw } from '@translations';
import { ExtendedContact, IReceiverAddress, TUuid } from '@types';
import { FallbackProvider } from '@vendor';

interface FormValues {
data: {
Expand Down Expand Up @@ -84,7 +84,7 @@ describe('GeneralLookupField', () => {

it('resolves ens and selects it by keypress enter', async () => {
jest
.spyOn(FallbackProvider.prototype, 'resolveName')
.spyOn(ProviderHandler.prototype, 'resolveName')
.mockResolvedValue(mockMappedContacts[0].address);
const address = mockMappedContacts[0].address;
const ens = 'eth.eth';
Expand All @@ -101,8 +101,7 @@ describe('GeneralLookupField', () => {
});

it('handles non registrered domain', async () => {
// @ts-expect-error Ethers return type is wrong
jest.spyOn(FallbackProvider.prototype, 'resolveName').mockResolvedValue(null);
jest.spyOn(ProviderHandler.prototype, 'resolveName').mockResolvedValue(null);
const ens = 'eth.eth';
const output = { data: { ...initialFormikValues } };
const { container, getByText, rerender } = getComponent(getDefaultProps(), output);
Expand All @@ -120,7 +119,7 @@ describe('GeneralLookupField', () => {

it('handles Unstoppable errors', async () => {
jest
.spyOn(Resolution.prototype, 'addr')
.spyOn(ProviderHandler.prototype, 'resolveName')
.mockRejectedValue(new ResolutionError(ResolutionErrorCode.RecordNotFound));
const ens = 'eth.crypto';
const output = { data: { ...initialFormikValues } };
Expand All @@ -141,7 +140,7 @@ describe('GeneralLookupField', () => {

it('handles Ethers errors', async () => {
jest
.spyOn(FallbackProvider.prototype, 'resolveName')
.spyOn(ProviderHandler.prototype, 'resolveName')
.mockRejectedValue(new Error('network does not support ENS'));
const ens = 'eth.eth';
const output = { data: { ...initialFormikValues } };
Expand Down
2 changes: 2 additions & 0 deletions src/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export const DEFAULT_NETWORK_TICKER = 'ETH' as TTicker;

export const DEFAULT_ASSET_DECIMAL = 18;

export const DEFAULT_COIN_TYPE = 60;

export const MYC_DEX_COMMISSION_RATE = 0.0025;

export const CREATION_ADDRESS = '0x0000000000000000000000000000000000000000';
Expand Down
15 changes: 10 additions & 5 deletions src/database/data/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,8 @@ export const NETWORKS_CONFIG: NetworkConfig = {
[WalletId.TREZOR]: DEFAULT_POLYGON,
[WalletId.LEDGER_NANO_S]: LEDGER_ETH,
[WalletId.TREZOR_NEW]: DEFAULT_POLYGON,
[WalletId.LEDGER_NANO_S_NEW]: LEDGER_ETH
[WalletId.LEDGER_NANO_S_NEW]: LEDGER_ETH,
default: DEFAULT_POLYGON
},
gasPriceSettings: {
min: 30,
Expand Down Expand Up @@ -885,7 +886,8 @@ export const NETWORKS_CONFIG: NetworkConfig = {
[WalletId.TREZOR]: DEFAULT_XDAI,
[WalletId.LEDGER_NANO_S]: LEDGER_ETH,
[WalletId.TREZOR_NEW]: DEFAULT_XDAI,
[WalletId.LEDGER_NANO_S_NEW]: LEDGER_ETH
[WalletId.LEDGER_NANO_S_NEW]: LEDGER_ETH,
default: DEFAULT_XDAI
},
gasPriceSettings: {
min: 0.01,
Expand All @@ -911,7 +913,8 @@ export const NETWORKS_CONFIG: NetworkConfig = {
[WalletId.TREZOR]: DEFAULT_ETH,
[WalletId.LEDGER_NANO_S]: LEDGER_ETH,
[WalletId.TREZOR_NEW]: DEFAULT_ETH,
[WalletId.LEDGER_NANO_S_NEW]: LEDGER_ETH
[WalletId.LEDGER_NANO_S_NEW]: LEDGER_ETH,
default: DEFAULT_ETH
},
gasPriceSettings: {
min: 1,
Expand All @@ -937,7 +940,8 @@ export const NETWORKS_CONFIG: NetworkConfig = {
[WalletId.TREZOR]: DEFAULT_ETH,
[WalletId.LEDGER_NANO_S]: LEDGER_ETH,
[WalletId.TREZOR_NEW]: DEFAULT_ETH,
[WalletId.LEDGER_NANO_S_NEW]: LEDGER_ETH
[WalletId.LEDGER_NANO_S_NEW]: LEDGER_ETH,
default: DEFAULT_ETH
},
gasPriceSettings: {
min: 20,
Expand Down Expand Up @@ -996,7 +1000,8 @@ export const NETWORKS_CONFIG: NetworkConfig = {
[WalletId.TREZOR]: DEFAULT_AVAX,
[WalletId.LEDGER_NANO_S]: DEFAULT_AVAX,
[WalletId.TREZOR_NEW]: DEFAULT_AVAX,
[WalletId.LEDGER_NANO_S_NEW]: DEFAULT_AVAX
[WalletId.LEDGER_NANO_S_NEW]: DEFAULT_AVAX,
default: DEFAULT_AVAX
},
gasPriceSettings: {
min: 10,
Expand Down
12 changes: 6 additions & 6 deletions src/features/InteractWithContracts/stateFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import { useCallback } from 'react';

import debounce from 'lodash/debounce';

import { CREATION_ADDRESS } from '@config';
import { CREATION_ADDRESS, DEFAULT_NETWORK } from '@config';
import { makeBasicTxConfig, makePendingTxReceipt, makeTxFromForm, toTxReceipt } from '@helpers';
import {
EtherscanService,
getGasEstimate,
getNetworkById,
isValidETHAddress,
ProviderHandler,
useAccounts,
Expand Down Expand Up @@ -60,14 +59,15 @@ const InteractWithContractsFactory: TUseStateReducerFactory<InteractWithContract
setState
}) => {
const { getContractsByNetwork, createContract, deleteContract } = useContracts();
const { networks } = useNetworks();
const { getNetworkById } = useNetworks();
const { addTxToAccount } = useAccounts();
const ethNetwork = getNetworkById(DEFAULT_NETWORK);

const handleNetworkSelected = (networkId: NetworkId) => {
setState((prevState: InteractWithContractState) => ({
...prevState,
account: undefined,
network: getNetworkById(networkId, networks),
network: getNetworkById(networkId),
contract: undefined,
contractAddress: '',
addressOrDomainInput: '',
Expand Down Expand Up @@ -143,8 +143,8 @@ const InteractWithContractsFactory: TUseStateReducerFactory<InteractWithContract
resolvingDomain: true
}));

const provider = new ProviderHandler(state.network);
const resolvedAddress = (await provider.resolveENSName(domain)) || CREATION_ADDRESS;
const provider = new ProviderHandler(ethNetwork);
const resolvedAddress = (await provider.resolveName(domain, state.network)) || CREATION_ADDRESS;

setState((prevState: InteractWithContractState) => ({
...prevState,
Expand Down
116 changes: 116 additions & 0 deletions src/services/EthService/network/providerHandler.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DEFAULT_EVRICE } from '@mycrypto/wallets';
import nock from 'nock';

import { fNetworks } from '@fixtures';
Expand Down Expand Up @@ -99,4 +100,119 @@ describe('ProviderHandler', () => {
).resolves.toBe(false);
});
});

describe('resolveName', () => {
nock.disableNetConnect();

afterEach(() => {
nock.cleanAll();
});

it('resolves an ENS name on Ethereum', async () => {
const provider = new ProviderHandler(fNetworks[0]);

nock(/.*/)
.post(/.*/)
.reply(200, () => ({
id: 1,
jsonrpc: '2.0',
result: '0x0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41'
}))
.post(/.*/)
.reply(200, () => ({
id: 1,
jsonrpc: '2.0',
result: '0x0000000000000000000000004bbeeb066ed09b7aed07bf39eee0460dfa261520'
}));

await expect(provider.resolveName('mycrypto.eth')).resolves.toBe(
'0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520'
);
});

it('returns null if no resolver found', async () => {
const provider = new ProviderHandler(fNetworks[0]);

nock(/.*/)
.post(/.*/)
.reply(200, () => ({
id: 1,
jsonrpc: '2.0',
result: '0x0000000000000000000000000000000000000000000000000000000000000000'
}));

await expect(provider.resolveName('mycrypto.eth')).resolves.toBeNull();
});

it('resolves an ENS name with a different coinType', async () => {
const provider = new ProviderHandler(fNetworks[0]);

nock(/.*/)
.post(/.*/)
.reply(200, () => ({
id: 1,
jsonrpc: '2.0',
result: '0x0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41'
}))
.post(/.*/)
.reply(200, () => ({
id: 1,
jsonrpc: '2.0',
result:
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000014983110309620d911731ac0932219af06091b6744000000000000000000000000'
}));

await expect(provider.resolveName('brantly.eth', fNetworks[2])).resolves.toBe(
'0x983110309620D911731Ac0932219af06091b6744'
);
});

it('falls back to ETH name if different coinType doesnt resolve', async () => {
const provider = new ProviderHandler(fNetworks[0]);

nock(/.*/)
.post(/.*/)
.reply(200, () => ({
id: 1,
jsonrpc: '2.0',
result: '0x0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41'
}))
.post(/.*/)
.reply(200, () => ({
id: 1,
jsonrpc: '2.0',
result:
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000014983110309620d911731ac0932219af06091b6744000000000000000000000000'
}))
.post(/.*/)
.reply(200, () => ({
id: 1,
jsonrpc: '2.0',
result: '0x000000000000000000000000983110309620d911731ac0932219af06091b6744'
}));

await expect(
provider.resolveName('brantly.eth', {
...fNetworks[2],
dPaths: { default: DEFAULT_EVRICE }
})
).resolves.toBe('0x983110309620D911731Ac0932219af06091b6744');
});

it('resolves an UD name on Ethereum', async () => {
const provider = new ProviderHandler(fNetworks[0]);

nock(/.*/)
.post(/.*/)
.reply(200, () => ({
id: 1,
jsonrpc: '2.0',
result:
'0x000000000000000000000000b66dce2da6afaaa98f2013446dbcb0f4b0ab28420000000000000000000000008aad44321a86b170879d7a244c1e8d360c99dda8000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002a30783861614434343332314138366231373038373964374132343463316538643336306339394464413800000000000000000000000000000000000000000000'
}));
await expect(provider.resolveName('brad.crypto')).resolves.toBe(
'0x8aaD44321A86b170879d7A244c1e8d360c99DdA8'
);
});
});
});
24 changes: 19 additions & 5 deletions src/services/EthService/network/providerHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { formatEther } from '@ethersproject/units';
import any from '@ungap/promise-any';
import Resolution from '@unstoppabledomains/resolution';

import { DEFAULT_ASSET_DECIMAL } from '@config';
import { DEFAULT_ASSET_DECIMAL, DEFAULT_COIN_TYPE } from '@config';
import { ERC20 } from '@services/EthService';
import { erc20Abi } from '@services/EthService/contracts/erc20';
import {
Expand All @@ -25,7 +25,7 @@ import {
TAddress,
TokenInformation
} from '@types';
import { baseToConvertedUnit } from '@utils';
import { baseToConvertedUnit, getCoinType } from '@utils';
import { FallbackProvider } from '@vendor';

import { EIP1271_ABI } from '../contracts';
Expand Down Expand Up @@ -198,15 +198,29 @@ export class ProviderHandler {
});
}

public resolveENSName(name: string): Promise<string | null> {
return this.injectClient((client) => {
public resolveName(name: string, network?: Network): Promise<string | null> {
return this.injectClient(async (client) => {
// Use Unstoppable if supported, otherwise is probably an ENS name
const unstoppable = Resolution.fromEthersProvider(client);
if (unstoppable.isSupportedDomain(name)) {
return unstoppable.addr(name, this.network.baseUnit);
}

return client.resolveName(name);
const resolver = await client.getResolver(name);
if (!resolver) {
return null;
}
const path = network?.dPaths.default;
const coinType = path && getCoinType(path);

if (coinType && coinType !== DEFAULT_COIN_TYPE) {
const resolved = await resolver.getAddress(coinType).catch(() => null);
if (resolved) {
return resolved;
}
}

return resolver.getAddress();
});
}

Expand Down
Loading

0 comments on commit 78d3b79

Please sign in to comment.