Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes and improvements 08 01 2023 #44

Merged
merged 4 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bonfida/spl-name-service",
"version": "2.2.0",
"version": "2.3.0",
"license": "MIT",
"files": [
"dist"
Expand Down
85 changes: 84 additions & 1 deletion js/src/favorite-domain.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { Buffer } from "buffer";
import { deserialize, Schema } from "borsh";
import { getReverseKeySync, reverseLookup } from "./utils";
import {
deserializeReverse,
getReverseKeyFromDomainKey,
reverseLookup,
} from "./utils";
import { PublicKey, Connection } from "@solana/web3.js";
import { ErrorType, SNSError } from "./error";
import { resolve } from "./resolve";
import { getDomainMint } from "./nft/name-tokenizer";
import {
AccountLayout,
getAssociatedTokenAddressSync,
} from "@solana/spl-token";

export const NAME_OFFERS_ID = new PublicKey(
"85iDfUvr3HJyLM2zcq5BXSiDvUWfw6cSE1FfNBo8Ap29",
Expand Down Expand Up @@ -107,3 +116,77 @@ export const getFavoriteDomain = async (
stale: !owner.equals(domainOwner),
};
};

/**
* 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.
* This function is optimized for network efficiency, making only four RPC calls, three of which are executed in parallel using Promise.all, thereby reducing the overall execution time.
* @param connection The Solana RPC connection object
* @param wallets An array of PublicKeys representing the wallets
* @returns A promise that resolves to an array of strings or undefined, representing the favorite domains or lack thereof for each wallet
*/
export const getMultipleFavoriteDomains = async (
connection: Connection,
wallets: PublicKey[],
): Promise<(string | undefined)[]> => {
const result: (string | undefined)[] = [];

const favKeys = wallets.map(
(e) => FavouriteDomain.getKeySync(NAME_OFFERS_ID, e)[0],
);
const favDomains = (await connection.getMultipleAccountsInfo(favKeys)).map(
(e) => {
if (!!e?.data) {
return FavouriteDomain.deserialize(e?.data).nameAccount;
}
return PublicKey.default;
},
);
const revKeys = favDomains.map((e) => getReverseKeyFromDomainKey(e));
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),
connection.getMultipleAccountsInfo(revKeys),
connection.getMultipleAccountsInfo(atas),
]);

for (let i = 0; i < wallets.length; i++) {
const domainInfo = domainInfos[i];
const rev = revs[i];
const tokenAcc = tokenAccs[i];

if (!domainInfo || !rev) {
result.push(undefined);
continue;
}

const nativeOwner = new PublicKey(domainInfo?.data.slice(32, 64));

if (nativeOwner.equals(wallets[i])) {
result.push(deserializeReverse(rev?.data.slice(96)));
continue;
}
// Either tokenized or stale
if (!tokenAcc) {
result.push(undefined);
continue;
}

const decoded = AccountLayout.decode(tokenAcc.data);
// Tokenized
if (Number(decoded.amount) === 1) {
result.push(deserializeReverse(rev?.data.slice(96)));
continue;
}

// Stale
result.push(undefined);
}

return result;
};
1 change: 1 addition & 0 deletions js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export * from "./deprecated/utils";
export * from "./error";
export * from "./custom-bg";
export * from "./record_v2";
export * from "./record_v2/utils";
export * from "./devnet";
14 changes: 11 additions & 3 deletions js/src/nft/name-tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ import { Connection, PublicKey } from "@solana/web3.js";
import { Buffer } from "buffer";

export const NAME_TOKENIZER_ID = new PublicKey(
"nftD3vbNkNqfj2Sd3HZwbpw4BxxKWr4AjGb9X38JeZk"
"nftD3vbNkNqfj2Sd3HZwbpw4BxxKWr4AjGb9X38JeZk",
);

export const MINT_PREFIX = Buffer.from("tokenized_name");

export const getDomainMint = (domain: PublicKey) => {
const [mint] = PublicKey.findProgramAddressSync(
[MINT_PREFIX, domain.toBuffer()],
NAME_TOKENIZER_ID,
);
return mint;
};

export enum Tag {
Uninitialized = 0,
CentralState = 1,
Expand Down Expand Up @@ -66,7 +74,7 @@ export class NftRecord {
static async findKey(nameAccount: PublicKey, programId: PublicKey) {
return await PublicKey.findProgramAddress(
[Buffer.from("nft_record"), nameAccount.toBuffer()],
programId
programId,
);
}
}
Expand All @@ -80,7 +88,7 @@ export class NftRecord {
*/
export const getRecordFromMint = async (
connection: Connection,
mint: PublicKey
mint: PublicKey,
) => {
const filters = [
{
Expand Down
31 changes: 3 additions & 28 deletions js/src/record_v2.ts → js/src/record_v2/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { Record } from "./types/record";
import { ErrorType, SNSError } from "./error";
import { Record } from "../types/record";
import { ErrorType, SNSError } from "../error";
import { Connection, PublicKey } from "@solana/web3.js";
import { encode as encodePunycode, decode as decodePunnycode } from "punycode";
import {
check,
getDomainKeySync,
getHashedNameSync,
getNameAccountKeySync,
} from "./utils";
} from "../utils";
import { decode, encode } from "bech32-buffer";
import ipaddr from "ipaddr.js";
import {
CENTRAL_STATE_SNS_RECORDS,
Record as SnsRecord,
Validation,
} from "@bonfida/sns-records";
import { resolve } from "./resolve";

/**
* A map that associates each record type with a public key, known as guardians.
Expand All @@ -33,30 +32,6 @@ export const ETH_ROA_RECORDS = new Set<Record>([
Record.BSC,
]);

/**
* This function verifies the staleness of a record.
* @param {Connection} connection - The Solana RPC connection object
* @param {Record} record - The record to be verified.
* @param {string} domain - The domain associated with the record.
* @returns {Promise<boolean>} - Returns a promise that resolves to a boolean indicating whether the record is stale.
*/
export const verifyStaleness = async (
connection: Connection,
record: Record,
domain: string,
) => {
const recordKey = getRecordV2Key(domain, record);
const owner = await resolve(connection, domain);
const recordObj = await SnsRecord.retrieve(connection, recordKey);

const stalenessId = recordObj.getStalenessId();

return (
owner.equals(new PublicKey(stalenessId)) &&
recordObj.header.stalenessValidation === Validation.Solana
);
};

/**
*
* This function verifies the right of association of a record.
Expand Down
29 changes: 29 additions & 0 deletions js/src/record_v2/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Connection, PublicKey } from "@solana/web3.js";
import { Record } from "../types/record";
import { resolve } from "../resolve";
import { getRecordV2Key } from ".";
import { Record as SnsRecord, Validation } from "@bonfida/sns-records";

/**
* This function verifies the staleness of a record.
* @param {Connection} connection - The Solana RPC connection object
* @param {Record} record - The record to be verified.
* @param {string} domain - The domain associated with the record.
* @returns {Promise<boolean>} - Returns a promise that resolves to a boolean indicating whether the record is stale.
*/
export const verifyStaleness = async (
connection: Connection,
record: Record,
domain: string,
) => {
const recordKey = getRecordV2Key(domain, record);
const owner = await resolve(connection, domain);
const recordObj = await SnsRecord.retrieve(connection, recordKey);

const stalenessId = recordObj.getStalenessId();

return (
owner.equals(new PublicKey(stalenessId)) &&
recordObj.header.stalenessValidation === Validation.Solana
);
};
10 changes: 5 additions & 5 deletions js/src/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const resolve = async (connection: Connection, domain: string) => {

const { registry, nftOwner } = await NameRegistryState.retrieve(
connection,
pubkey
pubkey,
);

if (nftOwner) {
Expand All @@ -33,7 +33,7 @@ export const resolve = async (connection: Connection, domain: string) => {
const solV2Owner = await resolveSolRecordV2(
connection,
registry.owner,
domain
domain,
);
if (solV2Owner !== undefined) {
return solV2Owner;
Expand All @@ -45,7 +45,7 @@ export const resolve = async (connection: Connection, domain: string) => {
const solV1Owner = await resolveSolRecordV1(
connection,
registry.owner,
domain
domain,
);

return solV1Owner;
Expand All @@ -63,7 +63,7 @@ export const resolve = async (connection: Connection, domain: string) => {
const resolveSolRecordV1 = async (
connection: Connection,
owner: PublicKey,
domain: string
domain: string,
) => {
const recordKey = getRecordKeySync(domain, Record.SOL);
const solRecord = await getSolRecord(connection, domain);
Expand All @@ -90,7 +90,7 @@ const resolveSolRecordV1 = async (
const resolveSolRecordV2 = async (
connection: Connection,
owner: PublicKey,
domain: string
domain: string,
) => {
try {
const recordV2Key = getRecordV2Key(domain, Record.SOL);
Expand Down
28 changes: 19 additions & 9 deletions js/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,20 @@ export class NameRegistryState {
this.class = new PublicKey(obj.class);
}

static deserialize(data: Buffer) {
let res: NameRegistryState = deserializeUnchecked(
this.schema,
NameRegistryState,
data,
);

res.data = data?.slice(this.HEADER_LEN);
return res;
}

public static async retrieve(
connection: Connection,
nameAccountKey: PublicKey
nameAccountKey: PublicKey,
) {
const nameAccount = await connection.getAccountInfo(nameAccountKey);
if (!nameAccount) {
Expand All @@ -46,7 +57,7 @@ export class NameRegistryState {
let res: NameRegistryState = deserializeUnchecked(
this.schema,
NameRegistryState,
nameAccount.data
nameAccount.data,
);

res.data = nameAccount.data?.slice(this.HEADER_LEN);
Expand All @@ -58,17 +69,16 @@ export class NameRegistryState {

static async _retrieveBatch(
connection: Connection,
nameAccountKeys: PublicKey[]
nameAccountKeys: PublicKey[],
) {
const nameAccounts = await connection.getMultipleAccountsInfo(
nameAccountKeys
);
const nameAccounts =
await connection.getMultipleAccountsInfo(nameAccountKeys);
const fn = (data: Buffer | undefined) => {
if (!data) return undefined;
const res: NameRegistryState = deserializeUnchecked(
this.schema,
NameRegistryState,
data
data,
);
res.data = data?.slice(this.HEADER_LEN);
return res;
Expand All @@ -78,13 +88,13 @@ export class NameRegistryState {

public static async retrieveBatch(
connection: Connection,
nameAccountKeys: PublicKey[]
nameAccountKeys: PublicKey[],
) {
let result: (NameRegistryState | undefined)[] = [];
const keys = [...nameAccountKeys];
while (keys.length > 0) {
result.push(
...(await this._retrieveBatch(connection, keys.splice(0, 100)))
...(await this._retrieveBatch(connection, keys.splice(0, 100))),
);
}
return result;
Expand Down
Loading
Loading