Skip to content

Instantly share code, notes, and snippets.

@tunnckoCore
Last active October 2, 2025 02:42
Show Gist options
  • Select an option

  • Save tunnckoCore/1039a72e7a9682b785019ecd7bc4412c to your computer and use it in GitHub Desktop.

Select an option

Save tunnckoCore/1039a72e7a9682b785019ecd7bc4412c to your computer and use it in GitHub Desktop.
Fetcher for all 192 ethscribed 0xNekos OG - could output item by item, or a collection manifest
import * as cheerio from "cheerio";
export type Attribute = {
trait_type: string;
value: string;
};
export type CollectionItem = {
id: string;
index: number;
sha: string;
name: string;
description: string;
attributes: Attribute[];
};
export type CollectionMetadata = {
name: string;
logo_image: string | null;
banner_image: string | null;
total_supply: number;
slug: string;
description: string;
website_url: string | null;
twitter_url: string | null;
discord_url: string | null;
background_color: string | null;
collection_items: CollectionItem[];
};
type EthscriptionItem = PrettifyRecursive<{
block_number: number;
block_hash: string;
block_timestamp: number;
transaction_hash: string;
transaction_index: number;
transaction_fee: number;
creator: string;
receiver: string;
content_sha: string;
current_owner: string;
previous_owner: string;
ethscription_number: number;
content_uri: string;
}>;
type PrettifyRecursive<T> = {
[K in keyof T]: T[K] extends object
? T[K] extends infer O
? O extends Date | RegExp | Function
? O
: PrettifyRecursive<O>
: never
: T[K];
} & {};
type EthscriptionsResponse = PrettifyRecursive<{
result: EthscriptionItem[];
pagination: {
has_more: boolean;
page_key?: string;
};
}>;
type Prettify<T> = {
[K in keyof T]: T[K];
};
type Traits = {
background: string;
cat: string;
eyes: string;
cursor: string;
};
type ItemWithMetadata = EthscriptionsResponse["result"][0] & {
traits: Traits;
};
type FinalItems = PrettifyRecursive<{
item: ItemWithMetadata & {
attributes: ERC721MetadataAttributes[];
};
neko: Omit<ItemWithMetadata, "content_uri">;
}>[];
type ERC721MetadataAttributes = {
trait_type: string;
value: string;
};
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
async function fetchAllEthscriptions(): Promise<FinalItems> {
const baseUrl = "https://mainnet.api.calldata.space/ethscriptions";
const baseParams = new URLSearchParams({
reverse: "true",
creator: "0x9d9db340778139774cf73dfb7bf27498fa67978f",
content_type: "text%2Fhtml",
per_page: "100",
with: "content_uri,current_owner,previous_owner,ethscription_number",
only: "block_number,block_timestamp,block_hash,transaction_hash,transaction_index,transaction_fee,ethscription_number,creator,receiver,content_sha",
});
const allItems: FinalItems = [];
let hasMore = true;
let pageKey: string | undefined;
while (hasMore) {
// Build URL with current parameters
const params = new URLSearchParams(baseParams);
if (pageKey) {
params.append("page_key", pageKey);
}
const url = `${baseUrl}?${params.toString()}`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: EthscriptionsResponse = await response.json();
const nekos: FinalItems = await Promise.all(
data.result.map(async (item) => {
item.ethscription_number = Number(item.ethscription_number);
const response = await fetch(item.content_uri);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const html = await response.text();
const $ = cheerio.load(html);
const attributes: ERC721MetadataAttributes[] = [];
let traits: Traits = {} as unknown as Traits;
$(".token-traits .tag").each((_, element) => {
const $tag = $(element);
const key = $tag.find(".trait-key").text().trim().toLowerCase();
const value = $tag.find(".trait-value").text().trim();
// Skip if it's the name tag or if key/value is empty
if (key && value && key !== "license") {
attributes.push({
trait_type: key,
value: value,
});
traits = traits || {};
traits[key] = value;
}
});
const { content_uri, ...neko } = { ...item, traits };
return { item: { ...item, traits, attributes }, neko };
}),
);
// console.log(nekos.map((x) => x.neko));
allItems.push(...nekos);
// Update pagination state
hasMore = data.pagination.has_more;
pageKey = data.pagination.page_key;
// Optional: Add a small delay to be respectful to the API
// if (hasMore) {
// await new Promise(resolve => setTimeout(resolve, 100));
// }
} catch (error) {
console.error("Error fetching ethscriptions:", error);
throw error;
}
}
// console.log(`Finished! Total items fetched: ${allItems.length}`);
return allItems;
}
// Example usage
async function main() {
try {
const allEthscriptions = await fetchAllEthscriptions();
// x.item = includes content_uri
// console.log(
// JSON.stringify(
// allEthscriptions.map((x) => x.neko),
// null,
// 2,
// ),
// );
// let i = 1;
// for await (const { neko } of allEthscriptions) {
// await Bun.write(`./public/metadata/${i}.json`, JSON.stringify(neko));
// i++;
// }
// collection metadata format
// https://github.com/Ethereum-Phunks/curated-metadata
const collectionMetadata: CollectionMetadata = {
total_supply: 192,
name: "0xNekos OG",
slug: "0xnekos-og",
logo_image:
"https://api.ethscriptions.com/v2/ethscriptions/0x7c19b69abdf38cebc6de9a955f4f2887d1815e508098bf4fb347f8d8bfc1834e/data",
banner_image: null,
description:
"Generative art collection of fresh, new, unique and optimized 192 0xNeko Cats as Ethscriptions. Inspired by 1989 game, they were originally 100 free minted as Ethereum NFTs in 2021. Later, the same exact 100 were free minted on Bitcoin Ordinals in April 2023.",
background_color: null,
website_url: null,
discord_url: null,
twitter_url: "https://twitter.com/wgw_eth",
collection_items: allEthscriptions.map((x, index) => {
const item: CollectionItem = {
id: x.item.transaction_hash,
index: index + 1,
sha: x.item.content_sha.replace("0x", ""),
name: `0xNeko OG #${index + 1}`,
description: "",
attributes: x.item.attributes,
};
return item;
}),
};
console.log(JSON.stringify(collectionMetadata, null, 2));
} catch (error) {
console.error("Failed to fetch ethscriptions:", error);
}
}
main();
export {
fetchAllEthscriptions,
type EthscriptionItem,
type EthscriptionsResponse,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment