UNPKG

sparrow-controllers

Version:

Collection of platform-agnostic modules for creating secure data models for cryptocurrency wallets

690 lines 36.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CollectiblesController = void 0; const events_1 = require("events"); const ethereumjs_util_1 = require("ethereumjs-util"); const async_mutex_1 = require("async-mutex"); const BaseController_1 = require("../BaseController"); const util_1 = require("../util"); const constants_1 = require("../constants"); const assetsUtil_1 = require("./assetsUtil"); const ALL_COLLECTIBLES_STATE_KEY = 'allCollectibles'; const ALL_COLLECTIBLES_CONTRACTS_STATE_KEY = 'allCollectibleContracts'; /** * Controller that stores assets and exposes convenience methods */ class CollectiblesController extends BaseController_1.BaseController { /** * Creates a CollectiblesController instance. * * @param options - The controller options. * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. * @param options.getERC721AssetName - Gets the name of the asset at the given address. * @param options.getERC721AssetSymbol - Gets the symbol of the asset at the given address. * @param options.getERC721TokenURI - Gets the URI of the ERC721 token at the given address, with the given ID. * @param options.getERC721OwnerOf - Get the owner of a ERC-721 collectible. * @param options.getERC1155BalanceOf - Gets balance of a ERC-1155 collectible. * @param options.getERC1155TokenURI - Gets the URI of the ERC1155 token at the given address, with the given ID. * @param config - Initial options used to configure this controller. * @param state - Initial state to set on this controller. */ constructor({ onPreferencesStateChange, onNetworkStateChange, getERC721AssetName, getERC721AssetSymbol, getERC721TokenURI, getERC721OwnerOf, getERC1155BalanceOf, getERC1155TokenURI, }, config, state) { super(config, state); this.mutex = new async_mutex_1.Mutex(); /** * EventEmitter instance used to listen to specific EIP747 events */ this.hub = new events_1.EventEmitter(); /** * Name of this controller used during composition */ this.name = 'CollectiblesController'; this.defaultConfig = { networkType: constants_1.MAINNET, selectedAddress: '', chainId: '', ipfsGateway: constants_1.IPFS_DEFAULT_GATEWAY_URL, openSeaEnabled: false, useIPFSSubdomains: true, }; this.defaultState = { allCollectibleContracts: {}, allCollectibles: {}, ignoredCollectibles: [], }; this.initialize(); this.getERC721AssetName = getERC721AssetName; this.getERC721AssetSymbol = getERC721AssetSymbol; this.getERC721TokenURI = getERC721TokenURI; this.getERC721OwnerOf = getERC721OwnerOf; this.getERC1155BalanceOf = getERC1155BalanceOf; this.getERC1155TokenURI = getERC1155TokenURI; onPreferencesStateChange(({ selectedAddress, ipfsGateway, openSeaEnabled }) => { this.configure({ selectedAddress, ipfsGateway, openSeaEnabled }); }); onNetworkStateChange(({ provider }) => { const { chainId } = provider; this.configure({ chainId }); }); } getCollectibleApi(contractAddress, tokenId) { const { chainId } = this.config; switch (chainId) { case constants_1.RINKEBY_CHAIN_ID: return `https://testnets-api.opensea.io/api/v1/asset/${contractAddress}/${tokenId}`; default: return `https://api.opensea.io/api/v1/asset/${contractAddress}/${tokenId}`; } } getCollectibleContractInformationApi(contractAddress) { const { chainId } = this.config; switch (chainId) { case constants_1.RINKEBY_CHAIN_ID: return `https://testnets-api.opensea.io/api/v1/asset_contract/${contractAddress}`; default: return `https://api.opensea.io/api/v1/asset_contract/${contractAddress}`; } } /** * Helper method to update nested state for allCollectibles and allCollectibleContracts. * * @param newCollection - the modified piece of state to update in the controller's store * @param baseStateKey - The root key in the store to update. * @param passedConfig - An object containing the selectedAddress and chainId that are passed through the auto-detection flow. * @param passedConfig.userAddress - the address passed through the collectible detection flow to ensure detected assets are stored to the correct account * @param passedConfig.chainId - the chainId passed through the collectible detection flow to ensure detected assets are stored to the correct account */ updateNestedCollectibleState(newCollection, baseStateKey, { userAddress, chainId } = { userAddress: this.config.selectedAddress, chainId: this.config.chainId, }) { const { [baseStateKey]: oldState } = this.state; const addressState = oldState[userAddress]; const newAddressState = Object.assign(Object.assign({}, addressState), { [chainId]: newCollection }); const newState = Object.assign(Object.assign({}, oldState), { [userAddress]: newAddressState }); this.update({ [baseStateKey]: newState, }); } /** * Request individual collectible information from OpenSea API. * * @param contractAddress - Hex address of the collectible contract. * @param tokenId - The collectible identifier. * @returns Promise resolving to the current collectible name and image. */ getCollectibleInformationFromApi(contractAddress, tokenId) { return __awaiter(this, void 0, void 0, function* () { const tokenURI = this.getCollectibleApi(contractAddress, tokenId); let collectibleInformation; /* istanbul ignore if */ if (this.openSeaApiKey) { collectibleInformation = yield (0, util_1.handleFetch)(tokenURI, { headers: { 'X-API-KEY': this.openSeaApiKey }, }); } else { collectibleInformation = yield (0, util_1.handleFetch)(tokenURI); } const { num_sales, background_color, image_url, image_preview_url, image_thumbnail_url, image_original_url, animation_url, animation_original_url, name, description, external_link, creator, last_sale, asset_contract: { schema_name }, } = collectibleInformation; /* istanbul ignore next */ const collectibleMetadata = Object.assign({}, { name: name || null }, { description: description || null }, { image: image_url || null }, creator && { creator }, num_sales && { numberOfSales: num_sales }, background_color && { backgroundColor: background_color }, image_preview_url && { imagePreview: image_preview_url }, image_thumbnail_url && { imageThumbnail: image_thumbnail_url }, image_original_url && { imageOriginal: image_original_url }, animation_url && { animation: animation_url }, animation_original_url && { animationOriginal: animation_original_url, }, external_link && { externalLink: external_link }, last_sale && { lastSale: last_sale }, schema_name && { standard: schema_name }); return collectibleMetadata; }); } /** * Request individual collectible information from contracts that follows Metadata Interface. * * @param contractAddress - Hex address of the collectible contract. * @param tokenId - The collectible identifier. * @returns Promise resolving to the current collectible name and image. */ getCollectibleInformationFromTokenURI(contractAddress, tokenId) { return __awaiter(this, void 0, void 0, function* () { const { ipfsGateway, useIPFSSubdomains } = this.config; const result = yield this.getCollectibleURIAndStandard(contractAddress, tokenId); let tokenURI = result[0]; const standard = result[1]; if (tokenURI.startsWith('ipfs://')) { tokenURI = (0, util_1.getFormattedIpfsUrl)(ipfsGateway, tokenURI, useIPFSSubdomains); } try { const object = yield (0, util_1.handleFetch)(tokenURI); // TODO: Check image_url existence. This is not part of EIP721 nor EIP1155 const image = Object.prototype.hasOwnProperty.call(object, 'image') ? 'image' : /* istanbul ignore next */ 'image_url'; return { image: object[image], name: object.name, description: object.description, standard, favorite: false, }; } catch (_a) { return { image: null, name: null, description: null, standard: standard || null, favorite: false, }; } }); } /** * Retrieve collectible uri with metadata. TODO Update method to use IPFS. * * @param contractAddress - Collectible contract address. * @param tokenId - Collectible token id. * @returns Promise resolving collectible uri and token standard. */ getCollectibleURIAndStandard(contractAddress, tokenId) { return __awaiter(this, void 0, void 0, function* () { // try ERC721 uri try { const uri = yield this.getERC721TokenURI(contractAddress, tokenId); return [uri, constants_1.ERC721]; } catch (_a) { // Ignore error } // try ERC1155 uri try { const tokenURI = yield this.getERC1155TokenURI(contractAddress, tokenId); /** * According to EIP1155 the URI value allows for ID substitution * in case the string `{id}` exists. * https://eips.ethereum.org/EIPS/eip-1155#metadata */ if (!tokenURI.includes('{id}')) { return [tokenURI, constants_1.ERC1155]; } const hexTokenId = (0, ethereumjs_util_1.stripHexPrefix)((0, util_1.BNToHex)(new ethereumjs_util_1.BN(tokenId))) .padStart(64, '0') .toLowerCase(); return [tokenURI.replace('{id}', hexTokenId), constants_1.ERC1155]; } catch (_b) { // Ignore error } return ['', '']; }); } /** * Request individual collectible information (name, image url and description). * * @param contractAddress - Hex address of the collectible contract. * @param tokenId - The collectible identifier. * @returns Promise resolving to the current collectible name and image. */ getCollectibleInformation(contractAddress, tokenId) { var _a, _b, _c, _d, _e, _f, _g, _h; return __awaiter(this, void 0, void 0, function* () { const blockchainMetadata = yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { return yield this.getCollectibleInformationFromTokenURI(contractAddress, tokenId); })); let openSeaMetadata; if (this.config.openSeaEnabled) { openSeaMetadata = yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { return yield this.getCollectibleInformationFromApi(contractAddress, tokenId); })); } return Object.assign(Object.assign({}, openSeaMetadata), { name: (_b = (_a = blockchainMetadata.name) !== null && _a !== void 0 ? _a : openSeaMetadata === null || openSeaMetadata === void 0 ? void 0 : openSeaMetadata.name) !== null && _b !== void 0 ? _b : null, description: (_d = (_c = blockchainMetadata.description) !== null && _c !== void 0 ? _c : openSeaMetadata === null || openSeaMetadata === void 0 ? void 0 : openSeaMetadata.description) !== null && _d !== void 0 ? _d : null, image: (_f = (_e = blockchainMetadata.image) !== null && _e !== void 0 ? _e : openSeaMetadata === null || openSeaMetadata === void 0 ? void 0 : openSeaMetadata.image) !== null && _f !== void 0 ? _f : null, standard: (_h = (_g = blockchainMetadata.standard) !== null && _g !== void 0 ? _g : openSeaMetadata === null || openSeaMetadata === void 0 ? void 0 : openSeaMetadata.standard) !== null && _h !== void 0 ? _h : null }); }); } /** * Request collectible contract information from OpenSea API. * * @param contractAddress - Hex address of the collectible contract. * @returns Promise resolving to the current collectible name and image. */ getCollectibleContractInformationFromApi(contractAddress) { return __awaiter(this, void 0, void 0, function* () { const api = this.getCollectibleContractInformationApi(contractAddress); let apiCollectibleContractObject; /* istanbul ignore if */ if (this.openSeaApiKey) { apiCollectibleContractObject = yield (0, util_1.handleFetch)(api, { headers: { 'X-API-KEY': this.openSeaApiKey }, }); } else { apiCollectibleContractObject = yield (0, util_1.handleFetch)(api); } return apiCollectibleContractObject; }); } /** * Request collectible contract information from the contract itself. * * @param contractAddress - Hex address of the collectible contract. * @returns Promise resolving to the current collectible name and image. */ getCollectibleContractInformationFromContract(contractAddress) { return __awaiter(this, void 0, void 0, function* () { const name = yield this.getERC721AssetName(contractAddress); const symbol = yield this.getERC721AssetSymbol(contractAddress); return { collection: { name }, symbol, address: contractAddress, }; }); } /** * Request collectible contract information from OpenSea API. * * @param contractAddress - Hex address of the collectible contract. * @returns Promise resolving to the collectible contract name, image and description. */ getCollectibleContractInformation(contractAddress) { return __awaiter(this, void 0, void 0, function* () { const blockchainContractData = yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { return yield this.getCollectibleContractInformationFromContract(contractAddress); })); let openSeaContractData; if (this.config.openSeaEnabled) { openSeaContractData = yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { return yield this.getCollectibleContractInformationFromApi(contractAddress); })); } if (blockchainContractData || openSeaContractData) { return Object.assign(Object.assign(Object.assign({}, openSeaContractData), blockchainContractData), { collection: Object.assign(Object.assign({ image_url: null }, openSeaContractData === null || openSeaContractData === void 0 ? void 0 : openSeaContractData.collection), blockchainContractData === null || blockchainContractData === void 0 ? void 0 : blockchainContractData.collection) }); } /* istanbul ignore next */ return { address: contractAddress, asset_contract_type: null, created_date: null, schema_name: null, symbol: null, total_supply: null, description: null, external_link: null, collection: { name: null, image_url: null }, }; }); } /** * Adds an individual collectible to the stored collectible list. * * @param address - Hex address of the collectible contract. * @param tokenId - The collectible identifier. * @param collectibleMetadata - Collectible optional information (name, image and description). * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. * @returns Promise resolving to the current collectible list. */ addIndividualCollectible(address, tokenId, collectibleMetadata, detection) { var _a; return __awaiter(this, void 0, void 0, function* () { // TODO: Remove unused return const releaseLock = yield this.mutex.acquire(); try { address = (0, util_1.toChecksumHexAddress)(address); const { allCollectibles } = this.state; let chainId, selectedAddress; if (detection) { chainId = detection.chainId; selectedAddress = detection.userAddress; } else { chainId = this.config.chainId; selectedAddress = this.config.selectedAddress; } const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; const existingEntry = collectibles.find((collectible) => collectible.address.toLowerCase() === address.toLowerCase() && collectible.tokenId === tokenId); if (existingEntry) { const differentMetadata = (0, assetsUtil_1.compareCollectiblesMetadata)(collectibleMetadata, existingEntry); if (differentMetadata) { // TODO: Switch to indexToUpdate const indexToRemove = collectibles.findIndex((collectible) => collectible.address.toLowerCase() === address.toLowerCase() && collectible.tokenId === tokenId); /* istanbul ignore next */ if (indexToRemove !== -1) { collectibles.splice(indexToRemove, 1); } } else { return collectibles; } } const newEntry = Object.assign({ address, tokenId, favorite: (existingEntry === null || existingEntry === void 0 ? void 0 : existingEntry.favorite) || false, isCurrentlyOwned: true }, collectibleMetadata); const newCollectibles = [...collectibles, newEntry]; this.updateNestedCollectibleState(newCollectibles, ALL_COLLECTIBLES_STATE_KEY, { chainId, userAddress: selectedAddress }); return newCollectibles; } finally { releaseLock(); } }); } /** * Adds a collectible contract to the stored collectible contracts list. * * @param address - Hex address of the collectible contract. * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. * @returns Promise resolving to the current collectible contracts list. */ addCollectibleContract(address, detection) { var _a; return __awaiter(this, void 0, void 0, function* () { const releaseLock = yield this.mutex.acquire(); try { address = (0, util_1.toChecksumHexAddress)(address); const { allCollectibleContracts } = this.state; let chainId, selectedAddress; if (detection) { chainId = detection.chainId; selectedAddress = detection.userAddress; } else { chainId = this.config.chainId; selectedAddress = this.config.selectedAddress; } const collectibleContracts = ((_a = allCollectibleContracts[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; const existingEntry = collectibleContracts.find((collectibleContract) => collectibleContract.address.toLowerCase() === address.toLowerCase()); if (existingEntry) { return collectibleContracts; } const contractInformation = yield this.getCollectibleContractInformation(address); const { asset_contract_type, created_date, schema_name, symbol, total_supply, description, external_link, collection: { name, image_url }, } = contractInformation; // If being auto-detected opensea information is expected // Otherwise at least name and symbol from contract is needed if ((detection && !name) || Object.keys(contractInformation).length === 0) { return collectibleContracts; } /* istanbul ignore next */ const newEntry = Object.assign({}, { address }, description && { description }, name && { name }, image_url && { logo: image_url }, symbol && { symbol }, total_supply !== null && typeof total_supply !== 'undefined' && { totalSupply: total_supply }, asset_contract_type && { assetContractType: asset_contract_type }, created_date && { createdDate: created_date }, schema_name && { schemaName: schema_name }, external_link && { externalLink: external_link }); const newCollectibleContracts = [...collectibleContracts, newEntry]; this.updateNestedCollectibleState(newCollectibleContracts, ALL_COLLECTIBLES_CONTRACTS_STATE_KEY, { chainId, userAddress: selectedAddress }); return newCollectibleContracts; } finally { releaseLock(); } }); } /** * Removes an individual collectible from the stored token list and saves it in ignored collectibles list. * * @param address - Hex address of the collectible contract. * @param tokenId - Token identifier of the collectible. */ removeAndIgnoreIndividualCollectible(address, tokenId) { var _a; address = (0, util_1.toChecksumHexAddress)(address); const { allCollectibles, ignoredCollectibles } = this.state; const { chainId, selectedAddress } = this.config; const newIgnoredCollectibles = [...ignoredCollectibles]; const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; const newCollectibles = collectibles.filter((collectible) => { if (collectible.address.toLowerCase() === address.toLowerCase() && collectible.tokenId === tokenId) { const alreadyIgnored = newIgnoredCollectibles.find((c) => c.address === address && c.tokenId === tokenId); !alreadyIgnored && newIgnoredCollectibles.push(collectible); return false; } return true; }); this.updateNestedCollectibleState(newCollectibles, ALL_COLLECTIBLES_STATE_KEY); this.update({ ignoredCollectibles: newIgnoredCollectibles, }); } /** * Removes an individual collectible from the stored token list. * * @param address - Hex address of the collectible contract. * @param tokenId - Token identifier of the collectible. */ removeIndividualCollectible(address, tokenId) { var _a; address = (0, util_1.toChecksumHexAddress)(address); const { allCollectibles } = this.state; const { chainId, selectedAddress } = this.config; const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; const newCollectibles = collectibles.filter((collectible) => !(collectible.address.toLowerCase() === address.toLowerCase() && collectible.tokenId === tokenId)); this.updateNestedCollectibleState(newCollectibles, ALL_COLLECTIBLES_STATE_KEY); } /** * Removes a collectible contract to the stored collectible contracts list. * * @param address - Hex address of the collectible contract. * @returns Promise resolving to the current collectible contracts list. */ removeCollectibleContract(address) { var _a; address = (0, util_1.toChecksumHexAddress)(address); const { allCollectibleContracts } = this.state; const { chainId, selectedAddress } = this.config; const collectibleContracts = ((_a = allCollectibleContracts[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; const newCollectibleContracts = collectibleContracts.filter((collectibleContract) => !(collectibleContract.address.toLowerCase() === address.toLowerCase())); this.updateNestedCollectibleState(newCollectibleContracts, ALL_COLLECTIBLES_CONTRACTS_STATE_KEY); return newCollectibleContracts; } /** * Sets an OpenSea API key to retrieve collectible information. * * @param openSeaApiKey - OpenSea API key. */ setApiKey(openSeaApiKey) { this.openSeaApiKey = openSeaApiKey; } /** * Checks the ownership of a ERC-721 or ERC-1155 collectible for a given address. * * @param ownerAddress - User public address. * @param collectibleAddress - Collectible contract address. * @param collectibleId - Collectible token ID. * @returns Promise resolving the collectible ownership. */ isCollectibleOwner(ownerAddress, collectibleAddress, collectibleId) { return __awaiter(this, void 0, void 0, function* () { // Checks the ownership for ERC-721. try { const owner = yield this.getERC721OwnerOf(collectibleAddress, collectibleId); return ownerAddress.toLowerCase() === owner.toLowerCase(); // eslint-disable-next-line no-empty } catch (_a) { // Ignore ERC-721 contract error } // Checks the ownership for ERC-1155. try { const balance = yield this.getERC1155BalanceOf(ownerAddress, collectibleAddress, collectibleId); return balance > 0; // eslint-disable-next-line no-empty } catch (_b) { // Ignore ERC-1155 contract error } throw new Error('Unable to verify ownership. Probably because the standard is not supported or the chain is incorrect.'); }); } /** * Verifies currently selected address owns entered collectible address/tokenId combo and * adds the collectible and respective collectible contract to the stored collectible and collectible contracts lists. * * @param address - Hex address of the collectible contract. * @param tokenId - The collectible identifier. */ addCollectibleVerifyOwnership(address, tokenId) { return __awaiter(this, void 0, void 0, function* () { const { selectedAddress } = this.config; if (!(yield this.isCollectibleOwner(selectedAddress, address, tokenId))) { throw new Error('This collectible is not owned by the user'); } yield this.addCollectible(address, tokenId); }); } /** * Adds a collectible and respective collectible contract to the stored collectible and collectible contracts lists. * * @param address - Hex address of the collectible contract. * @param tokenId - The collectible identifier. * @param collectibleMetadata - Collectible optional metadata. * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. * @returns Promise resolving to the current collectible list. */ addCollectible(address, tokenId, collectibleMetadata, detection) { return __awaiter(this, void 0, void 0, function* () { address = (0, util_1.toChecksumHexAddress)(address); const newCollectibleContracts = yield this.addCollectibleContract(address, detection); collectibleMetadata = collectibleMetadata || (yield this.getCollectibleInformation(address, tokenId)); // If collectible contract was not added, do not add individual collectible const collectibleContract = newCollectibleContracts.find((contract) => contract.address.toLowerCase() === address.toLowerCase()); // If collectible contract information, add individual collectible if (collectibleContract) { yield this.addIndividualCollectible(address, tokenId, collectibleMetadata, detection); } }); } /** * Removes a collectible from the stored token list. * * @param address - Hex address of the collectible contract. * @param tokenId - Token identifier of the collectible. */ removeCollectible(address, tokenId) { var _a; address = (0, util_1.toChecksumHexAddress)(address); this.removeIndividualCollectible(address, tokenId); const { allCollectibles } = this.state; const { chainId, selectedAddress } = this.config; const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; const remainingCollectible = collectibles.find((collectible) => collectible.address.toLowerCase() === address.toLowerCase()); if (!remainingCollectible) { this.removeCollectibleContract(address); } } /** * Removes a collectible from the stored token list and saves it in ignored collectibles list. * * @param address - Hex address of the collectible contract. * @param tokenId - Token identifier of the collectible. */ removeAndIgnoreCollectible(address, tokenId) { var _a; address = (0, util_1.toChecksumHexAddress)(address); this.removeAndIgnoreIndividualCollectible(address, tokenId); const { allCollectibles } = this.state; const { chainId, selectedAddress } = this.config; const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; const remainingCollectible = collectibles.find((collectible) => collectible.address.toLowerCase() === address.toLowerCase()); if (!remainingCollectible) { this.removeCollectibleContract(address); } } /** * Removes all collectibles from the ignored list. */ clearIgnoredCollectibles() { this.update({ ignoredCollectibles: [] }); } /** * Checks whether input collectible is still owned by the user * And updates the isCurrentlyOwned value on the collectible object accordingly. * * @param collectible - The collectible object to check and update. * @param batch - A boolean indicating whether this method is being called as part of a batch or single update. * @param accountParams - The userAddress and chainId to check ownership against * @param accountParams.userAddress - the address passed through the confirmed transaction flow to ensure detected assets are stored to the correct account * @param accountParams.chainId - the chainId passed through the confirmed transaction flow to ensure detected assets are stored to the correct account * @returns the collectible with the updated isCurrentlyOwned value */ checkAndUpdateSingleCollectibleOwnershipStatus(collectible, batch, { userAddress, chainId } = { userAddress: this.config.selectedAddress, chainId: this.config.chainId, }) { var _a; return __awaiter(this, void 0, void 0, function* () { const { address, tokenId } = collectible; let isOwned = collectible.isCurrentlyOwned; try { isOwned = yield this.isCollectibleOwner(userAddress, address, tokenId); } catch (error) { if (!(error instanceof Error && error.message.includes('Unable to verify ownership'))) { throw error; } } collectible.isCurrentlyOwned = isOwned; if (batch === true) { return collectible; } // if this is not part of a batched update we update this one collectible in state const { allCollectibles } = this.state; const collectibles = ((_a = allCollectibles[userAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; const collectibleToUpdate = collectibles.find((item) => item.tokenId === tokenId && item.address.toLowerCase() === address.toLowerCase()); if (collectibleToUpdate) { collectibleToUpdate.isCurrentlyOwned = isOwned; this.updateNestedCollectibleState(collectibles, ALL_COLLECTIBLES_STATE_KEY, { userAddress, chainId }); } return collectible; }); } /** * Checks whether Collectibles associated with current selectedAddress/chainId combination are still owned by the user * And updates the isCurrentlyOwned value on each accordingly. */ checkAndUpdateAllCollectiblesOwnershipStatus() { var _a; return __awaiter(this, void 0, void 0, function* () { const { allCollectibles } = this.state; const { chainId, selectedAddress } = this.config; const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; const updatedCollectibles = yield Promise.all(collectibles.map((collectible) => __awaiter(this, void 0, void 0, function* () { var _b; return ((_b = (yield this.checkAndUpdateSingleCollectibleOwnershipStatus(collectible, true))) !== null && _b !== void 0 ? _b : collectible); }))); this.updateNestedCollectibleState(updatedCollectibles, ALL_COLLECTIBLES_STATE_KEY); }); } /** * Update collectible favorite status. * * @param address - Hex address of the collectible contract. * @param tokenId - Hex address of the collectible contract. * @param favorite - Collectible new favorite status. */ updateCollectibleFavoriteStatus(address, tokenId, favorite) { var _a; const { allCollectibles } = this.state; const { chainId, selectedAddress } = this.config; const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; const index = collectibles.findIndex((collectible) => collectible.address === address && collectible.tokenId === tokenId); if (index === -1) { return; } const updatedCollectible = Object.assign(Object.assign({}, collectibles[index]), { favorite }); // Update Collectibles array collectibles[index] = updatedCollectible; this.updateNestedCollectibleState(collectibles, ALL_COLLECTIBLES_STATE_KEY); } } exports.CollectiblesController = CollectiblesController; exports.default = CollectiblesController; //# sourceMappingURL=CollectiblesController.js.map