Skip to content

Instantly share code, notes, and snippets.

@crypt0miester
Created November 22, 2024 06:36
Show Gist options
  • Save crypt0miester/c0b75b6c7fd2c7394d24ad354f077212 to your computer and use it in GitHub Desktop.
Save crypt0miester/c0b75b6c7fd2c7394d24ad354f077212 to your computer and use it in GitHub Desktop.
/**
* @module all-domain-service
* utility functions for managing svm domain names
*/
import {
ANS_PROGRAM_ID,
TLD_HOUSE_PROGRAM_ID,
getHashedName,
NameRecordHeader,
getNameAccountKeyWithBump,
} from "@onsol/tldparser";
import {
AccountInfo,
Connection,
GetProgramAccountsResponse,
PublicKey,
} from "@solana/web3.js";
import { BN } from "bn.js";
import pLimit from "p-limit";
// types for improved type safety
export interface TldHouseData {
pubkey?: PublicKey;
tld?: string;
parentKey?: PublicKey;
}
export interface NameRecordWithTldData {
nameAccount: PublicKey;
domain: string | undefined;
tldHouse: PublicKey | undefined;
parentName: PublicKey | undefined;
tld: string | undefined;
owner: PublicKey | undefined;
expiresAt: Date;
createdAt: Date;
nonTransferable: boolean;
name: string | undefined;
}
/**
* extracts tld from tld house account data
* @param tldHouseData - raw account info buffer
* @returns extracted tld string
*/
export function getTldFromTldHouseAccountInfo(
tldHouseData: AccountInfo<Buffer>,
): string {
// offset past discriminator and keys
const tldStart = 8 + 32 + 32 + 32;
const tldBuffer = tldHouseData?.data?.subarray(tldStart);
const nameLength = new BN(tldBuffer?.subarray(0, 4), "le").toNumber();
return tldBuffer
.subarray(4, 4 + nameLength)
.toString()
.replace(/\0.*$/g, "");
}
/**
* extracts parent account from tld house data
* @param tldHouseData - raw account info buffer
* @returns parent account public key
*/
export function getParentAccountFromTldHouseAccountInfo(
tldHouseData: AccountInfo<Buffer>,
): PublicKey {
const parentAccountStart = 8 + 32 + 32;
const parentAccountBuffer = tldHouseData?.data?.subarray(
parentAccountStart,
parentAccountStart + 32,
);
return new PublicKey(parentAccountBuffer);
}
/**
* fetches all tld houses from the solana network
* @param connection - solana connection instance
* @returns array of tld house data
*/
export const getAllTldHouses = async (
connection: Connection,
): Promise<TldHouseData[]> => {
// filter for tld house program accounts
const filters = [
{
memcmp: {
offset: 0,
bytes: "iQgos3SdaVE",
},
},
];
const allTldHouses = await connection.getProgramAccounts(
TLD_HOUSE_PROGRAM_ID,
{ filters },
);
return allTldHouses.map((data) => {
try {
const tld = getTldFromTldHouseAccountInfo(data.account);
const parentKey = getParentAccountFromTldHouseAccountInfo(data.account);
return { pubkey: data.pubkey, tld, parentKey };
} catch (error) {
console.debug(
"failed to parse tld house data for",
data.pubkey.toString(),
);
return { pubkey: undefined, tld: undefined, parentKey: undefined };
}
});
};
/**
* combines name accounts with their corresponding tld house data
* @param nameAccounts - raw name account data
* @param allTldHousesData - processed tld house data
* @returns combined name records with tld data
*/
const insertTldHouseIntoData = (
nameAccounts: GetProgramAccountsResponse,
allTldHousesData: TldHouseData[],
): (NameRecordWithTldData | undefined)[] => {
const allNameAccountsWithData = nameAccounts.map((account) => ({
pubkey: account.pubkey,
nameRecordData: NameRecordHeader.fromAccountInfo(account.account),
}));
return allNameAccountsWithData
.map((nameAccount, index) => {
// find matching tld house by parent key
const tldHouseData = allTldHousesData.find(
(tldHouse) =>
tldHouse.parentKey?.toString() ===
nameAccount.nameRecordData.parentName.toString(),
);
if (
tldHouseData &&
isDomainValid(nameAccount.nameRecordData.expiresAt)
) {
return {
nameAccount: nameAccounts[index].pubkey,
tldHouse: tldHouseData.pubkey,
domain: undefined,
parentName: tldHouseData.parentKey,
tld: tldHouseData.tld,
owner: nameAccount.nameRecordData.owner,
expiresAt: nameAccount.nameRecordData.expiresAt,
createdAt: nameAccount.nameRecordData.createdAt,
nonTransferable: nameAccount.nameRecordData.nonTransferable,
name: undefined,
};
}
return undefined;
})
.filter(Boolean);
};
/**
* performs batched reverse lookup for domain names
* @param connection - solana connection instance
* @param nameAccountsWithData - name records with tld data
* @returns enriched name records with domain names
*/
export async function performReverseLookupBatchedTurbo(
connection: Connection,
nameAccountsWithData: (NameRecordWithTldData | undefined)[],
): Promise<(NameRecordWithTldData | undefined)[]> {
// helper to get reverse lookup accounts
const getReverseLookUpAccounts = async (
accounts: (NameRecordWithTldData | undefined)[],
): Promise<(PublicKey | undefined)[]> => {
const promises = accounts.map(async (account) => {
if (!account) return;
const reverseLookupHashedName = await getHashedName(
account.nameAccount.toBase58(),
);
const [reverseLookUpAccount] = getNameAccountKeyWithBump(
reverseLookupHashedName,
account.tldHouse,
undefined,
);
return reverseLookUpAccount;
});
return Promise.all(promises);
};
// process in batches of 100
const batches = [];
for (let i = 0; i < nameAccountsWithData.length; i += 100) {
batches.push(nameAccountsWithData.slice(i, i + 100));
}
// limit concurrent requests
const limit = pLimit(20);
const batchResults = await Promise.all(
batches.map((batch) =>
limit(async () => {
const reverseLookUpAccounts = await getReverseLookUpAccounts(batch);
if (reverseLookUpAccounts.includes(undefined)) {
throw new Error("invalid reverse lookup accounts");
}
const reverseLookupAccountInfos =
await connection.getMultipleAccountsInfo(
reverseLookUpAccounts as PublicKey[],
);
return reverseLookupAccountInfos.map((info) =>
info?.data.subarray(200, info.data.length).toString(),
);
}),
),
);
const reverseLookupDomains = batchResults.flat();
// enrich name accounts with domain data
return nameAccountsWithData.map((nameAccountWithData, index) => {
if (!nameAccountWithData) return nameAccountWithData;
const domain = reverseLookupDomains[index];
return {
...nameAccountWithData,
domain,
name: domain ? `${domain}${nameAccountWithData.tld}` : undefined,
};
});
}
/**
* checks if a domain is expired (including grace period)
* @param expiryDate - domain expiry date
* @returns boolean indicating if domain is valid
*/
export function isDomainValid(expiryDate: Date): boolean {
if (!expiryDate) return true;
// permanent domain
if (expiryDate.getTime() === 0) return true;
const oneDay = 1000 * 3600 * 24;
const gracePeriod = oneDay * 50; // 50 days grace period
return expiryDate.getTime() + gracePeriod >= new Date().getTime();
}
/**
* fetches all domains owned by a user
* @param connection - solana connection instance
* @param owner - owner's public key
* @returns array of name records with full domain data
*/
async function getAllUserDomains(
connection: Connection,
owner: PublicKey,
): Promise<(NameRecordWithTldData | undefined)[]> {
// filter for owner's accounts
const filters = [
{
memcmp: {
offset: 40,
bytes: owner.toString(),
},
},
];
const allNameAccountsRaw = await connection.getProgramAccounts(
ANS_PROGRAM_ID,
{ filters },
);
const allTldHouses = await getAllTldHouses(connection);
let nameAccountsWithData = insertTldHouseIntoData(
allNameAccountsRaw,
allTldHouses,
);
return performReverseLookupBatchedTurbo(connection, nameAccountsWithData);
}
/**
* fetches domains for a specific user
* @param userPubkey - user's public key
* @returns array of user's domain records
*/
export async function getUserDomain(
userPubkey: PublicKey,
): Promise<(NameRecordWithTldData | undefined)[]> {
const connection = new Connection("");
const nameAccounts = await getAllUserDomains(connection, userPubkey);
return nameAccounts;
}
// Run the function to get the domains
// getUserDomain(
// new PublicKey("2EGGxj2qbNAJNgLCPKca8sxZYetyTjnoRspTPjzN2D67"),
// ).catch(console.error);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment