```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) {}
}
```