diff --git a/js/src/bindings/registerFavorite.ts b/js/src/bindings/registerFavorite.ts index 073e64dc..b0b01a32 100644 --- a/js/src/bindings/registerFavorite.ts +++ b/js/src/bindings/registerFavorite.ts @@ -1,6 +1,8 @@ -import { PublicKey, SystemProgram } from "@solana/web3.js"; +import { Connection, PublicKey, SystemProgram } from "@solana/web3.js"; import { registerFavoriteInstruction } from "../instructions/registerFavoriteInstruction"; import { FavouriteDomain, NAME_OFFERS_ID } from "../favorite-domain"; +import { NameRegistryState } from "../state"; +import { ROOT_DOMAIN_ACCOUNT } from "../constants"; /** * This function can be used to register a domain name as favorite @@ -9,14 +11,30 @@ import { FavouriteDomain, NAME_OFFERS_ID } from "../favorite-domain"; * @param programId The name offer program ID * @returns */ -export const registerFavorite = (nameAccount: PublicKey, owner: PublicKey) => { - const [favKey] = FavouriteDomain.getKeySync(NAME_OFFERS_ID, owner); +export const registerFavorite = async ( + connection: Connection, + nameAccount: PublicKey, + owner: PublicKey, +) => { + let parent: PublicKey | undefined = undefined; + const { registry } = await NameRegistryState.retrieve( + connection, + nameAccount, + ); + if (!registry.parentName.equals(ROOT_DOMAIN_ACCOUNT)) { + parent = registry.parentName; + } + + const [favKey] = await FavouriteDomain.getKey(NAME_OFFERS_ID, owner); const ix = new registerFavoriteInstruction().getInstruction( NAME_OFFERS_ID, nameAccount, favKey, owner, SystemProgram.programId, + parent, ); return ix; }; + +export { registerFavorite as setPrimaryDomain }; diff --git a/js/src/favorite-domain.ts b/js/src/favorite-domain.ts index 2e758e69..0be66d01 100644 --- a/js/src/favorite-domain.ts +++ b/js/src/favorite-domain.ts @@ -11,6 +11,7 @@ import { reverseLookup } from "./utils/reverseLookup"; import { FavouriteDomainNotFoundError } from "./error"; import { getDomainMint } from "./nft/getDomainMint"; import { NameRegistryState } from "./state"; +import { NAME_PROGRAM_ID, ROOT_DOMAIN_ACCOUNT } from "./constants"; export const NAME_OFFERS_ID = new PublicKey( "85iDfUvr3HJyLM2zcq5BXSiDvUWfw6cSE1FfNBo8Ap29", @@ -83,6 +84,8 @@ export class FavouriteDomain { } } +export { FavouriteDomain as PrimaryDomain }; + /** * This function can be used to retrieve the favorite domain of a user * @param connection The Solana RPC connection object @@ -97,16 +100,26 @@ export const getFavoriteDomain = async ( NAME_OFFERS_ID, new PublicKey(owner), ); - const favorite = await FavouriteDomain.retrieve(connection, favKey); - - const reverse = await reverseLookup(connection, favorite.nameAccount); const { registry, nftOwner } = await NameRegistryState.retrieve( connection, favorite.nameAccount, ); const domainOwner = nftOwner || registry.owner; + let reverse = await reverseLookup( + connection, + favorite.nameAccount, + registry.parentName.equals(ROOT_DOMAIN_ACCOUNT) + ? undefined + : registry.parentName, + ); + + if (!registry.parentName.equals(ROOT_DOMAIN_ACCOUNT)) { + const parentReverse = await reverseLookup(connection, registry.parentName); + reverse += `.${parentReverse}`; + } + return { domain: favorite.nameAccount, reverse, @@ -114,6 +127,8 @@ export const getFavoriteDomain = async ( }; }; +export { getFavoriteDomain as getPrimaryDomain }; + /** * This function can be used to retrieve the favorite domains for multiple wallets, up to a maximum of 100. * If a wallet does not have a favorite domain, the result will be 'undefined' instead of the human readable domain as a string. @@ -139,22 +154,38 @@ export const getMultipleFavoriteDomains = async ( return PublicKey.default; }, ); - const revKeys = favDomains.map((e) => getReverseKeyFromDomainKey(e)); + + const domainInfos = await connection.getMultipleAccountsInfo(favDomains); + const parentRevKeys: PublicKey[] = []; + const revKeys = domainInfos.map((e, idx) => { + const parent = new PublicKey(e?.data.slice(0, 32) ?? Buffer.alloc(32)); + const isSub = + e?.owner.equals(NAME_PROGRAM_ID) && !parent.equals(ROOT_DOMAIN_ACCOUNT); + parentRevKeys.push( + isSub ? getReverseKeyFromDomainKey(parent) : PublicKey.default, + ); + return getReverseKeyFromDomainKey( + favDomains[idx], + isSub ? parent : undefined, + ); + }); const atas = favDomains.map((e, idx) => { const mint = getDomainMint(e); const ata = getAssociatedTokenAddressSync(mint, wallets[idx], true); return ata; }); - const [domainInfos, revs, tokenAccs] = await Promise.all([ - connection.getMultipleAccountsInfo(favDomains), + const [revs, tokenAccs, parentRevs] = await Promise.all([ connection.getMultipleAccountsInfo(revKeys), connection.getMultipleAccountsInfo(atas), + connection.getMultipleAccountsInfo(parentRevKeys), ]); for (let i = 0; i < wallets.length; i++) { + let parentRev = ""; const domainInfo = domainInfos[i]; const rev = revs[i]; + const parentRevAccount = parentRevs[i]; const tokenAcc = tokenAccs[i]; if (!domainInfo || !rev) { @@ -162,10 +193,15 @@ export const getMultipleFavoriteDomains = async ( continue; } + if (parentRevAccount && parentRevAccount.owner.equals(NAME_PROGRAM_ID)) { + const des = deserializeReverse(parentRevAccount.data.slice(96)); + parentRev += `.${des}`; + } + const nativeOwner = new PublicKey(domainInfo?.data.slice(32, 64)); if (nativeOwner.equals(wallets[i])) { - result.push(deserializeReverse(rev?.data.slice(96))); + result.push(deserializeReverse(rev?.data.slice(96)) + parentRev); continue; } // Either tokenized or stale @@ -177,7 +213,7 @@ export const getMultipleFavoriteDomains = async ( const decoded = AccountLayout.decode(tokenAcc.data); // Tokenized if (Number(decoded.amount) === 1) { - result.push(deserializeReverse(rev?.data.slice(96))); + result.push(deserializeReverse(rev?.data.slice(96)) + parentRev); continue; } @@ -187,3 +223,5 @@ export const getMultipleFavoriteDomains = async ( return result; }; + +export { getMultipleFavoriteDomains as getMultiplePrimaryDomains }; diff --git a/js/src/instructions/registerFavoriteInstruction.ts b/js/src/instructions/registerFavoriteInstruction.ts index 0d2a1faa..8cdcd41f 100644 --- a/js/src/instructions/registerFavoriteInstruction.ts +++ b/js/src/instructions/registerFavoriteInstruction.ts @@ -22,6 +22,7 @@ export class registerFavoriteInstruction { favouriteAccount: PublicKey, owner: PublicKey, systemProgram: PublicKey, + optParent?: PublicKey, ): TransactionInstruction { const data = Buffer.from(this.serialize()); let keys: AccountKey[] = []; @@ -45,6 +46,13 @@ export class registerFavoriteInstruction { isSigner: false, isWritable: false, }); + if (!!optParent) { + keys.push({ + pubkey: optParent, + isSigner: false, + isWritable: false, + }); + } return new TransactionInstruction({ keys, programId, diff --git a/js/src/utils/deserializeReverse.ts b/js/src/utils/deserializeReverse.ts index 253f0ebc..eab71a98 100644 --- a/js/src/utils/deserializeReverse.ts +++ b/js/src/utils/deserializeReverse.ts @@ -8,5 +8,8 @@ export function deserializeReverse( ): string | undefined { if (!data) return undefined; const nameLength = data.slice(0, 4).readUInt32LE(0); - return data.slice(4, 4 + nameLength).toString(); + return data + .slice(4, 4 + nameLength) + .toString() + .replace(/\0/g, ""); } diff --git a/js/src/utils/reverseLookup.ts b/js/src/utils/reverseLookup.ts index 39ce3803..7d7ab96a 100644 --- a/js/src/utils/reverseLookup.ts +++ b/js/src/utils/reverseLookup.ts @@ -1,11 +1,8 @@ import { Connection, PublicKey } from "@solana/web3.js"; import { NameRegistryState } from "../state"; -import { REVERSE_LOOKUP_CLASS } from "../constants"; import { NoAccountDataError } from "../error"; - -import { getHashedNameSync } from "./getHashedNameSync"; -import { getNameAccountKeySync } from "./getNameAccountKeySync"; import { deserializeReverse } from "./deserializeReverse"; +import { getReverseKeyFromDomainKey } from "./getReverseKeyFromDomainKey"; /** * This function can be used to perform a reverse look up @@ -16,17 +13,11 @@ import { deserializeReverse } from "./deserializeReverse"; export async function reverseLookup( connection: Connection, nameAccount: PublicKey, + parent?: PublicKey, ): Promise { - const hashedReverseLookup = getHashedNameSync(nameAccount.toBase58()); - const reverseLookupAccount = getNameAccountKeySync( - hashedReverseLookup, - REVERSE_LOOKUP_CLASS, - ); + const reverseKey = getReverseKeyFromDomainKey(nameAccount, parent); - const { registry } = await NameRegistryState.retrieve( - connection, - reverseLookupAccount, - ); + const { registry } = await NameRegistryState.retrieve(connection, reverseKey); if (!registry.data) { throw new NoAccountDataError("The registry data is empty"); } diff --git a/js/tests/favorite.test.ts b/js/tests/favorite.test.ts index 07ad3a2f..f34b630c 100644 --- a/js/tests/favorite.test.ts +++ b/js/tests/favorite.test.ts @@ -30,6 +30,14 @@ test("Favorite domain", async () => { stale: false, }, }, + { + user: new PublicKey("A41TAGFpQkFpJidLwH37ydunE7Q3jpBaS228RkoXiRQk"), + favorite: { + domain: new PublicKey("BaQq8Uib3Aw5SPBedC8MdYCvpfEC9iLkUMHc5M74sAjv"), + reverse: "1.00728", + stale: false, + }, + }, ]; for (let item of items) { const fav = await getFavoriteDomain(connection, item.user); @@ -59,6 +67,10 @@ test("Multiple favorite domains", async () => { wallet: new PublicKey("36Dn3RWhB8x4c83W6ebQ2C2eH9sh5bQX2nMdkP2cWaA4"), domain: "fav-tokenized", }, + { + wallet: new PublicKey("A41TAGFpQkFpJidLwH37ydunE7Q3jpBaS228RkoXiRQk"), + domain: "1.00728", + }, ]; const result = await getMultipleFavoriteDomains( connection, @@ -70,7 +82,11 @@ test("Multiple favorite domains", async () => { test("Register fav", async () => { const owner = new PublicKey("Fxuoy3gFjfJALhwkRcuKjRdechcgffUApeYAfMWck6w8"); const tx = new Transaction(); - const ix = registerFavorite(getDomainKeySync("wallet-guide-3").pubkey, owner); + const ix = await registerFavorite( + connection, + getDomainKeySync("wallet-guide-3").pubkey, + owner, + ); tx.add(ix); const { blockhash } = await connection.getLatestBlockhash(); tx.recentBlockhash = blockhash;