Created
October 16, 2024 06:44
-
-
Save crypt0miester/6391ae0df3c6edb9ea1635ebbf7da5d7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
ANS_PROGRAM_ID, | |
findNameHouse, | |
findNftRecord, | |
getHashedName, | |
getNameAccountKeyWithBump, | |
getParentAccountFromTldHouseAccountInfo, | |
getTldFromTldHouseAccountInfo, | |
NameRecordHeader, | |
NftRecord, | |
TLD_HOUSE_PROGRAM_ID, | |
} from "@onsol/tldparser"; | |
import { | |
Connection, | |
GetProgramAccountsResponse, | |
PublicKey, | |
} from "@solana/web3.js"; | |
import pLimit from "p-limit"; | |
const connection = new Connection("https://mainnetbeta-rpc.eclipse.xyz/"); | |
export async function getAllTld(connection: Connection): Promise< | |
Array<{ | |
tldHouse: PublicKey; | |
tld: String; | |
parentAccount: PublicKey; | |
}> | |
> { | |
const tldHouseDiscriminator = [247, 144, 135, 1, 238, 173, 19, 249]; | |
const filters: any = [ | |
{ | |
memcmp: { | |
offset: 0, | |
bytes: tldHouseDiscriminator, | |
}, | |
}, | |
]; | |
const accounts = await connection.getProgramAccounts(TLD_HOUSE_PROGRAM_ID, { | |
filters: filters, | |
}); | |
const tldsAndParentAccounts: { | |
tldHouse: PublicKey; | |
tld: String; | |
parentAccount: PublicKey; | |
}[] = []; | |
accounts.map(({ pubkey, account }) => { | |
const parentAccount = getParentAccountFromTldHouseAccountInfo(account); | |
const tld = getTldFromTldHouseAccountInfo(account); | |
tldsAndParentAccounts.push({ tldHouse: pubkey, tld, parentAccount }); | |
}); | |
return tldsAndParentAccounts; | |
} | |
async function findAllDomainsForTld( | |
connection: Connection, | |
parentAccount: PublicKey, | |
): Promise<{ pubkey: PublicKey; nameRecordHeader: NameRecordHeader }[]> { | |
const filters: any = [ | |
{ | |
memcmp: { | |
offset: 8, | |
bytes: parentAccount.toBase58(), | |
}, | |
}, | |
]; | |
const accounts: GetProgramAccountsResponse = | |
await connection.getProgramAccounts(ANS_PROGRAM_ID, { | |
filters: filters, | |
}); | |
return accounts.map((a) => { | |
return { | |
pubkey: a.pubkey, | |
nameRecordHeader: NameRecordHeader.fromAccountInfo(a.account), | |
}; | |
}); | |
} | |
export async function performReverseLookupBatchedTurbo( | |
connection: Connection, | |
nameAccounts: PublicKey[], | |
tldHouse: PublicKey, | |
): Promise<(string | undefined)[]> { | |
let reverseLookupDomains: (string | undefined)[] = []; | |
const getReverseLookUpAccounts = async ( | |
accounts: PublicKey[], | |
): Promise<PublicKey[]> => { | |
const promises = accounts.map(async (nameAccount) => { | |
const reverseLookupHashedName = await getHashedName( | |
nameAccount.toBase58(), | |
); | |
const [reverseLookUpAccount] = await getNameAccountKeyWithBump( | |
reverseLookupHashedName, | |
tldHouse, | |
undefined, | |
); | |
return reverseLookUpAccount; | |
}); | |
return Promise.all(promises); | |
}; | |
const batches = []; | |
for (let i = 0; i < nameAccounts.length; i += 100) { | |
batches.push(nameAccounts.slice(i, i + 100)); | |
} | |
const limit = pLimit(20); | |
const batchResults = await Promise.all( | |
batches.map((batch) => | |
limit(async () => { | |
const reverseLookUpAccounts = await getReverseLookUpAccounts(batch); | |
const reverseLookupAccountInfos = | |
await connection.getMultipleAccountsInfo(reverseLookUpAccounts); | |
const batchDomains = reverseLookupAccountInfos.map( | |
(reverseLookupAccountInfo) => { | |
const domain = reverseLookupAccountInfo?.data | |
.subarray(200, reverseLookupAccountInfo?.data.length) | |
.toString(); | |
return domain; | |
}, | |
); | |
return batchDomains; | |
}), | |
), | |
); | |
reverseLookupDomains = batchResults.flat(); | |
return reverseLookupDomains; | |
} | |
type DomainType = { | |
nameAccount: string; | |
domain: string; | |
owner?: string; | |
expiresAt: Date; | |
createdAt: Date; | |
}; | |
// onlyDomains will grab only domains and no nfts | |
async function getAllRegisteredDomainsV2( | |
// specify TLD | |
tldExpected?: string, | |
// ignores NFTs | |
onlyDomains: boolean = false, | |
) { | |
// get all TLDs | |
const allTlds = await getAllTld(connection); | |
const domains: DomainType[] = []; | |
const nftDomains: DomainType[] = []; | |
for (const tld of allTlds) { | |
if (tldExpected && tld.tld !== tldExpected) continue; | |
// get all name accounts in a specific TLD | |
const allNameAccountsForTld = await findAllDomainsForTld( | |
connection, | |
tld.parentAccount, | |
); | |
// await setTimeout(50); | |
const [nameHouseAccount] = findNameHouse(tld.tldHouse); | |
const nameAccountPubkeys = allNameAccountsForTld.map((a) => a.pubkey); | |
const domainsReverse = await performReverseLookupBatchedTurbo( | |
connection, | |
nameAccountPubkeys, | |
tld.tldHouse, | |
); | |
for (let index = 0; index < domainsReverse.length; index++) { | |
const domain = domainsReverse[index]; | |
if (!domain) continue; | |
const [nftRecord] = findNftRecord( | |
allNameAccountsForTld[index].pubkey, | |
nameHouseAccount, | |
); | |
const finalOwner = | |
allNameAccountsForTld[index].nameRecordHeader.owner?.toString(); | |
if (finalOwner === nftRecord.toString()) { | |
nftDomains.push({ | |
nameAccount: allNameAccountsForTld[index].pubkey.toString(), | |
domain: `${domain}${tld.tld}`, | |
owner: finalOwner, | |
expiresAt: allNameAccountsForTld[index].nameRecordHeader.expiresAt, | |
createdAt: allNameAccountsForTld[index].nameRecordHeader.createdAt, | |
}); | |
} else if (!onlyDomains) { | |
domains.push({ | |
nameAccount: allNameAccountsForTld[index].pubkey.toString(), | |
domain: `${domain}${tld.tld}`, | |
owner: finalOwner, | |
expiresAt: allNameAccountsForTld[index].nameRecordHeader.expiresAt, | |
createdAt: allNameAccountsForTld[index].nameRecordHeader.createdAt, | |
}); | |
} | |
} | |
} | |
await processNftDomains(nftDomains); | |
return [...domains, ...nftDomains]; | |
} | |
async function processNftDomains(nftDomains: DomainType[]) { | |
const limit = pLimit(20); | |
const batches = []; | |
for (let i = 0; i < nftDomains.length; i += 100) { | |
batches.push(nftDomains.slice(i, i + 100)); | |
} | |
const batchResults = await Promise.all( | |
batches.map((batch) => | |
limit(async () => { | |
const nftRecordBatch = batch.map( | |
(batchData) => new PublicKey(batchData.owner!), | |
); | |
const nftRecordBatchAccountsInfo = | |
await connection.getMultipleAccountsInfo(nftRecordBatch); | |
const batchDomains = nftRecordBatchAccountsInfo.map( | |
(nftRecordBatchAccountInfo) => { | |
if (!nftRecordBatchAccountInfo) return undefined; | |
const nftRecordData = NftRecord.fromAccountInfo( | |
nftRecordBatchAccountInfo, | |
)[0]; | |
return nftRecordData; | |
}, | |
); | |
return batchDomains; | |
}), | |
), | |
); | |
const nftRecordsData = batchResults.flat(); | |
const domains = await Promise.all( | |
nftDomains.map((domain, index) => | |
limit(async () => { | |
let domainOwner = domain.owner; | |
let nftRecordData = nftRecordsData[index]; | |
if (!domainOwner || !nftRecordData) { | |
return domain; | |
} | |
const largestAccounts = await connection.getTokenLargestAccounts( | |
nftRecordData.nftMintAccount, | |
); | |
if (largestAccounts.value.length > 0) { | |
const largestAccountInfo = await connection.getParsedAccountInfo( | |
largestAccounts.value[0].address, | |
); | |
if (largestAccountInfo?.value?.data) { | |
domain.owner = new PublicKey( | |
// @ts-ignore | |
largestAccountInfo.value.data.parsed.info.owner, | |
).toString(); | |
} | |
} | |
// await setTimeout(50); | |
// console.log(domainOwner) | |
return domain; | |
}), | |
), | |
); | |
return domains; | |
} | |
async function main() { | |
const domains = await getAllRegisteredDomainsV2(".turbo"); | |
console.log(JSON.stringify(domains)); | |
console.log(domains.length); | |
} | |
// get all domains registered on AllDomains | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment