From 6b410617928be982ace4ff4406d3badf9a04c4ce Mon Sep 17 00:00:00 2001 From: crypt0miester Date: Fri, 28 Oct 2022 05:56:48 +0000 Subject: [PATCH 1/2] add tld_house integration --- src/components/SendDialog.js | 18 +++++ src/utils/alt-name-service/index.ts | 79 +++++++++++++++++++++ src/utils/alt-name-service/state.ts | 92 ++++++++++++++++++++++++ src/utils/alt-name-service/utils.ts | 106 ++++++++++++++++++++++++++++ 4 files changed, 295 insertions(+) create mode 100644 src/utils/alt-name-service/index.ts create mode 100644 src/utils/alt-name-service/state.ts create mode 100644 src/utils/alt-name-service/utils.ts diff --git a/src/components/SendDialog.js b/src/components/SendDialog.js index 0c1637db..aa4eae44 100644 --- a/src/components/SendDialog.js +++ b/src/components/SendDialog.js @@ -282,6 +282,24 @@ function SendSplDialog({ onClose, publicKey, balanceInfo, onSubmitRef }) { setIsDomainName(true); setDomainOwner(domainOwner); } + + if (!domainOwner && destinationAddress.split('.').length === 2) { + let domainOwner; + + domainOwner = await getOwnerFromDomainTld(wallet.connection, destinationAddress); + + if (!domainOwner) { + setAddressHelperText( + `This domain name is not registered`, + ); + setPassValidation(undefined); + setShouldShowOverride(undefined); + return; + } + setIsDomainName(true); + setDomainOwner(domainOwner); + } + if (!destinationAddress) { setAddressHelperText(defaultAddressHelperText); setPassValidation(undefined); diff --git a/src/utils/alt-name-service/index.ts b/src/utils/alt-name-service/index.ts new file mode 100644 index 00000000..e017bbf3 --- /dev/null +++ b/src/utils/alt-name-service/index.ts @@ -0,0 +1,79 @@ +import { PublicKey, Connection } from '@solana/web3.js'; + +import { + findOwnedNameAccountsForUser, + getHashedName, + getNameAccountKeyWithBump, + getNameOwner, + getOriginNameAccountKey, +} from './utils'; + + +export const ORIGIN_TLD = 'ANS'; +export const ANS_PROGRAM_ID = new PublicKey( + 'ALTNSZ46uaAUU7XUV6awvdorLGqAsPwa9shm7h4uP2FK', +); +/** + * retrieves all nameaccounts for any user in a particular tld. + * + * @param connection sol connection + * @param userAccount user publickey or string + * @param tld tld to be retrieved from + */ +export async function getAllUserDomainsFromTld( + connection: Connection, + userAccount: PublicKey | string, + tld: string, +): Promise { + const tldName = '.' + tld; + + const nameOriginTldKey = await getOriginNameAccountKey(); + const parentHashedName = getHashedName(tldName); + const [parentAccountKey] = await getNameAccountKeyWithBump( + parentHashedName, + undefined, + nameOriginTldKey, + ); + if (typeof userAccount == 'string') { + userAccount = new PublicKey(userAccount); + } + const allDomains = await findOwnedNameAccountsForUser( + connection, + userAccount, + parentAccountKey, + ); + return allDomains; +} + +/** + * retrieves owner of a particular Name Account from domain.tld. + * + * @param connection sol connection + * @param domainTld full string of domain and tld e.g. "miester.poor" + */ +export async function getOwnerFromDomainTld( + connection: Connection, + domainTld: string, +): Promise { + const domainTldSplit = domainTld.split('.'); + const domain = domainTldSplit[0]; + const tldName = '.' + domainTldSplit[1]; + + const nameOriginTldKey = await getOriginNameAccountKey(); + const parentHashedName = getHashedName(tldName); + const [parentAccountKey] = await getNameAccountKeyWithBump( + parentHashedName, + undefined, + nameOriginTldKey, + ); + + const domainHashedName = getHashedName(domain); + const [domainAccountKey] = await getNameAccountKeyWithBump( + domainHashedName, + undefined, + parentAccountKey, + ); + + const nameOwner = await getNameOwner(connection, domainAccountKey); + return nameOwner; +} \ No newline at end of file diff --git a/src/utils/alt-name-service/state.ts b/src/utils/alt-name-service/state.ts new file mode 100644 index 00000000..71fd49b1 --- /dev/null +++ b/src/utils/alt-name-service/state.ts @@ -0,0 +1,92 @@ +import { Connection, PublicKey } from '@solana/web3.js'; +import { deserializeUnchecked, Schema } from 'borsh'; + +/** + * Holds the data for the {@link NameRecordHeader} Account and provides de/serialization + * functionality for that data + */ +export class NameRecordHeader { + constructor(obj: { + parentName: Uint8Array; + owner: Uint8Array; + nclass: Uint8Array; + }) { + this.parentName = new PublicKey(obj.parentName); + this.owner = new PublicKey(obj.owner); + this.nclass = new PublicKey(obj.nclass); + } + + parentName: PublicKey; + owner: PublicKey; + nclass: PublicKey; + data: Buffer | undefined; + + static DISCRIMINATOR = [68, 72, 88, 44, 15, 167, 103, 243]; + static HASH_PREFIX = 'ALT Name Service'; + + /** + * NameRecordHeader Schema across all name service accounts + */ + static schema: Schema = new Map([ + [ + NameRecordHeader, + { + kind: 'struct', + fields: [ + ['discriminator', [8]], + ['parentName', [32]], + ['owner', [32]], + ['nclass', [32]], + ['padding', [96]], + ], + }, + ], + ]); + + /** + * Returns the minimum size of a {@link Buffer} holding the serialized data of + * {@link NameRecordHeader} + */ + static get byteSize() { + return 8 + 32 + 32 + 32 + 96; + } + + /** + * Retrieves the account info from the provided address and deserializes + * the {@link NameRecordHeader} from its data. + */ + public static async fromAccountAddress( + connection: Connection, + nameAccountKey: PublicKey, + ): Promise { + const nameAccount = await connection.getAccountInfo( + nameAccountKey, + 'confirmed', + ); + if (!nameAccount) { + return undefined; + } + + const res: NameRecordHeader = deserializeUnchecked( + this.schema, + NameRecordHeader, + nameAccount.data, + ); + + res.data = nameAccount.data?.subarray(this.byteSize); + + return res; + } + + /** + * Returns a readable version of {@link NameRecordHeader} properties + * and can be used to convert to JSON and/or logging + */ + pretty() { + return { + parentName: this.parentName.toBase58(), + owner: this.owner.toBase58(), + nclass: this.nclass.toBase58(), + }; + } +} \ No newline at end of file diff --git a/src/utils/alt-name-service/utils.ts b/src/utils/alt-name-service/utils.ts new file mode 100644 index 00000000..de9f986e --- /dev/null +++ b/src/utils/alt-name-service/utils.ts @@ -0,0 +1,106 @@ +import { Connection, PublicKey } from '@solana/web3.js'; +import { createHash } from 'crypto'; +import { ANS_PROGRAM_ID, ORIGIN_TLD } from '.'; + +import { NameRecordHeader } from './state'; + +/** + * retrieves raw name account + * + * @param hashedName hashed name of the name account + * @param nameClass defaults to pubkey::default() + * @param parentName defaults to pubkey::default() + */ +export async function getNameAccountKeyWithBump( + hashedName: Buffer, + nameClass?: PublicKey, + parentName?: PublicKey, +): Promise<[PublicKey, number]> { + const seeds = [ + hashedName, + nameClass ? nameClass.toBuffer() : Buffer.alloc(32), + parentName ? parentName.toBuffer() : Buffer.alloc(32), + ]; + + return await PublicKey.findProgramAddress(seeds, ANS_PROGRAM_ID); +} + +/** + * retrieves owner of the name account + * + * @param connection sol connection + * @param nameAccountKey defaults to pubkey::default() + */ +export async function getNameOwner( + connection: Connection, + nameAccountKey: PublicKey, +): Promise { + return (await NameRecordHeader.fromAccountAddress(connection, nameAccountKey))?.owner; +} + +/** + * computes hashed name + * + * @param name any string or domain name + */ + +export function getHashedName(name: string): Buffer { + const input = NameRecordHeader.HASH_PREFIX + name; + const buffer = createHash('sha256').update(input, 'utf8').digest(); + return buffer; +} + +/** + * A constant in tld house. + * + * get origin name account should always equal to 3mX9b4AZaQehNoQGfckVcmgmA6bkBoFcbLj9RMmMyNcU + * + * @param originTld + */ +export async function getOriginNameAccountKey( + originTld: string = ORIGIN_TLD, +): Promise { + const hashed_name = getHashedName(originTld); + const [nameAccountKey] = await getNameAccountKeyWithBump( + hashed_name, + undefined, + undefined, + ); + return nameAccountKey; +} + +/** + * finds list of all name accounts for a particular user. + * + * @param connection sol connection + * @param userAccount user's public key + * @param parentAccount nameAccount's parentName + */ +export async function findOwnedNameAccountsForUser( + connection: Connection, + userAccount: PublicKey, + parentAccount: PublicKey | undefined, +): Promise { + const filters: any = [ + { + memcmp: { + offset: 40, + bytes: userAccount.toBase58(), + }, + }, + ]; + + if (parentAccount) { + filters.push({ + memcmp: { + offset: 8, + bytes: parentAccount.toBase58(), + }, + }); + } + + const accounts = await connection.getProgramAccounts(ANS_PROGRAM_ID, { + filters: filters, + }); + return accounts.map((a: any) => a.pubkey); +} \ No newline at end of file From 7290e0a1a901fd8f6f4e95945d03546375bcca02 Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 4 Nov 2022 13:23:14 +0000 Subject: [PATCH 2/2] getOwnerFromDomainTld import bugfix --- src/components/SendDialog.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/SendDialog.js b/src/components/SendDialog.js index aa4eae44..b6094706 100644 --- a/src/components/SendDialog.js +++ b/src/components/SendDialog.js @@ -42,6 +42,10 @@ import { getNameKey, } from '../utils/name-service'; +import { + getOwnerFromDomainTld, +} from '../utils/alt-name-service'; + const WUSDC_MINT = new PublicKey( 'BXXkv6z8ykpG1yuvUDPgh732wzVHB69RnB9YgSYh3itW', );