diff --git a/src/assets/AssetsContractController.ts b/src/assets/AssetsContractController.ts index 12af9dd9533..7fac18c6845 100644 --- a/src/assets/AssetsContractController.ts +++ b/src/assets/AssetsContractController.ts @@ -9,6 +9,7 @@ import { NetworkState } from '../network/NetworkController'; import { ERC721Standard } from './Standards/CollectibleStandards/ERC721/ERC721Standard'; import { ERC1155Standard } from './Standards/CollectibleStandards/ERC1155/ERC1155Standard'; import { ERC20Standard } from './Standards/ERC20Standard'; +import { NonStandardFallback } from './Standards/NonStandardFallBack'; /** * Check if token detection is enabled for certain networks @@ -67,6 +68,8 @@ export class AssetsContractController extends BaseController< private erc20Standard?: ERC20Standard; + private nonStandardFallback?: NonStandardFallback; + /** * Name of this controller used during composition */ @@ -129,6 +132,11 @@ export class AssetsContractController extends BaseController< this.erc721Standard = new ERC721Standard(this.web3); this.erc1155Standard = new ERC1155Standard(this.web3); this.erc20Standard = new ERC20Standard(this.web3); + + this.nonStandardFallback = new NonStandardFallback({ + erc721Standard: this.erc721Standard, + erc20Standard: this.erc20Standard, + }); } get provider() { @@ -211,7 +219,8 @@ export class AssetsContractController extends BaseController< if ( this.erc721Standard === undefined || this.erc1155Standard === undefined || - this.erc20Standard === undefined + this.erc20Standard === undefined || + this.nonStandardFallback === undefined ) { throw new Error(MISSING_PROVIDER_ERROR); } @@ -253,7 +262,12 @@ export class AssetsContractController extends BaseController< // Ignore } - throw new Error('Unable to determine contract standard'); + return await this.nonStandardFallback.getDetails( + tokenAddress, + ipfsGateway, + userAddress, + tokenId, + ); } /** diff --git a/src/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.ts b/src/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.ts index ca4f9eebb84..fb9a0850afe 100644 --- a/src/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.ts +++ b/src/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.ts @@ -202,6 +202,38 @@ export class ERC721Standard { }); }; + getTokenURIAndImage = async ( + address: string, + ipfsGateway: string, + tokenId: string, + ) => { + let tokenURI, image; + + try { + tokenURI = await this.getTokenURI(address, tokenId); + if (tokenURI.startsWith('ipfs://')) { + tokenURI = getFormattedIpfsUrl(ipfsGateway, tokenURI, true); + } + } catch { + // ignore + } + + if (tokenURI) { + try { + const response = await timeoutFetch(tokenURI); + const object = await response.json(); + image = object?.image; + if (image?.startsWith('ipfs://')) { + image = getFormattedIpfsUrl(ipfsGateway, image, true); + } + } catch { + // ignore + } + } + + return { tokenURI, image }; + }; + /** * Query if a contract implements an interface. * @@ -242,21 +274,11 @@ export class ERC721Standard { } if (tokenId) { - try { - tokenURI = await this.getTokenURI(address, tokenId); - if (tokenURI.startsWith('ipfs://')) { - tokenURI = getFormattedIpfsUrl(ipfsGateway, tokenURI, true); - } - - const response = await timeoutFetch(tokenURI); - const object = await response.json(); - image = object?.image; - if (image?.startsWith('ipfs://')) { - image = getFormattedIpfsUrl(ipfsGateway, image, true); - } - } catch { - // ignore - } + ({ tokenURI, image } = await this.getTokenURIAndImage( + address, + ipfsGateway, + tokenId, + )); } return { diff --git a/src/assets/Standards/NonStandardFallBack.ts b/src/assets/Standards/NonStandardFallBack.ts new file mode 100644 index 00000000000..bebfb361863 --- /dev/null +++ b/src/assets/Standards/NonStandardFallBack.ts @@ -0,0 +1,91 @@ +import { BN } from 'ethereumjs-util'; +import { UNKNOWN_STANDARD } from '../../constants'; +import { ERC721Standard } from './CollectibleStandards/ERC721/ERC721Standard'; +import { ERC20Standard } from './ERC20Standard'; + +export class NonStandardFallback { + private erc20Standard: ERC20Standard; + + private erc721Standard: ERC721Standard; + + constructor({ + erc20Standard, + erc721Standard, + }: { + erc20Standard: ERC20Standard; + erc721Standard: ERC721Standard; + }) { + this.erc20Standard = erc20Standard; + this.erc721Standard = erc721Standard; + } + + /** + * Query for useful values if a contract does not fully implement one of the three major token interfaces we support (ERC20, ERC721, ERC1155). + * + * @param address - Asset contract address. + * @param ipfsGateway - The user's preferred IPFS gateway. + * @param userAddress - The public address for the currently active user's account. + * @param tokenId - tokenId of a given token in the contract. + * @returns Promise resolving an object containing the standard, decimals, symbol and balance of the given contract/userAddress pair. + */ + async getDetails( + address: string, + ipfsGateway: string, + userAddress?: string, + tokenId?: string, + ): Promise<{ + standard: string; + symbol: string | undefined; + decimals: string | undefined; + balance: BN | undefined; + name: string | undefined; + tokenURI: string | undefined; + image: string | undefined; + }> { + let decimals, symbol, balance, name, image, tokenURI; + + try { + decimals = await this.erc20Standard.getTokenDecimals(address); + } catch { + // ignore + } + + try { + decimals = await this.erc20Standard.getTokenSymbol(address); + } catch { + // ignore + } + + if (userAddress) { + try { + balance = await this.erc20Standard.getBalanceOf(address, userAddress); + } catch { + // ignore + } + } + + try { + name = await this.erc721Standard.getAssetName(address); + } catch { + // ignore + } + + if (tokenId) { + ({ tokenURI, image } = await this.erc721Standard.getTokenURIAndImage( + address, + ipfsGateway, + tokenId, + )); + } + + return { + standard: UNKNOWN_STANDARD, + decimals, + symbol, + balance, + name, + tokenURI, + image, + }; + } +} diff --git a/src/constants.ts b/src/constants.ts index 89a0f9ff05f..fc1e3c4f358 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -10,6 +10,7 @@ export const RINKEBY_CHAIN_ID = '4'; export const ERC721 = 'ERC721'; export const ERC1155 = 'ERC1155'; export const ERC20 = 'ERC20'; +export const UNKNOWN_STANDARD = 'UNKNOWN_STANDARD'; // TOKEN INTERFACE IDS export const ERC721_INTERFACE_ID = '0x80ac58cd';