Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NFTs: filter instead of throw error when NFT has invalid network #5537

Merged
merged 10 commits into from
Apr 11, 2024
2 changes: 1 addition & 1 deletion src/networks/arbitrum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const getArbitrumNetworkObject = (): NetworkProperties => {
defaultSlippage: 200,
},

nfts: {},
nfts: { simplehashNetwork: 'arbitrum' },

// design tings
colors: {
Expand Down
2 changes: 1 addition & 1 deletion src/networks/avalanche.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const getAvalancheNetworkObject = (): NetworkProperties => {
defaultSlippage: 200,
},

nfts: {},
nfts: { simplehashNetwork: 'avalanche' },

// design tings
colors: {
Expand Down
2 changes: 1 addition & 1 deletion src/networks/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const getBaseNetworkObject = (): NetworkProperties => {
defaultSlippage: 200,
},

nfts: {},
nfts: { simplehashNetwork: 'base' },

// design tings
colors: {
Expand Down
2 changes: 1 addition & 1 deletion src/networks/blast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const getBlastNetworkObject = (): NetworkProperties => {
defaultSlippage: 200,
},

nfts: {},
nfts: { simplehashNetwork: 'blast' },

// design tings
colors: {
Expand Down
4 changes: 3 additions & 1 deletion src/networks/bsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ export const getBSCNetworkObject = (): NetworkProperties => {
defaultSlippage: 200,
},

nfts: {},
nfts: {
simplehashNetwork: 'bsc',
},

// design tings
colors: {
Expand Down
4 changes: 3 additions & 1 deletion src/networks/gnosis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ export const getGnosisNetworkObject = (): NetworkProperties => {
defaultSlippage: 200,
},

nfts: {},
nfts: {
simplehashNetwork: 'gnosis',
},

// design tings
colors: {
Expand Down
4 changes: 3 additions & 1 deletion src/networks/goerli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ export const getGoerliNetworkObject = (): NetworkProperties => {
defaultToFastGas: true,
},

nfts: {},
nfts: {
simplehashNetwork: 'ethereum-goerli',
},

// design tings
colors: {
Expand Down
2 changes: 1 addition & 1 deletion src/networks/optimism.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const getOptimismNetworkObject = (): NetworkProperties => {
defaultSlippage: 200,
},

nfts: {},
nfts: { simplehashNetwork: 'optimism' },

// design tings
colors: {
Expand Down
2 changes: 1 addition & 1 deletion src/networks/polygon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const getPolygonNetworkObject = (): NetworkProperties => {
defaultToFastGas: true,
},

nfts: {},
nfts: { simplehashNetwork: 'polygon' },

// design tings
colors: {
Expand Down
2 changes: 1 addition & 1 deletion src/networks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export interface NetworkProperties extends Chain {
};

nfts: {
simplehashNetwork?: string;
simplehashNetwork: string | null;
};

// design tings
Expand Down
27 changes: 18 additions & 9 deletions src/resources/nfts/simplehash/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { NFT_API_KEY, NFT_API_URL } from 'react-native-dotenv';
import { RainbowFetchClient } from '@/rainbow-fetch';
import { Network } from '@/helpers';
import { getSimpleHashChainFromNetwork } from '@/resources/nfts/simplehash/utils';
import { SimpleHashChain, SimpleHashListing, SimpleHashNFT, SimpleHashMarketplaceId } from '@/resources/nfts/simplehash/types';
import { RainbowNetworks } from '@/networks';
import { SimpleHashListing, SimpleHashNFT, SimpleHashMarketplaceId } from '@/resources/nfts/simplehash/types';
import { RainbowNetworks, getNetworkObj } from '@/networks';
import { UniqueAsset } from '@/entities';
import { RainbowError, logger } from '@/logger';
import { getGnosisNetworkObject } from '@/networks/gnosis';

export const START_CURSOR = 'start';

Expand All @@ -20,10 +20,11 @@ export async function fetchSimpleHashNFT(
tokenId: string,
network: Omit<Network, Network.goerli> = Network.mainnet
): Promise<SimpleHashNFT | undefined> {
const chain = getSimpleHashChainFromNetwork(network);
const chain = getNetworkObj(network as Network).nfts.simplehashNetwork;

if (!chain) {
throw new Error(`fetchSimpleHashNFT: no SimpleHash chain for network: ${network}`);
logger.error(new RainbowError(`fetchSimpleHashNFT: no SimpleHash chain for network: ${network}`));
return;
}

const response = await nftApi.get(`/nfts/${chain}/${contractAddress}/${tokenId}`, {
Expand All @@ -41,8 +42,9 @@ export async function fetchSimpleHashNFTs(
cursor: string = START_CURSOR
): Promise<{ data: SimpleHashNFT[]; nextCursor: string | null }> {
const chainsParam = RainbowNetworks.filter(network => network.features.nfts)
.map(network => network.nfts?.simplehashNetwork || network.value)
.map(network => network.nfts.simplehashNetwork || network.value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont need the fallback anymore yeah? the null type may end up causing issues here tho 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trueeee

.join(',');
console.log(chainsParam);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log

const cursorSuffix = createCursorSuffix(cursor);
const response = await nftApi.get(`/nfts/owners?chains=${chainsParam}&wallet_addresses=${walletAddress}${cursorSuffix}`, {
headers: {
Expand All @@ -65,7 +67,12 @@ export async function fetchSimpleHashNFTListing(
// array of all eth listings on OpenSea for this token
let listings: SimpleHashListing[] = [];
let cursor = START_CURSOR;
const chain = getSimpleHashChainFromNetwork(network);
const chain = getNetworkObj(network as Network).nfts.simplehashNetwork;

if (!chain) {
logger.error(new RainbowError(`fetchSimpleHashNFTListing: no SimpleHash chain for network: ${network}`));
return;
}

while (cursor) {
const cursorSuffix = createCursorSuffix(cursor);
Expand Down Expand Up @@ -99,10 +106,11 @@ export async function fetchSimpleHashNFTListing(
* @param nft
*/
export async function refreshNFTContractMetadata(nft: UniqueAsset) {
const chain = nft.isPoap ? SimpleHashChain.Gnosis : getSimpleHashChainFromNetwork(nft.network);
const chain = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObj(nft.network)).nfts.simplehashNetwork;

if (!chain) {
logger.error(new RainbowError(`refreshNFTContractMetadata: no SimpleHash chain for network: ${nft.network}`));
return;
}

try {
Expand Down Expand Up @@ -150,10 +158,11 @@ export async function refreshNFTContractMetadata(nft: UniqueAsset) {
* @param nft
*/
export async function reportNFT(nft: UniqueAsset) {
const chain = nft.isPoap ? SimpleHashChain.Gnosis : getSimpleHashChainFromNetwork(nft.network);
const chain = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObj(nft.network)).nfts.simplehashNetwork;

if (!chain) {
logger.error(new RainbowError(`reportNFT: no SimpleHash chain for network: ${nft.network}`));
return;
}

try {
Expand Down
15 changes: 1 addition & 14 deletions src/resources/nfts/simplehash/types.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
import { Network } from '@/helpers';

export enum SimpleHashChain {
Arbitrum = 'arbitrum',
Bsc = 'bsc',
Ethereum = 'ethereum',
Gnosis = 'gnosis',
Optimism = 'optimism',
Polygon = 'polygon',
Zora = 'zora',
Base = 'base',
Avalanche = 'avalanche',
Blast = 'blast',
}

/**
* @see https://docs.simplehash.com/reference/sale-model
*/
Expand Down Expand Up @@ -100,7 +87,7 @@ type SimpleHashCollection = {
*/
export type SimpleHashNFT = {
nft_id: string;
chain: SimpleHashChain;
chain: string;
contract_address: string;
token_id: string | null;
name: string | null;
Expand Down
124 changes: 34 additions & 90 deletions src/resources/nfts/simplehash/utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { AssetType, AssetTypes, EthereumAddress } from '@/entities';
import { AssetType } from '@/entities';
import { UniqueAsset } from '@/entities/uniqueAssets';
import {
SimpleHashNFT,
ValidatedSimpleHashNFT,
SimpleHashChain,
SimpleHashFloorPrice,
SimpleHashMarketplaceId,
SimpleHashTrait,
SimpleHashMarketplace,
} from '@/resources/nfts/simplehash/types';
import { Network } from '@/helpers/networkTypes';
import { ENS_NFT_CONTRACT_ADDRESS, ETH_ADDRESS, POAP_NFT_ADDRESS } from '@/references';
import { convertRawAmountToRoundedDecimal } from '@/helpers/utilities';
import { NFT, NFTFloorPrice, NFTMarketplace, NFTMarketplaceId, NFTTrait, PolygonAllowlist } from '../types';
Expand All @@ -22,6 +20,9 @@ import { PixelRatio } from 'react-native';
import { deviceUtils } from '@/utils';
import { TokenStandard } from '@/handlers/web3';
import { handleNFTImages } from '@/utils/handleNFTImages';
import { RainbowNetworks } from '@/networks';
import { getPolygonNetworkObject } from '@/networks/polygon';
import { getGnosisNetworkObject } from '@/networks/gnosis';

const ENS_COLLECTION_NAME = 'ENS';
const SVG_MIME_TYPE = 'image/svg+xml';
Expand All @@ -32,72 +33,6 @@ const MAX_IMAGE_SCALE = 3;
const FULL_NFT_IMAGE_SIZE = size * MAX_IMAGE_SCALE;
const GOOGLE_USER_CONTENT_URL = 'https://lh3.googleusercontent.com/';

// same thing here, seems like only difference is we use mainnet instead of ethereum
/**
* Returns a `SimpleHashChain` from a given `Network`. Can return undefined if
* a `Network` has no counterpart in SimpleHash.
* @param network `Network`
* @returns `SimpleHashChain` or `undefined`
*/
export function getSimpleHashChainFromNetwork(network: Omit<Network, Network.goerli>): SimpleHashChain | undefined {
switch (network) {
case Network.mainnet:
return SimpleHashChain.Ethereum;
case Network.polygon:
return SimpleHashChain.Polygon;
case Network.arbitrum:
return SimpleHashChain.Arbitrum;
case Network.optimism:
return SimpleHashChain.Optimism;
case Network.bsc:
return SimpleHashChain.Bsc;
case Network.zora:
return SimpleHashChain.Zora;
case Network.avalanche:
return SimpleHashChain.Avalanche;
case Network.blast:
return SimpleHashChain.Blast;
default:
return undefined;
}
}

/**
* Returns a `Network` from a `SimpleHashChain`. If an invalid value is
* forcably passed in, it will throw.
* @param chain `SimpleHashChain`
* @returns `Network`
*/
export function getNetworkFromSimpleHashChain(chain: SimpleHashChain): Network {
switch (chain) {
case SimpleHashChain.Ethereum:
case SimpleHashChain.Gnosis:
return Network.mainnet;
case SimpleHashChain.Polygon:
return Network.polygon;
case SimpleHashChain.Arbitrum:
return Network.arbitrum;
case SimpleHashChain.Optimism:
return Network.optimism;
case SimpleHashChain.Bsc:
return Network.bsc;
case SimpleHashChain.Zora:
return Network.zora;
case SimpleHashChain.Base:
return Network.base;
case SimpleHashChain.Avalanche:
return Network.avalanche;
case SimpleHashChain.Blast:
return Network.blast;
default:
/*
* Throws here because according to TS types, we should NEVER hit this
* default branch in the logic
*/
throw new Error(`getNetworkFromSimpleHashChain received unknown chain: ${chain}`);
}
}

/**
* Filters out NFTs that do not have a name, collection name,
* contract address, or token id, Gnosis NFTs that are not POAPs,
Expand All @@ -108,30 +43,39 @@ export function getNetworkFromSimpleHashChain(chain: SimpleHashChain): Network {
* @returns filtered array of `ValidatedSimpleHashNFT`s
*/
export function filterSimpleHashNFTs(nfts: SimpleHashNFT[], polygonAllowlist?: PolygonAllowlist): ValidatedSimpleHashNFT[] {
return nfts
.filter(nft => {
const lowercasedContractAddress = nft.contract_address?.toLowerCase();
const network = getNetworkFromSimpleHashChain(nft.chain);
return nfts.flatMap(nft => {
const {
chain,
name,
collection: { name: collectionName },
contract_address,
token_id,
} = nft;

const lowercasedContractAddress = nft.contract_address?.toLowerCase();
const network = RainbowNetworks.find(network => network.nfts.simplehashNetwork === chain)?.value;

const isMissingRequiredFields = !nft.name || !nft.collection?.name || !nft.contract_address || !nft.token_id || !network;
const isPolygonAndNotAllowed =
polygonAllowlist && nft.chain === SimpleHashChain.Polygon && !polygonAllowlist[lowercasedContractAddress];
const isGnosisAndNotPOAP = nft.chain === SimpleHashChain.Gnosis && lowercasedContractAddress !== POAP_NFT_ADDRESS;
const isMissingRequiredFields = !name || !collectionName || !contract_address || !token_id || !network;
const isPolygonAndNotAllowed =
polygonAllowlist && nft.chain === getPolygonNetworkObject().nfts.simplehashNetwork && !polygonAllowlist[lowercasedContractAddress];
const isGnosisAndNotPOAP =
nft.chain === getGnosisNetworkObject().nfts.simplehashNetwork && lowercasedContractAddress !== POAP_NFT_ADDRESS;

if (isMissingRequiredFields || isPolygonAndNotAllowed || isGnosisAndNotPOAP) {
return false;
}
if (isMissingRequiredFields || isPolygonAndNotAllowed || isGnosisAndNotPOAP) {
return [];
}

return true;
})
.map(nft => ({
...nft,
name: nft.name!,
contract_address: nft.contract_address,
chain: getNetworkFromSimpleHashChain(nft.chain),
collection: { ...nft.collection, name: nft.collection.name! },
token_id: nft.token_id!,
}));
return [
{
...nft,
name: name,
contract_address: lowercasedContractAddress,
chain: network,
collection: { ...nft.collection, name: collectionName },
token_id: token_id,
},
];
});
}

/**
Expand Down
Loading