Skip to content

Instantly share code, notes, and snippets.

@nathankellenicki
Last active October 28, 2022 21:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nathankellenicki/72ba541dea5c8c0994e23ef52f9512ca to your computer and use it in GitHub Desktop.
Save nathankellenicki/72ba541dea5c8c0994e23ef52f9512ca to your computer and use it in GitHub Desktop.
Code for finding unique owners of Hot Wheels NFT Garage series's
import axios from "axios";
import moment from "moment";
const Series = {
"Series 1 Employee Rodger Dodger": ["401460"],
"Series 1": ["353670", "353671", "353672", "353673", "353674", "353675", "353676", "353677", "353678", "353679", "353680", "353681", "353682", "353683", "362065", "353685", "353686", "353687", "353688", "353689", "353690", "353691", "362066", "353693", "353694", "353695", "353696", "353697", "353698", "362067", "353700", "353701", "353702", "353703", "353704", "362068", "353706", "353707", "353708", "353709"],
"Series 2": ["476736", "473693", "473694", "473695", "473696", "473697", "473698", "473699", "473700", "476737", "473702", "473703", "473704", "473705", "473706", "473707", "473708", "473709", "473710", "476738", "473712", "473713", "473714", "473715", "473716", "473717", "473718", "473719", "473720", "476739", "473722", "473723", "473724", "473725", "473726", "473727", "473728", "473729", "473730", "476740", "473732", "473733", "473734"],
"Series 3": ["542222", "542223", "542224", "542225", "542226", "542227", "542228", "542229", "542230", "542231", "542232", "542233", "542234", "542235", "542236", "542237", "542238", "542239", "542240", "542241", "542242", "542243", "542244", "542245", "542246", "542247", "542248", "542249", "542250", "542251", "542252", "542253", "542254", "542255", "542256", "542257", "542258", "542259", "542260", "542261", "542262", "542263", "542264", "542265", "542266", "542267", "542268", "542269", "542270", "542271", "542272", "543001", "542475", "542476"],
"Ultra Hots": ["603912", "603913", "603914", "603915", "603916", "603917"]
};
const Packs = {
"Series 1 4 Pack": "353732",
"Series 1 10 Pack": "353733",
"Series 2 Pack": "477399",
"Series 2 Promo Pack": "473735",
"Series 3 Pack": "542277",
"Series 3 Pack (Re-Mint)": "545272",
"Series 3 Promo Pack": "542472",
"Ultra Hots Pack": "603901"
};
const PackSeriesMapping = {
[Packs["Series 1 4 Pack"]]: ["353670", "353671", "353672", "353673", "353674", "353675", "353676", "353677", "353678", "353679", "353680", "353681", "353682", "353683", "362065", "353685", "353686", "353687", "353688", "353689", "353690", "353691", "362066", "353693", "353694", "353695", "353696", "353697", "353698", "362067", "353700", "353701", "353702", "353703", "353704", "362068", "353706", "353707", "353708", "353709"],
[Packs["Series 1 10 Pack"]]: ["353670", "353671", "353672", "353673", "353674", "353675", "353676", "353677", "353678", "353679", "353680", "353681", "353682", "353683", "362065", "353685", "353686", "353687", "353688", "353689", "353690", "353691", "362066", "353693", "353694", "353695", "353696", "353697", "353698", "362067", "353700", "353701", "353702", "353703", "353704", "362068", "353706", "353707", "353708", "353709"],
[Packs["Series 2 Pack"]]: ["476736", "473693", "473694", "473695", "473696", "473697", "473698", "473699", "473700", "476737", "473702", "473703", "473704", "473705", "473706", "473707", "473708", "473709", "473710", "476738", "473712", "473713", "473714", "473715", "473716", "473717", "473718", "473719", "473720", "476739", "473722", "473723", "473724", "473725", "473726", "473727", "473728", "473729", "473730", "476740", "473732"],
[Packs["Series 2 Promo Pack"]]: ["473733", "473734"],
[Packs["Series 3 Pack"]]: ["542222", "542223", "542224", "542225", "542226", "542227", "542228", "542229", "542230", "542231", "542232", "542233", "542234", "542235", "542236", "542237", "542238", "542239", "542240", "542241", "542242", "542243", "542244", "542245", "542246", "542247", "542248", "542249", "542250", "542251", "542252", "542253", "542254", "542255", "542256", "542257", "542258", "542259", "542260", "542261", "542262", "542263", "542264", "542265", "542266", "542267", "542268", "542269", "542270", "542271", "542272", "543001"],
[Packs["Series 3 Pack (Re-Mint)"]]: ["542222", "542223", "542224", "542225", "542226", "542227", "542228", "542229", "542230", "542231", "542232", "542233", "542234", "542235", "542236", "542237", "542238", "542239", "542240", "542241", "542242", "542243", "542244", "542245", "542246", "542247", "542248", "542249", "542250", "542251", "542252", "542253", "542254", "542255", "542256", "542257", "542258", "542259", "542260", "542261", "542262", "542263", "542264", "542265", "542266", "542267", "542268", "542269", "542270", "542271", "542272", "543001"],
[Packs["Series 3 Promo Pack"]]: ["542475", "542476"],
[Packs["Ultra Hots Pack"]]: ["603912", "603913", "603914", "603915", "603916", "603917"]
};
const lastPulls = {};
const packCounts = {};
const rawData = {};
const getPackStats = async () => {
let pageNum = 1;
let lastPull = new Date();
const stats7Days = {};
while (lastPull > (new Date()).setDate((new Date()).getDate() - 22)) {
const { data } = await axios(`https://wax.api.atomicassets.io/atomicassets/v1/transfers/?template_id=${Object.keys(Packs).map((name) => Packs[name]).join(",")}&recipient=unbox.nft&order=desc&page=${pageNum++}`);
for (const transfer of data.data) {
if (lastPull < (new Date()).setDate((new Date()).getDate() - 22)) {
break;
}
const { template_id: packTemplateId } = transfer.assets[0].template;
stats7Days[packTemplateId] = stats7Days[packTemplateId] || 0;
stats7Days[packTemplateId]++;
lastPull = new Date(parseInt(transfer.created_at_time, 10));
for (const templateId of PackSeriesMapping[packTemplateId]) {
if (lastPulls[templateId] && lastPull > lastPulls[templateId]) {
packCounts[templateId] = packCounts[templateId] || 0;
packCounts[templateId]++;
}
}
}
await new Promise((resolve) => setTimeout(resolve, 300)); // NK: Artificial delay to avoid rate limiting
}
}
const getAccountsWithTemplate = async (seriesId, templateId) => {
const { data } = await axios(`https://wax.api.atomicassets.io/atomicassets/v1/transfers/?template_id=${templateId}&sender=premint.nft&order=desc`);
const lastPull = new Date(parseInt(data.data[0].assets[0].transferred_at_time, 10));
const { cardid, name, rarity, collection } = data.data[0].assets[0].data;
const { max_supply, issued_supply } = data.data[0].assets[0].template;
await new Promise((resolve) => setTimeout(resolve, 300)); // NK: Artificial delay to avoid rate limiting
const accounts = {};
let total = 0;
let pageNum = 1;
let hasMore = true;
while (hasMore) {
const { data } = await axios(`https://wax.api.atomicassets.io/atomicassets/v1/accounts?template_id=${templateId}&page=${pageNum++}`);
if (data.data.length <= 0) {
hasMore = false;
break;
}
for (const item of data.data) {
if (item.account === "premint.nft") { // NK: Exclude premint.nft from owners list
continue;
}
accounts[item.account] = accounts[item.account] || 0;
accounts[item.account] += parseInt(item.assets, 10);
total += parseInt(item.assets, 10);
}
await new Promise((resolve) => setTimeout(resolve, 300)); // NK: Artificial delay to avoid rate limiting
}
lastPulls[templateId] = lastPull;
const percent = ((total / issued_supply) * 100).toFixed(2);
rawData[seriesId].push([cardid,name,collection,rarity,templateId,Object.keys(accounts).length,total,issued_supply,percent,moment(lastPull).format("MM/DD/YYYY HH:mm:ss")]);
return accounts;
}
const getAccountsWithSeries = async (seriesId) => {
rawData[seriesId] = [];
const templateCounts = {};
for (const templateId of Series[seriesId]) {
const templateAccounts = await getAccountsWithTemplate(seriesId, templateId);
for (const account in templateAccounts) {
templateCounts[account] = templateCounts[account] || [];
templateCounts[account].push(templateAccounts[account]);
}
}
const owners = Object.keys(templateCounts).filter((account) => templateCounts[account].length >= Series[seriesId].length);
const counts = {};
for (const account of owners) {
counts[account] = Math.min(...templateCounts[account]);
}
return counts;
}
const getTotalSetsFromAccounts = (accounts) => {
return Object.keys(accounts).reduce((prev, key) => prev + accounts[key], 0);
}
const getMasterSets = (accountsArray) => {
const accounts = [];
accountsArray.sort((a, b) => {
if (a.length < b.length) {
return 1;
}
if (a.length > b.length) {
return -1;
}
return 0;
});
for (const account of Object.keys(accountsArray[0])) {
let exists = true;
for (let i = 1; i < accountsArray.length; i++) {
if (!accountsArray[i][account]) {
exists = false;
break;
}
}
if (exists) {
accounts.push(account);
}
}
return accounts;
}
(async () => {
const querySeries = ["Series 1", "Series 2", "Series 3", "Ultra Hots"];
const seriesResults = [];
for (const series of querySeries) {
const accounts = await getAccountsWithSeries(series);
seriesResults.push(accounts);
}
await getPackStats();
for (const seriesId in rawData) {
console.log("========");
console.log(seriesId);
console.log("========");
console.log("CardId,Name,Collection,Rarity,TemplateId,UserCount,TotalCount,IssuedCount,PulledPercentage,LastPull,PacksSinceLastPull");
for (const item of rawData[seriesId]) {
if (packCounts[item[4]]) {
item.push(packCounts[item[4]]);
} else {
item.push(0);
}
console.log(`${item[0]},${item[1]},${item[2]},${item[3]},${item[4]},${item[5]},${item[6]},${item[7]},${item[8]}%,${item[9]},${item[10]}`);
}
}
console.log("========");
console.log("Series,UserCount,SetsCount");
for (const series in querySeries) {
const accounts = seriesResults[series];
const sets = getTotalSetsFromAccounts(accounts);
console.log(`${querySeries[series]},${Object.keys(accounts).length},${sets}`);
}
console.log("========");
console.log("Series,UserCount")
console.log(`Master Set,${getMasterSets(seriesResults.slice(0, 3)).length}`);
console.log(`Master Set (with Ultra hots),${getMasterSets(seriesResults).length}`);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment