Last active
October 28, 2024 13:21
-
-
Save crypt0miester/291719c62840394b72abc83669eae54f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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