```typescript import { ExtractionWorkerHandler } from '@neume-network/extraction-worker'; import { decodeLog, toHex, encodeFunctionSignature, decodeParameters } from 'eth-fun'; import { callTokenUri } from '../components/call-tokenuri.js'; import { fetchTokenUri } from '../components/fetch-tokenuri.js'; import { callOwner } from '../components/call-owner.js'; import { getArweaveTokenUri } from '../components/get-arweave-tokenuri.js'; import { Config, JsonRpcLog, NFT } from '../types.js'; import { Strategy } from './strategy.types.js'; import { randomItem, ifIpfsConvertToNativeIpfs } from '../utils.js'; // Instead of querying at the block number soundxyz NFT // was minted, we query at a higher block number because // soundxyz changed their tokenURI and the previous one // doesn't work anymore. https://github.com/neume-network/data/issues/19 // // createdAtBlock 13725566 (https://etherscan.io/tx/0xfa325f74eb6c7f5e6bb60a264404543d6158f79de01bc5aab35180354e554dce) // workingAfterBlockNumber 15050010 export default class Sound implements Strategy { public static version = '1.0.0'; public static createdAtBlock = 15050010; public static deprecatedAtBlock = null; public static invalidIDs = []; private worker: ExtractionWorkerHandler; private config: Config; constructor(worker: ExtractionWorkerHandler, config: Config) { this.worker = worker; this.config = config; } filterContracts = async (from: number, to: number) => { const artistCreatedSelector = '0x23748b43b77f98380e738976c6324996908ffc1989994dd3c68631c87a65a7c0'; const rpcHost = randomItem(this.config.rpc); const options = { url: rpcHost.url, headers: { ...(rpcHost.key && { Authorization: `Bearer ${rpcHost.key}` }), }, retry: { retries: 3, }, }; const fromBlock = toHex(from); const toBlock = toHex(to); const message = await this.worker({ type: 'json-rpc', method: 'eth_getLogs', commissioner: Sound.name, params: [ { fromBlock, toBlock, topics: [[artistCreatedSelector]], }, ], version: '0.0.1', options, }); if (message.error) { throw new Error( `Error occured while filtering ${Sound.name} contracts: \n${JSON.stringify( message, null, 2, )}`, ); } const logs = message.results as any as Array<JsonRpcLog>; return logs.map((log) => { const topics = log.topics; topics.shift(); const result = decodeLog( [ { type: 'uint256', name: 'artistId', }, { type: 'string', name: 'name', }, { type: 'string', name: 'symbol', }, { type: 'address', name: 'artistAddress', indexed: true, }, ], log.data, topics, ); return { address: result.artistAddress.toLowerCase(), name: Sound.name, version: Sound.version, }; }); }; crawl = async (nft: NFT) => { // if ( // Sound.invalidIDs.filter((id) => `${nft.erc721.address}/${nft.erc721.token.id}`.match(id)) // .length != 0 // ) { // console.log( // `Ignoring ${nft.erc721.address}/${nft.erc721.token.id} because it is blacklisted`, // ); // return null; // } nft.erc721.token.uri = await callTokenUri( this.worker, this.config, nft.erc721.blockNumber, nft, ); nft.erc721.token.uriContent = await fetchTokenUri(nft.erc721.token.uri, this.worker); nft.creator = await callOwner( this.worker, this.config, nft.erc721.address, nft.erc721.blockNumber, ); const datum = nft.erc721.token.uriContent; let duration; if (datum?.duration) { duration = `PT${Math.floor(datum.duration / 60)}M${(datum.duration % 60).toFixed(0)}S`; } return { version: Sound.version, title: datum.name, artist: { version: Sound.version, name: datum.artist_name, address: nft.creator, }, platform: { version: Sound.version, name: 'Sound', uri: 'https://sound.xyz', }, erc721: { version: Sound.version, createdAt: nft.erc721.blockNumber, // TODO: Stop hard coding this value owner: '0x4456AE02EA5534cEd3A151e41a715bBA685A7CAb', address: nft.erc721.address, tokenId: nft.erc721.token.id, tokenURI: nft.erc721.token.uri, metadata: { ...datum, name: datum.name, description: datum.description, image: datum.image, }, }, manifestations: [ { version: Sound.version, uri: ifIpfsConvertToNativeIpfs(datum.audio_url), mimetype: 'audio', }, { version: Sound.version, uri: ifIpfsConvertToNativeIpfs(datum.image), mimetype: 'image', }, { version: Sound.version, uri: ifIpfsConvertToNativeIpfs(datum.animation_url), mimetype: 'image', }, ], }; }; updateOwner(nft: NFT) {} } ```