From 6186122da79385fd9a6864ad5aeceab496aba681 Mon Sep 17 00:00:00 2001 From: Peter Kompasz Date: Fri, 19 Jul 2024 17:43:45 +0300 Subject: [PATCH 1/6] add Highlight --- src/ingestors/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ingestors/index.ts b/src/ingestors/index.ts index e726629..f6a5d8d 100644 --- a/src/ingestors/index.ts +++ b/src/ingestors/index.ts @@ -1,6 +1,7 @@ import { MintIngestor } from '../lib/types/mint-ingestor'; import { ProhibitionDailyIngestor } from './prohibition-daily'; import { FxHashIngestor } from './fxhash'; +import { HighlightIngestor } from './highlight'; export type MintIngestionMap = { [key: string]: MintIngestor; @@ -9,6 +10,7 @@ export type MintIngestionMap = { export const ALL_MINT_INGESTORS: MintIngestionMap = { 'prohibition-daily': new ProhibitionDailyIngestor(), fxhash: new FxHashIngestor(), + highlight: new HighlightIngestor(), }; export * from './'; From 42cf98d93b77735b41b44e910932b759fe4ae3c0 Mon Sep 17 00:00:00 2001 From: Peter Kompasz Date: Fri, 19 Jul 2024 17:44:26 +0300 Subject: [PATCH 2/6] Highlight Ingestor init --- src/ingestors/highlight/abi.ts | 42 ++++++ src/ingestors/highlight/index.ts | 146 +++++++++++++++++++ src/ingestors/highlight/offchain-metadata.ts | 121 +++++++++++++++ src/ingestors/highlight/onchain-metadata.ts | 39 +++++ src/ingestors/highlight/types.ts | 42 ++++++ 5 files changed, 390 insertions(+) create mode 100644 src/ingestors/highlight/abi.ts create mode 100644 src/ingestors/highlight/index.ts create mode 100644 src/ingestors/highlight/offchain-metadata.ts create mode 100644 src/ingestors/highlight/onchain-metadata.ts create mode 100644 src/ingestors/highlight/types.ts diff --git a/src/ingestors/highlight/abi.ts b/src/ingestors/highlight/abi.ts new file mode 100644 index 0000000..b0761fa --- /dev/null +++ b/src/ingestors/highlight/abi.ts @@ -0,0 +1,42 @@ +export const MINT_CONTRACT_ABI = [ + { + inputs: [{ internalType: 'uint256', name: 'vectorId', type: 'uint256' }], + name: 'getAbridgedVector', + outputs: [ + { + components: [ + { internalType: 'address', name: 'contractAddress', type: 'address' }, + { internalType: 'uint48', name: 'startTimestamp', type: 'uint48' }, + { internalType: 'uint48', name: 'endTimestamp', type: 'uint48' }, + { internalType: 'address', name: 'paymentRecipient', type: 'address' }, + { internalType: 'uint48', name: 'maxTotalClaimableViaVector', type: 'uint48' }, + { internalType: 'uint48', name: 'totalClaimedViaVector', type: 'uint48' }, + { internalType: 'address', name: 'currency', type: 'address' }, + { internalType: 'uint48', name: 'tokenLimitPerTx', type: 'uint48' }, + { internalType: 'uint48', name: 'maxUserClaimableViaVector', type: 'uint48' }, + { internalType: 'uint192', name: 'pricePerToken', type: 'uint192' }, + { internalType: 'uint48', name: 'editionId', type: 'uint48' }, + { internalType: 'bool', name: 'editionBasedCollection', type: 'bool' }, + { internalType: 'bool', name: 'requireDirectEOA', type: 'bool' }, + { internalType: 'bytes32', name: 'allowlistRoot', type: 'bytes32' }, + ], + internalType: 'struct IAbridgedMintVector.AbridgedVector', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'vectorId', type: 'uint256' }, + { internalType: 'uint48', name: 'numTokensToMint', type: 'uint48' }, + { internalType: 'address', name: 'mintRecipient', type: 'address' }, + ], + name: 'vectorMint721', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, +]; diff --git a/src/ingestors/highlight/index.ts b/src/ingestors/highlight/index.ts new file mode 100644 index 0000000..fca8cb2 --- /dev/null +++ b/src/ingestors/highlight/index.ts @@ -0,0 +1,146 @@ +import { MintContractOptions, MintIngestor, MintIngestorResources } from '../../lib/types/mint-ingestor'; +import { MintIngestionErrorName, MintIngestorError } from '../../lib/types/mint-ingestor-error'; +import { MintInstructionType, MintTemplate } from '../../lib/types/mint-template'; +import { MintTemplateBuilder } from '../../lib/builder/mint-template-builder'; +import { getMetadata, getMintPriceInWei } from './onchain-metadata'; +import { getCollectionByAddress, getCollectionById, getVectorId } from './offchain-metadata'; +import { MINT_CONTRACT_ABI } from './abi'; + +const CONTRACT_ADDRESS = '0x8087039152c472Fa74F47398628fF002994056EA'; + +export class HighlightIngestor implements MintIngestor { + async supportsUrl(resources: MintIngestorResources, url: string): Promise { + const id = url.split('/').pop(); + if (!id) { + return false; + } + + const collection = await getCollectionById(resources, id); + + if (!collection || collection.chainId !== 8453) { + return false; + } + + const urlPattern = /^https:\/\/highlight\.xyz\/mint\/[a-f0-9]{24}$/; + return ( + new URL(url).hostname === 'www.highlight.xyz' || new URL(url).hostname === 'highlight.xyz' || urlPattern.test(url) + ); + } + + async supportsContract(resources: MintIngestorResources, contractOptions: MintContractOptions): Promise { + if (contractOptions.chainId !== 8453) { + return false; + } + const collection = await getCollectionByAddress(resources, contractOptions); + if (!collection) { + return false; + } + return true; + } + + async createMintForContract( + resources: MintIngestorResources, + contractOptions: MintContractOptions, + ): Promise { + const mintBuilder = new MintTemplateBuilder() + .setMintInstructionType(MintInstructionType.EVM_MINT) + .setPartnerName('Highlight'); + + if (contractOptions.url) { + mintBuilder.setMarketingUrl(contractOptions.url); + } + + const collection = await getCollectionByAddress(resources, contractOptions); + + if (!collection) { + throw new MintIngestorError(MintIngestionErrorName.CouldNotResolveMint, 'Collection not found'); + } + + const contractAddress = collection.contract; + const description = collection?.description; + + mintBuilder.setName(collection.name).setDescription(description).setFeaturedImageUrl(collection.image.split('?')[0]); + mintBuilder.setMintOutputContract({ chainId: 8453, address: contractAddress }); + + if (collection.sampleImages.length) { + collection.sampleImages.forEach((url, index) => { + mintBuilder.addImage(url, `Sample image #${index}`); + }); + } + + if (!collection.creator) { + throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Error finding creator'); + } + + mintBuilder.setCreator({ + // TODO + name: 'null', + walletAddress: collection.creator, + }); + + const vectorId = await getVectorId(resources, collection.highlightCollection.id); + + if (!vectorId) { + throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Id not available'); + } + + const totalPriceWei = await getMintPriceInWei(vectorId, resources.alchemy); + + if (!totalPriceWei) { + throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Price not available'); + } + + mintBuilder.setMintInstructions({ + chainId: 8453, + contractAddress: CONTRACT_ADDRESS, + contractMethod: 'vectorMint721', + contractParams: `[${vectorId}, 1, address]`, + abi: MINT_CONTRACT_ABI, + priceWei: totalPriceWei, + }); + + const metadata = await getMetadata(+vectorId, resources.alchemy); + + if (!metadata) { + throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Missing timestamps'); + } + + const { startTimestamp, endTimestamp } = metadata; + + const liveDate = +new Date() > startTimestamp * 1000 ? new Date() : new Date(startTimestamp * 1000); + mintBuilder + .setAvailableForPurchaseStart(new Date(startTimestamp * 1000 || Date.now())) + .setAvailableForPurchaseEnd(new Date(endTimestamp * 1000 || '2030-01-01')) + .setLiveDate(liveDate); + + return mintBuilder.build(); + } + + async createMintTemplateForUrl(resources: MintIngestorResources, url: string): Promise { + const isCompatible = await this.supportsUrl(resources, url); + if (!isCompatible) { + throw new MintIngestorError(MintIngestionErrorName.IncompatibleUrl, 'Incompatible URL'); + } + + // Example URL: https://highlight.xyz/mint/665fa33f07b3436991e55632 + const splits = url.split('/'); + const id = splits.pop(); + const chain = splits.pop(); + + if (!id) { + throw new MintIngestorError(MintIngestionErrorName.CouldNotResolveMint, 'Url error'); + } + + const collection = await getCollectionById(resources, id); + + if (!collection) { + throw new MintIngestorError(MintIngestionErrorName.CouldNotResolveMint, 'No such collection'); + } + + return this.createMintForContract(resources, { + chainId: collection.chainId, + contractAddress: collection.address, + url, + }); + } +} diff --git a/src/ingestors/highlight/offchain-metadata.ts b/src/ingestors/highlight/offchain-metadata.ts new file mode 100644 index 0000000..f20bcf6 --- /dev/null +++ b/src/ingestors/highlight/offchain-metadata.ts @@ -0,0 +1,121 @@ +import { MintContractOptions, MintIngestorResources } from 'src/lib'; +import { Collection, CollectionByAddress, CollectionByAddress1 } from './types'; + +export const getCollectionById = async ( + resources: MintIngestorResources, + id: string, +): Promise => { + const url = 'https://api.highlight.xyz:8080/'; + + const headers = { + accept: 'application/json', + 'content-type': 'application/json', + }; + + const data = { + operationName: 'GetCollectionDetails', + variables: { + collectionId: id, + }, + query: ` + query GetCollectionDetails($collectionId: String!) { + getPublicCollectionDetails(collectionId: $collectionId) { + id + name + description + collectionImage + marketplaceId + accountId + address + symbol + chainId + status + baseUri + onChainBaseUri + } + } + `, + }; + + try { + const resp = await resources.fetcher.post(url, data, { headers }); + if (!resp.data.data) { + throw new Error("Empty response"); + } + return resp.data.data.getPublicCollectionDetails; + } catch (error) { + + } +}; + +export const getCollectionByAddress = async ( + resources: MintIngestorResources, + contractOptioons: MintContractOptions, +): Promise => { + try { + const resp = await resources.fetcher( + `https://marketplace.highlight.xyz/reservoir/base/collections/v7?id=${contractOptioons.contractAddress}&normalizeRoyalties=false`, + ); + const collection1: CollectionByAddress1 = resp.data.collections.find((c: CollectionByAddress) => { + return c.id.toLowerCase() === contractOptioons.contractAddress.toLowerCase() && c.chainId === 8453; + }); + + const resp2 = await resources.fetcher( + `https://marketplace.highlight.xyz/reservoir/base/tokens/v7?collection=${contractOptioons.contractAddress}&limit=1&normalizeRoyalties=false`, + ); + const collection2 = resp2.data.tokens[0]; + return { + ...collection1, + ...collection2, + }; + } catch (error) {} +}; + +export const getVectorId = async (resources: MintIngestorResources, id: string): Promise => { + const url = 'https://api.highlight.xyz:8080/'; + + const headers = { + accept: 'application/json', + 'content-type': 'application/json', + }; + + const data = { + operationName: 'GetCollectionSaleDetails', + variables: { + collectionId: id, + }, + query: ` + query GetCollectionSaleDetails($collectionId: String!) { + getPublicCollectionDetails(collectionId: $collectionId) { + size + mintVectors { + name + start + end + paused + price + currency + chainId + paymentCurrency { + address + decimals + symbol + type + mintFee + } + onchainMintVectorId + } + } + } + `, + }; + + try { + const resp = await resources.fetcher.post(url, data, { headers }); + const vectorString = resp.data.data.getPublicCollectionDetails.mintVectors.find( + (c: { chainId: number }) => c.chainId === 8453, + ).onchainMintVectorId; + const vectorId = vectorString.split(':').pop(); + return vectorId; + } catch (error) {} +}; diff --git a/src/ingestors/highlight/onchain-metadata.ts b/src/ingestors/highlight/onchain-metadata.ts new file mode 100644 index 0000000..da3e4de --- /dev/null +++ b/src/ingestors/highlight/onchain-metadata.ts @@ -0,0 +1,39 @@ +import { Alchemy, Contract } from 'alchemy-sdk'; +import { MINT_CONTRACT_ABI } from './abi'; + +const CONTRACT_ADDRESS = '0x8087039152c472Fa74F47398628fF002994056EA'; + +const getContract = async (alchemy: Alchemy): Promise => { + const ethersProvider = await alchemy.config.getProvider(); + const contract = new Contract(CONTRACT_ADDRESS, MINT_CONTRACT_ABI, ethersProvider); + return contract; +}; + +export const getMintPriceInWei = async (vectorId: string, alchemy: Alchemy): Promise => { + try { + const contract = await getContract(alchemy); + const data = await contract.functions.getAbridgedVector(vectorId); + const { pricePerToken } = data[0]; + + const fee = 800000000000000; + const totalFee = parseInt(pricePerToken.toString()) + fee; + + return `${totalFee}`; + } catch (error) { + console.log(error); + } +}; + +export const getMetadata = async ( + vectorId: number, + alchemy: Alchemy, +): Promise<{ startTimestamp: number; endTimestamp: number } | undefined> => { + try { + const contract = await getContract(alchemy); + const metadata = await contract.functions.getAbridgedVector(vectorId); + const { startTimestamp, endTimestamp } = metadata[0] + return { startTimestamp, endTimestamp }; + } catch (error) { + + } +}; diff --git a/src/ingestors/highlight/types.ts b/src/ingestors/highlight/types.ts new file mode 100644 index 0000000..1265c14 --- /dev/null +++ b/src/ingestors/highlight/types.ts @@ -0,0 +1,42 @@ +export type Collection = { + id: string; + name: string; + description: string; + collectionImage: string; + marketplaceId: string; + accountId: string; + address: string; + symbol: string; + chainId: number; + status: string; + baseUri: string, +}; + +export type CollectionByAddress1 = { + chainId: number; + id: string; + createdAt: string; + updatedAt: string; + contractDeployedAt: string; + name: string; + image: string; + symbol: string; + description: string; + sampleImages: string[]; + creator: string; +} + +export type CollectionByAddress2 = { + chainId: number; + contract: string; + highlightCollection: { + // Vector id + id: string; + name: string; + owner: string; + imageUrl: string; + animationUrl: string; + } +} + +export type CollectionByAddress = CollectionByAddress1 & CollectionByAddress2 From ac08b24646186c3d44906de1120724c6a2a5f425 Mon Sep 17 00:00:00 2001 From: Peter Kompasz Date: Fri, 19 Jul 2024 17:44:40 +0300 Subject: [PATCH 3/6] Highlight Ingestor tests --- test/ingestors/highlight.test.ts | 107 +++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 test/ingestors/highlight.test.ts diff --git a/test/ingestors/highlight.test.ts b/test/ingestors/highlight.test.ts new file mode 100644 index 0000000..b553d49 --- /dev/null +++ b/test/ingestors/highlight.test.ts @@ -0,0 +1,107 @@ +import { expect } from 'chai'; +import { HighlightIngestor } from '../../src/ingestors/highlight'; +import { mintIngestorResources } from '../../src/lib/resources'; +import { EVMMintInstructions } from '../../src/lib/types/mint-template'; +import { MintTemplateBuilder } from '../../src/lib/builder/mint-template-builder'; +import { basicIngestorTests } from '../shared/basic-ingestor-tests'; + +const resources = mintIngestorResources(); + +describe('highlight', function () { + basicIngestorTests(new HighlightIngestor(), resources, { + successUrls: [ + 'https://highlight.xyz/mint/665fa33f07b3436991e55632', + 'https://highlight.xyz/mint/66856628ff8a01fdccc132f4', + ], + failureUrls: [ + 'https://highlight.xyz/mint/66963c500b48236f1acf322b', + 'https://foundation.app/mint/base/the-billows', + ], + successContracts: [ + { chainId: 8453, contractAddress: '0x0E5DDe3De7cf2761d8a81Ee68F48410425e2dBbA' }, + { chainId: 8453, contractAddress: '0xBE96B2572CA0F1ac8ec6323Bc9037AffD270bA7F' }, + ], + failureContracts: [ + { chainId: 5000, contractAddress: '0x62F8C536De24ED32611f128f64F6eAbd9b82176c' }, + ], + }); + it('supportsUrl: Returns false for an unsupported URL', async function () { + const ingestor = new HighlightIngestor(); + const url = 'https://example.com'; + const resources = mintIngestorResources(); + const result = await ingestor.supportsUrl(resources, url); + expect(result).to.be.false; + }); + + it('supportsUrl: Returns true for a supported URL', async function () { + const ingestor = new HighlightIngestor(); + const url = 'https://highlight.xyz/mint/66966e909562252851632a96'; + const resources = mintIngestorResources(); + const result = await ingestor.supportsUrl(resources, url); + expect(result).to.be.true; + + const url2 = 'https://highlight.xyz/mint/66966e909562252851632a96'; + const result2 = await ingestor.supportsUrl(resources, url2); + expect(result2).to.be.true; + }); + + it('createMintTemplateForUrl: Returns a mint template for a supported URL', async function () { + const ingestor = new HighlightIngestor(); + const url = 'https://highlight.xyz/mint/665fa33f07b3436991e55632'; + const resources = mintIngestorResources(); + const template = await ingestor.createMintTemplateForUrl(resources, url); + + // Verify that the mint template passed validation + const builder = new MintTemplateBuilder(template); + builder.validateMintTemplate(); + + expect(template.name).to.equal('COMBAT MODE by Emily Xie'); + expect(template.description).to.contain( + 'It depicts two creatures in battle, melding the nostalgia of old school video games with the contemporary possibilities of digital illustration.', + ); + const mintInstructions = template.mintInstructions as EVMMintInstructions; + + expect(mintInstructions.contractAddress).to.equal('0x8087039152c472Fa74F47398628fF002994056EA'); + expect(mintInstructions.contractMethod).to.equal('vectorMint721'); + expect(mintInstructions.contractParams).to.equal('[866, 1, address]'); + expect(mintInstructions.priceWei).to.equal('2100000000000000'); + + expect(template.featuredImageUrl).to.equal( + 'https://img.reservoir.tools/images/v2/base/z9JRSpLYGu7%2BCZoKWtAuANCXTgWBry4OTpgBkNYv7UVX%2FOELQ1B1IQGOoFgJBPmEzWQJa5hKPeiypcjXnSgXVEhZJDeOg9vk5slunBxp8ABMKIlkw3COL8nejLu9cx7f5QrJHJecqNaXIZCHlWY311DY%2F4e9zjeJnyY%2Fvp3J%2FivCSdJShfdu2%2FoCfqed8TvVTrlrElK7Wp8owCwKnZNhaw%3D%3D', + ); + + expect(template.marketingUrl).to.equal(url); + expect(template.availableForPurchaseStart?.getTime()).to.equal(+new Date('2024-06-05T16:00:00.000Z')); + expect(template.availableForPurchaseEnd?.getTime()).to.equal(+new Date('2024-08-31T16:00:00.000Z')); + }); + + it('createMintTemplateForUrl: Returns a mint template for a supported URL with free price', async function () { + const ingestor = new HighlightIngestor(); + const url = 'https://highlight.xyz/mint/66744e64e610ed36adeb1a64'; + const resources = mintIngestorResources(); + const template = await ingestor.createMintTemplateForUrl(resources, url); + + // Verify that the mint template passed validation + const builder = new MintTemplateBuilder(template); + builder.validateMintTemplate(); + + expect(template.name).to.equal('RGB Friends'); + expect(template.description).to.contain('RGB Friends is an infinite, generative PFP collection'); + const mintInstructions = template.mintInstructions as EVMMintInstructions; + + expect(mintInstructions.contractAddress).to.equal('0x8087039152c472Fa74F47398628fF002994056EA'); + expect(mintInstructions.contractMethod).to.equal('vectorMint721'); + expect(mintInstructions.contractParams).to.equal('[977, 1, address]'); + expect(mintInstructions.priceWei).to.equal('800000000000000'); + + expect(template.featuredImageUrl).to.equal( + 'https://img.reservoir.tools/images/v2/base/z9JRSpLYGu7%2BCZoKWtAuAKM5v2dthdDNgoFYsopVhfXBHjSfVbMXHiaW1XsdogS5oNzhOcvyJcxoIKiiKqHsNxiXyJX%2B%2BppNtkeQvHYCslZTqG21HhITlOtTV8jhhZhOQdWST4CHb1DA%2B5K8ZAHTSu9b0MV4dWJJsqPVJ439DhVcURxmw1fKJ4pAhC3iCwl1DOXK1xnEOnLO0il04rMAPA%3D%3D', + ); + + expect(template.marketingUrl).to.equal(url); + expect(template.availableForPurchaseStart?.getTime()).to.equal(+new Date('2024-06-20T17:00:04.000Z')); + expect(template.availableForPurchaseEnd?.getTime()).to.equal(+new Date('2030-01-01T00:00:00.000Z')); + }); +}); + +// TODO Test owner From 1bd89eae52c8cca1a50fc602c4d29c874ef84b73 Mon Sep 17 00:00:00 2001 From: Peter Kompasz Date: Fri, 19 Jul 2024 18:18:07 +0300 Subject: [PATCH 4/6] added owner details --- src/ingestors/highlight/index.ts | 13 ++++-- src/ingestors/highlight/offchain-metadata.ts | 47 ++++++++++++++++++-- test/ingestors/highlight.test.ts | 22 ++++++--- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/src/ingestors/highlight/index.ts b/src/ingestors/highlight/index.ts index fca8cb2..45b9956 100644 --- a/src/ingestors/highlight/index.ts +++ b/src/ingestors/highlight/index.ts @@ -3,7 +3,7 @@ import { MintIngestionErrorName, MintIngestorError } from '../../lib/types/mint- import { MintInstructionType, MintTemplate } from '../../lib/types/mint-template'; import { MintTemplateBuilder } from '../../lib/builder/mint-template-builder'; import { getMetadata, getMintPriceInWei } from './onchain-metadata'; -import { getCollectionByAddress, getCollectionById, getVectorId } from './offchain-metadata'; +import { getCollectionByAddress, getCollectionById, getCollectionOwnerDetails, getVectorId } from './offchain-metadata'; import { MINT_CONTRACT_ABI } from './abi'; const CONTRACT_ADDRESS = '0x8087039152c472Fa74F47398628fF002994056EA'; @@ -59,7 +59,10 @@ export class HighlightIngestor implements MintIngestor { const contractAddress = collection.contract; const description = collection?.description; - mintBuilder.setName(collection.name).setDescription(description).setFeaturedImageUrl(collection.image.split('?')[0]); + mintBuilder + .setName(collection.name) + .setDescription(description) + .setFeaturedImageUrl(collection.image.split('?')[0]); mintBuilder.setMintOutputContract({ chainId: 8453, address: contractAddress }); if (collection.sampleImages.length) { @@ -72,10 +75,12 @@ export class HighlightIngestor implements MintIngestor { throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Error finding creator'); } + const creator = await getCollectionOwnerDetails(resources, collection.highlightCollection.id); + mintBuilder.setCreator({ - // TODO - name: 'null', + name: creator.creatorAccountSettings.displayName, walletAddress: collection.creator, + imageUrl: creator.creatorAccountSettings.displayAvatar, }); const vectorId = await getVectorId(resources, collection.highlightCollection.id); diff --git a/src/ingestors/highlight/offchain-metadata.ts b/src/ingestors/highlight/offchain-metadata.ts index f20bcf6..a808d3b 100644 --- a/src/ingestors/highlight/offchain-metadata.ts +++ b/src/ingestors/highlight/offchain-metadata.ts @@ -40,12 +40,10 @@ export const getCollectionById = async ( try { const resp = await resources.fetcher.post(url, data, { headers }); if (!resp.data.data) { - throw new Error("Empty response"); + throw new Error('Empty response'); } return resp.data.data.getPublicCollectionDetails; - } catch (error) { - - } + } catch (error) {} }; export const getCollectionByAddress = async ( @@ -119,3 +117,44 @@ export const getVectorId = async (resources: MintIngestorResources, id: string): return vectorId; } catch (error) {} }; + +export const getCollectionOwnerDetails = async (resources: MintIngestorResources, id: string) => { + const url = 'https://api.highlight.xyz:8080/'; + const data = { + operationName: 'GetCollectionCreatorDetails', + variables: { + withEns: true, + collectionId: id, + }, + query: `query GetCollectionCreatorDetails($collectionId: String!, $withEns: Boolean) { + getPublicCollectionDetails(collectionId: $collectionId) { + id + creatorAddresses { + address + name + } + creatorEns + creatorAccountSettings(withEns: $withEns) { + verified + imported + displayAvatar + displayName + walletAddresses + } + } + }`, + }; + + const headers = { + accept: 'application/json', + 'content-type': 'application/json', + }; + + try { + const resp = await resources.fetcher.post(url, data, { headers }); + if (resp.data.errors) { + throw new Error("Error fetching owner"); + } + return resp.data.data.getPublicCollectionDetails; + } catch (error) {} +}; diff --git a/test/ingestors/highlight.test.ts b/test/ingestors/highlight.test.ts index b553d49..2e9eb60 100644 --- a/test/ingestors/highlight.test.ts +++ b/test/ingestors/highlight.test.ts @@ -21,9 +21,7 @@ describe('highlight', function () { { chainId: 8453, contractAddress: '0x0E5DDe3De7cf2761d8a81Ee68F48410425e2dBbA' }, { chainId: 8453, contractAddress: '0xBE96B2572CA0F1ac8ec6323Bc9037AffD270bA7F' }, ], - failureContracts: [ - { chainId: 5000, contractAddress: '0x62F8C536De24ED32611f128f64F6eAbd9b82176c' }, - ], + failureContracts: [{ chainId: 5000, contractAddress: '0x62F8C536De24ED32611f128f64F6eAbd9b82176c' }], }); it('supportsUrl: Returns false for an unsupported URL', async function () { const ingestor = new HighlightIngestor(); @@ -70,6 +68,14 @@ describe('highlight', function () { 'https://img.reservoir.tools/images/v2/base/z9JRSpLYGu7%2BCZoKWtAuANCXTgWBry4OTpgBkNYv7UVX%2FOELQ1B1IQGOoFgJBPmEzWQJa5hKPeiypcjXnSgXVEhZJDeOg9vk5slunBxp8ABMKIlkw3COL8nejLu9cx7f5QrJHJecqNaXIZCHlWY311DY%2F4e9zjeJnyY%2Fvp3J%2FivCSdJShfdu2%2FoCfqed8TvVTrlrElK7Wp8owCwKnZNhaw%3D%3D', ); + if (template.creator) { + expect(template.creator.name).to.equal('Emily Xie'); + expect(template.creator.walletAddress).to.equal('0x591a0b1994e8880215b89c5b9cd8d0738e5c0f1e'); + expect(template.creator.imageUrl).to.equal( + 'https://highlight-creator-assets.highlight.xyz/main/image/91eaf712-b9de-49e4-8674-85f37dd823e0.png', + ); + } + expect(template.marketingUrl).to.equal(url); expect(template.availableForPurchaseStart?.getTime()).to.equal(+new Date('2024-06-05T16:00:00.000Z')); expect(template.availableForPurchaseEnd?.getTime()).to.equal(+new Date('2024-08-31T16:00:00.000Z')); @@ -98,10 +104,16 @@ describe('highlight', function () { 'https://img.reservoir.tools/images/v2/base/z9JRSpLYGu7%2BCZoKWtAuAKM5v2dthdDNgoFYsopVhfXBHjSfVbMXHiaW1XsdogS5oNzhOcvyJcxoIKiiKqHsNxiXyJX%2B%2BppNtkeQvHYCslZTqG21HhITlOtTV8jhhZhOQdWST4CHb1DA%2B5K8ZAHTSu9b0MV4dWJJsqPVJ439DhVcURxmw1fKJ4pAhC3iCwl1DOXK1xnEOnLO0il04rMAPA%3D%3D', ); + if (template.creator) { + expect(template.creator.name).to.equal('RGB'); + expect(template.creator.walletAddress).to.equal('0xf3e2399c5d1c698a6c1dfa195adbd12a6afd1899'); + expect(template.creator.imageUrl).to.equal( + 'https://highlight-creator-assets.highlight.xyz/main/image/47cfb18b-a189-41f2-8013-1cee51c0c08d.png', + ); + } + expect(template.marketingUrl).to.equal(url); expect(template.availableForPurchaseStart?.getTime()).to.equal(+new Date('2024-06-20T17:00:04.000Z')); expect(template.availableForPurchaseEnd?.getTime()).to.equal(+new Date('2030-01-01T00:00:00.000Z')); }); }); - -// TODO Test owner From 1379a045acd3aafa49b4eaeff58b45fc3055eea1 Mon Sep 17 00:00:00 2001 From: Peter Kompasz Date: Fri, 19 Jul 2024 18:21:54 +0300 Subject: [PATCH 5/6] added ingestor name as prefix --- src/ingestors/highlight/index.ts | 20 ++++++++++---------- src/ingestors/highlight/offchain-metadata.ts | 8 ++++---- src/ingestors/highlight/onchain-metadata.ts | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/ingestors/highlight/index.ts b/src/ingestors/highlight/index.ts index 45b9956..f376518 100644 --- a/src/ingestors/highlight/index.ts +++ b/src/ingestors/highlight/index.ts @@ -2,8 +2,8 @@ import { MintContractOptions, MintIngestor, MintIngestorResources } from '../../ import { MintIngestionErrorName, MintIngestorError } from '../../lib/types/mint-ingestor-error'; import { MintInstructionType, MintTemplate } from '../../lib/types/mint-template'; import { MintTemplateBuilder } from '../../lib/builder/mint-template-builder'; -import { getMetadata, getMintPriceInWei } from './onchain-metadata'; -import { getCollectionByAddress, getCollectionById, getCollectionOwnerDetails, getVectorId } from './offchain-metadata'; +import { getHighlightMetadata, getHighlightMintPriceInWei } from './onchain-metadata'; +import { getHighlightCollectionByAddress, getHighlightCollectionById, getHighlightCollectionOwnerDetails, getHighlightVectorId } from './offchain-metadata'; import { MINT_CONTRACT_ABI } from './abi'; const CONTRACT_ADDRESS = '0x8087039152c472Fa74F47398628fF002994056EA'; @@ -15,7 +15,7 @@ export class HighlightIngestor implements MintIngestor { return false; } - const collection = await getCollectionById(resources, id); + const collection = await getHighlightCollectionById(resources, id); if (!collection || collection.chainId !== 8453) { return false; @@ -31,7 +31,7 @@ export class HighlightIngestor implements MintIngestor { if (contractOptions.chainId !== 8453) { return false; } - const collection = await getCollectionByAddress(resources, contractOptions); + const collection = await getHighlightCollectionByAddress(resources, contractOptions); if (!collection) { return false; } @@ -50,7 +50,7 @@ export class HighlightIngestor implements MintIngestor { mintBuilder.setMarketingUrl(contractOptions.url); } - const collection = await getCollectionByAddress(resources, contractOptions); + const collection = await getHighlightCollectionByAddress(resources, contractOptions); if (!collection) { throw new MintIngestorError(MintIngestionErrorName.CouldNotResolveMint, 'Collection not found'); @@ -75,7 +75,7 @@ export class HighlightIngestor implements MintIngestor { throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Error finding creator'); } - const creator = await getCollectionOwnerDetails(resources, collection.highlightCollection.id); + const creator = await getHighlightCollectionOwnerDetails(resources, collection.highlightCollection.id); mintBuilder.setCreator({ name: creator.creatorAccountSettings.displayName, @@ -83,13 +83,13 @@ export class HighlightIngestor implements MintIngestor { imageUrl: creator.creatorAccountSettings.displayAvatar, }); - const vectorId = await getVectorId(resources, collection.highlightCollection.id); + const vectorId = await getHighlightVectorId(resources, collection.highlightCollection.id); if (!vectorId) { throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Id not available'); } - const totalPriceWei = await getMintPriceInWei(vectorId, resources.alchemy); + const totalPriceWei = await getHighlightMintPriceInWei(vectorId, resources.alchemy); if (!totalPriceWei) { throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Price not available'); @@ -104,7 +104,7 @@ export class HighlightIngestor implements MintIngestor { priceWei: totalPriceWei, }); - const metadata = await getMetadata(+vectorId, resources.alchemy); + const metadata = await getHighlightMetadata(+vectorId, resources.alchemy); if (!metadata) { throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Missing timestamps'); @@ -136,7 +136,7 @@ export class HighlightIngestor implements MintIngestor { throw new MintIngestorError(MintIngestionErrorName.CouldNotResolveMint, 'Url error'); } - const collection = await getCollectionById(resources, id); + const collection = await getHighlightCollectionById(resources, id); if (!collection) { throw new MintIngestorError(MintIngestionErrorName.CouldNotResolveMint, 'No such collection'); diff --git a/src/ingestors/highlight/offchain-metadata.ts b/src/ingestors/highlight/offchain-metadata.ts index a808d3b..001cac1 100644 --- a/src/ingestors/highlight/offchain-metadata.ts +++ b/src/ingestors/highlight/offchain-metadata.ts @@ -1,7 +1,7 @@ import { MintContractOptions, MintIngestorResources } from 'src/lib'; import { Collection, CollectionByAddress, CollectionByAddress1 } from './types'; -export const getCollectionById = async ( +export const getHighlightCollectionById = async ( resources: MintIngestorResources, id: string, ): Promise => { @@ -46,7 +46,7 @@ export const getCollectionById = async ( } catch (error) {} }; -export const getCollectionByAddress = async ( +export const getHighlightCollectionByAddress = async ( resources: MintIngestorResources, contractOptioons: MintContractOptions, ): Promise => { @@ -69,7 +69,7 @@ export const getCollectionByAddress = async ( } catch (error) {} }; -export const getVectorId = async (resources: MintIngestorResources, id: string): Promise => { +export const getHighlightVectorId = async (resources: MintIngestorResources, id: string): Promise => { const url = 'https://api.highlight.xyz:8080/'; const headers = { @@ -118,7 +118,7 @@ export const getVectorId = async (resources: MintIngestorResources, id: string): } catch (error) {} }; -export const getCollectionOwnerDetails = async (resources: MintIngestorResources, id: string) => { +export const getHighlightCollectionOwnerDetails = async (resources: MintIngestorResources, id: string) => { const url = 'https://api.highlight.xyz:8080/'; const data = { operationName: 'GetCollectionCreatorDetails', diff --git a/src/ingestors/highlight/onchain-metadata.ts b/src/ingestors/highlight/onchain-metadata.ts index da3e4de..8557637 100644 --- a/src/ingestors/highlight/onchain-metadata.ts +++ b/src/ingestors/highlight/onchain-metadata.ts @@ -9,7 +9,7 @@ const getContract = async (alchemy: Alchemy): Promise => { return contract; }; -export const getMintPriceInWei = async (vectorId: string, alchemy: Alchemy): Promise => { +export const getHighlightMintPriceInWei = async (vectorId: string, alchemy: Alchemy): Promise => { try { const contract = await getContract(alchemy); const data = await contract.functions.getAbridgedVector(vectorId); @@ -24,7 +24,7 @@ export const getMintPriceInWei = async (vectorId: string, alchemy: Alchemy): Pro } }; -export const getMetadata = async ( +export const getHighlightMetadata = async ( vectorId: number, alchemy: Alchemy, ): Promise<{ startTimestamp: number; endTimestamp: number } | undefined> => { From a3735170cb81b261058392f54d499cf4efc70f57 Mon Sep 17 00:00:00 2001 From: Peter Kompasz Date: Tue, 23 Jul 2024 17:29:26 +0300 Subject: [PATCH 6/6] minor changes --- src/ingestors/highlight/index.ts | 9 +++++++-- src/ingestors/highlight/onchain-metadata.ts | 8 +++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ingestors/highlight/index.ts b/src/ingestors/highlight/index.ts index f376518..07ee59a 100644 --- a/src/ingestors/highlight/index.ts +++ b/src/ingestors/highlight/index.ts @@ -3,7 +3,12 @@ import { MintIngestionErrorName, MintIngestorError } from '../../lib/types/mint- import { MintInstructionType, MintTemplate } from '../../lib/types/mint-template'; import { MintTemplateBuilder } from '../../lib/builder/mint-template-builder'; import { getHighlightMetadata, getHighlightMintPriceInWei } from './onchain-metadata'; -import { getHighlightCollectionByAddress, getHighlightCollectionById, getHighlightCollectionOwnerDetails, getHighlightVectorId } from './offchain-metadata'; +import { + getHighlightCollectionByAddress, + getHighlightCollectionById, + getHighlightCollectionOwnerDetails, + getHighlightVectorId, +} from './offchain-metadata'; import { MINT_CONTRACT_ABI } from './abi'; const CONTRACT_ADDRESS = '0x8087039152c472Fa74F47398628fF002994056EA'; @@ -89,7 +94,7 @@ export class HighlightIngestor implements MintIngestor { throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Id not available'); } - const totalPriceWei = await getHighlightMintPriceInWei(vectorId, resources.alchemy); + const totalPriceWei = await getHighlightMintPriceInWei(+vectorId, resources.alchemy); if (!totalPriceWei) { throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Price not available'); diff --git a/src/ingestors/highlight/onchain-metadata.ts b/src/ingestors/highlight/onchain-metadata.ts index 8557637..4ce5524 100644 --- a/src/ingestors/highlight/onchain-metadata.ts +++ b/src/ingestors/highlight/onchain-metadata.ts @@ -9,7 +9,7 @@ const getContract = async (alchemy: Alchemy): Promise => { return contract; }; -export const getHighlightMintPriceInWei = async (vectorId: string, alchemy: Alchemy): Promise => { +export const getHighlightMintPriceInWei = async (vectorId: number, alchemy: Alchemy): Promise => { try { const contract = await getContract(alchemy); const data = await contract.functions.getAbridgedVector(vectorId); @@ -31,9 +31,7 @@ export const getHighlightMetadata = async ( try { const contract = await getContract(alchemy); const metadata = await contract.functions.getAbridgedVector(vectorId); - const { startTimestamp, endTimestamp } = metadata[0] + const { startTimestamp, endTimestamp } = metadata[0]; return { startTimestamp, endTimestamp }; - } catch (error) { - - } + } catch (error) {} };