Skip to content

Instantly share code, notes, and snippets.

@crypt0miester
Last active October 28, 2024 13:21
Show Gist options
  • Save crypt0miester/291719c62840394b72abc83669eae54f to your computer and use it in GitHub Desktop.
Save crypt0miester/291719c62840394b72abc83669eae54f to your computer and use it in GitHub Desktop.
import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
import { deserialize, Schema } from "borsh";
/**
* Holds the data for the {@link NameRecordHeader} Account and provides de/serialization
* functionality for that data
*/
export class NameRecordHeaderRaw {
// only for normal domains, tld name record might not be working.
static async create(
obj: {
parentName: Uint8Array;
owner: Uint8Array;
nclass: Uint8Array;
expiresAt: Uint8Array;
createdAt: Uint8Array;
nonTransferable: Uint8Array;
},
connection: Connection,
parentNameRecord?: NameRecordHeaderRaw,
): Promise<NameRecordHeaderRaw> {
const instance = new NameRecordHeaderRaw(obj);
if (!parentNameRecord) {
await instance.initializeParentNameRecordHeader(connection);
} else {
instance.updateGracePeriod(parentNameRecord);
}
return instance;
}
async initializeParentNameRecordHeader(
connection: Connection,
): Promise<void> {
if (this.parentName.toString() === PublicKey.default.toString()) {
this.isValid = true;
return;
}
const parentNameRecordHeader = await NameRecordHeaderRaw.fromAccountAddress(
connection,
this.parentName,
);
this.updateGracePeriod(parentNameRecordHeader);
}
updateGracePeriod(parentNameRecord: NameRecordHeaderRaw | undefined): void {
const currentTime = Date.now();
const defaultGracePeriod = 50 * 24 * 60 * 60 * 1000;
const gracePeriod =
parentNameRecord?.expiresAt.getTime() || defaultGracePeriod;
this.isValid =
this.expiresAt.getTime() === 0 ||
this.expiresAt.getTime() + gracePeriod > currentTime;
if (!this.isValid) {
this.owner = undefined;
}
}
constructor(obj: {
parentName: Uint8Array;
owner: Uint8Array;
nclass: Uint8Array;
expiresAt: Uint8Array;
createdAt: Uint8Array;
nonTransferable: Uint8Array;
}) {
this.parentName = new PublicKey(obj.parentName);
this.nclass = new PublicKey(obj.nclass);
// Convert expiresAt bytes to number using DataView
const expiresAtView = new DataView(obj.expiresAt.buffer);
const expiresAtTimestamp = Number(expiresAtView.getBigUint64(0, true));
this.expiresAt = new Date(expiresAtTimestamp * 1000);
// Convert createdAt bytes to number using DataView
const createdAtView = new DataView(obj.createdAt.buffer);
const createdAtTimestamp = Number(createdAtView.getBigUint64(0, true));
this.createdAt = new Date(createdAtTimestamp * 1000);
this.nonTransferable = obj.nonTransferable[0] === 1;
const gracePeriod = 50 * 24 * 60 * 60 * 1000;
this.isValid =
expiresAtTimestamp === 0
? true
: this.expiresAt > new Date(Date.now() + gracePeriod);
this.owner = this.isValid ? new PublicKey(obj.owner) : undefined;
this.expiresAtBuffer = Buffer.from(obj.expiresAt);
}
parentName: PublicKey;
owner: PublicKey | undefined;
nclass: PublicKey;
expiresAt: Date;
createdAt: Date;
isValid: boolean;
nonTransferable: boolean;
expiresAtBuffer: Buffer;
data: Buffer | undefined;
static DISCRIMINATOR = [68, 72, 88, 44, 15, 167, 103, 243];
static HASH_PREFIX = "ALT Name Service";
static ROOT_ANS_PUBLIC_KEY = new PublicKey(
"3mX9b4AZaQehNoQGfckVcmgmA6bkBoFcbLj9RMmMyNcU",
);
/**
* NameRecordHeaderRaw Schema for Borsh serialization/deserialization
*/
static schema: Schema = {
struct: {
discriminator: { array: { type: "u8", len: 8 } },
parentName: { array: { type: "u8", len: 32 } },
owner: { array: { type: "u8", len: 32 } },
nclass: { array: { type: "u8", len: 32 } },
expiresAt: { array: { type: "u8", len: 8 } },
createdAt: { array: { type: "u8", len: 8 } },
nonTransferable: "bool",
padding: { array: { type: "u8", len: 79 } },
},
};
/**
* Returns the minimum size of a {@link Buffer} holding the serialized data of
* {@link NameRecordHeader}
*/
static get byteSize() {
return 8 + 32 + 32 + 32 + 8 + 8 + 1 + 79;
}
/**
* 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<NameRecordHeaderRaw | undefined> {
const nameAccount = await connection.getAccountInfo(
nameAccountKey,
"confirmed",
);
if (!nameAccount) {
return undefined;
}
const decodedData = deserialize(
this.schema,
Uint8Array.from(nameAccount.data),
) as {
discriminator: number[];
parentName: Uint8Array;
owner: Uint8Array;
nclass: Uint8Array;
expiresAt: Uint8Array;
createdAt: Uint8Array;
nonTransferable: Uint8Array;
};
const res = new NameRecordHeaderRaw(decodedData);
res.data = nameAccount.data?.subarray(this.byteSize);
if (res.parentName.toString() !== this.ROOT_ANS_PUBLIC_KEY.toString()) {
await res.initializeParentNameRecordHeader(connection);
} else {
res.isValid = true;
}
return res;
}
/**
* Retrieves the account info from the provided data and deserializes
* the {@link NameRecordHeader} from its data.
*/
public static fromAccountInfo(
nameAccountAccountInfo: AccountInfo<Buffer>,
): NameRecordHeaderRaw {
const decodedData = deserialize(
this.schema,
Uint8Array.from(nameAccountAccountInfo.data),
) as {
discriminator: number[];
parentName: Uint8Array;
owner: Uint8Array;
nclass: Uint8Array;
expiresAt: Uint8Array;
createdAt: Uint8Array;
nonTransferable: Uint8Array;
};
const res = new NameRecordHeaderRaw(decodedData);
res.data = nameAccountAccountInfo.data?.subarray(this.byteSize);
return res;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment