@solana/spl-name-service
Version:
SPL Name Service JavaScript API
434 lines (394 loc) • 11.8 kB
text/typescript
import {
Connection,
PublicKey,
SystemProgram,
TransactionInstruction,
} from '@solana/web3.js';
import { deserialize, Schema, serialize } from 'borsh';
import { deleteNameRegistry, NAME_PROGRAM_ID } from './bindings';
import {
createInstruction,
deleteInstruction,
transferInstruction,
updateInstruction,
} from './instructions';
import { NameRegistryState } from './state';
import {
getFilteredProgramAccounts,
getHashedName,
getNameAccountKey,
Numberu32,
Numberu64,
} from './utils';
////////////////////////////////////////////////////
// Global Variables
export const TWITTER_VERIFICATION_AUTHORITY = new PublicKey(
'FvPH7PrVrLGKPfqaf3xJodFTjZriqrAXXLTVWEorTFBi',
);
// The address of the name registry that will be a parent to all twitter handle registries,
// it should be owned by the TWITTER_VERIFICATION_AUTHORITY and its name is irrelevant
export const TWITTER_ROOT_PARENT_REGISTRY_KEY = new PublicKey(
'4YcexoW3r78zz16J2aqmukBLRwGq6rAvWzJpkYAXqebv',
);
////////////////////////////////////////////////////
// Bindings
// Signed by the authority, the payer and the verified pubkey
export async function createVerifiedTwitterRegistry(
connection: Connection,
twitterHandle: string,
verifiedPubkey: PublicKey,
space: number, // The space that the user will have to write data into the verified registry
payerKey: PublicKey,
): Promise<TransactionInstruction[]> {
// Create user facing registry
const hashedTwitterHandle = await getHashedName(twitterHandle);
const twitterHandleRegistryKey = await getNameAccountKey(
hashedTwitterHandle,
undefined,
TWITTER_ROOT_PARENT_REGISTRY_KEY,
);
let instructions = [
createInstruction(
NAME_PROGRAM_ID,
SystemProgram.programId,
twitterHandleRegistryKey,
verifiedPubkey,
payerKey,
hashedTwitterHandle,
new Numberu64(await connection.getMinimumBalanceForRentExemption(space)),
new Numberu32(space),
undefined,
TWITTER_ROOT_PARENT_REGISTRY_KEY,
TWITTER_VERIFICATION_AUTHORITY, // Twitter authority acts as owner of the parent for all user-facing registries
),
];
instructions = instructions.concat(
await createReverseTwitterRegistry(
connection,
twitterHandle,
twitterHandleRegistryKey,
verifiedPubkey,
payerKey,
),
);
return instructions;
}
// Overwrite the data that is written in the user facing registry
// Signed by the verified pubkey
export async function changeTwitterRegistryData(
twitterHandle: string,
verifiedPubkey: PublicKey,
offset: number, // The offset at which to write the input data into the NameRegistryData
input_data: Buffer,
): Promise<TransactionInstruction[]> {
const hashedTwitterHandle = await getHashedName(twitterHandle);
const twitterHandleRegistryKey = await getNameAccountKey(
hashedTwitterHandle,
undefined,
TWITTER_ROOT_PARENT_REGISTRY_KEY,
);
const instructions = [
updateInstruction(
NAME_PROGRAM_ID,
twitterHandleRegistryKey,
new Numberu32(offset),
input_data,
verifiedPubkey,
undefined,
),
];
return instructions;
}
// Change the verified pubkey for a given twitter handle
// Signed by the Authority, the verified pubkey and the payer
export async function changeVerifiedPubkey(
connection: Connection,
twitterHandle: string,
currentVerifiedPubkey: PublicKey,
newVerifiedPubkey: PublicKey,
payerKey: PublicKey,
): Promise<TransactionInstruction[]> {
const hashedTwitterHandle = await getHashedName(twitterHandle);
const twitterHandleRegistryKey = await getNameAccountKey(
hashedTwitterHandle,
undefined,
TWITTER_ROOT_PARENT_REGISTRY_KEY,
);
// Transfer the user-facing registry ownership
let instructions = [
transferInstruction(
NAME_PROGRAM_ID,
twitterHandleRegistryKey,
newVerifiedPubkey,
currentVerifiedPubkey,
undefined,
),
];
// Delete the current reverse registry
instructions.push(
await deleteNameRegistry(
connection,
currentVerifiedPubkey.toString(),
payerKey,
TWITTER_VERIFICATION_AUTHORITY,
TWITTER_ROOT_PARENT_REGISTRY_KEY,
),
);
// Create the new reverse registry
instructions = instructions.concat(
await createReverseTwitterRegistry(
connection,
twitterHandle,
twitterHandleRegistryKey,
newVerifiedPubkey,
payerKey,
),
);
return instructions;
}
// Delete the verified registry for a given twitter handle
// Signed by the verified pubkey
export async function deleteTwitterRegistry(
twitterHandle: string,
verifiedPubkey: PublicKey,
): Promise<TransactionInstruction[]> {
const hashedTwitterHandle = await getHashedName(twitterHandle);
const twitterHandleRegistryKey = await getNameAccountKey(
hashedTwitterHandle,
undefined,
TWITTER_ROOT_PARENT_REGISTRY_KEY,
);
const hashedVerifiedPubkey = await getHashedName(verifiedPubkey.toString());
const reverseRegistryKey = await getNameAccountKey(
hashedVerifiedPubkey,
TWITTER_VERIFICATION_AUTHORITY,
TWITTER_ROOT_PARENT_REGISTRY_KEY,
);
const instructions = [
// Delete the user facing registry
deleteInstruction(
NAME_PROGRAM_ID,
twitterHandleRegistryKey,
verifiedPubkey,
verifiedPubkey,
),
// Delete the reverse registry
deleteInstruction(
NAME_PROGRAM_ID,
reverseRegistryKey,
verifiedPubkey,
verifiedPubkey,
),
];
return instructions;
}
//////////////////////////////////////////
// Getter Functions
// Returns the key of the user-facing registry
export async function getTwitterRegistryKey(
twitter_handle: string,
): Promise<PublicKey> {
const hashedTwitterHandle = await getHashedName(twitter_handle);
return await getNameAccountKey(
hashedTwitterHandle,
undefined,
TWITTER_ROOT_PARENT_REGISTRY_KEY,
);
}
export async function getTwitterRegistry(
connection: Connection,
twitter_handle: string,
): Promise<NameRegistryState> {
const hashedTwitterHandle = await getHashedName(twitter_handle);
const twitterHandleRegistryKey = await getNameAccountKey(
hashedTwitterHandle,
undefined,
TWITTER_ROOT_PARENT_REGISTRY_KEY,
);
const registry = NameRegistryState.retrieve(
connection,
twitterHandleRegistryKey,
);
return registry;
}
export async function getHandleAndRegistryKey(
connection: Connection,
verifiedPubkey: PublicKey,
): Promise<[string, PublicKey]> {
const hashedVerifiedPubkey = await getHashedName(verifiedPubkey.toString());
const reverseRegistryKey = await getNameAccountKey(
hashedVerifiedPubkey,
TWITTER_VERIFICATION_AUTHORITY,
TWITTER_ROOT_PARENT_REGISTRY_KEY,
);
const reverseRegistryState = await ReverseTwitterRegistryState.retrieve(
connection,
reverseRegistryKey,
);
return [
reverseRegistryState.twitterHandle,
new PublicKey(reverseRegistryState.twitterRegistryKey),
];
}
// Uses the RPC node filtering feature, execution speed may vary
export async function getTwitterHandleandRegistryKeyViaFilters(
connection: Connection,
verifiedPubkey: PublicKey,
): Promise<[string, PublicKey]> {
const filters = [
{
memcmp: {
offset: 0,
bytes: TWITTER_ROOT_PARENT_REGISTRY_KEY.toBase58(),
},
},
{
memcmp: {
offset: 32,
bytes: verifiedPubkey.toBase58(),
},
},
{
memcmp: {
offset: 64,
bytes: TWITTER_VERIFICATION_AUTHORITY.toBase58(),
},
},
];
const filteredAccounts = await getFilteredProgramAccounts(
connection,
NAME_PROGRAM_ID,
filters,
);
for (const f of filteredAccounts) {
if (f.accountInfo.data.length > NameRegistryState.HEADER_LEN + 32) {
const data = f.accountInfo.data.slice(NameRegistryState.HEADER_LEN);
const state = deserialize(
ReverseTwitterRegistryState.schema,
data,
) as ReverseTwitterRegistryState;
return [state.twitterHandle, new PublicKey(state.twitterRegistryKey)];
}
}
throw new Error('Registry not found.');
}
// Uses the RPC node filtering feature, execution speed may vary
// Does not give you the handle, but is an alternative to getHandlesAndKeysFromVerifiedPubkey + getTwitterRegistry to get the data
export async function getTwitterRegistryData(
connection: Connection,
verifiedPubkey: PublicKey,
): Promise<Buffer> {
const filters = [
{
memcmp: {
offset: 0,
bytes: TWITTER_ROOT_PARENT_REGISTRY_KEY.toBase58(),
},
},
{
memcmp: {
offset: 32,
bytes: verifiedPubkey.toBase58(),
},
},
{
memcmp: {
offset: 64,
bytes: new PublicKey(Buffer.alloc(32, 0)).toBase58(),
},
},
];
const filteredAccounts = await getFilteredProgramAccounts(
connection,
NAME_PROGRAM_ID,
filters,
);
if (filteredAccounts.length > 1) {
throw new Error('Found more than one registry.');
}
return filteredAccounts[0].accountInfo.data.slice(
NameRegistryState.HEADER_LEN,
);
}
//////////////////////////////////////////////
// Utils
export class ReverseTwitterRegistryState {
twitterRegistryKey: Uint8Array;
twitterHandle: string;
static schema: Schema = {
struct: {
twitterRegistryKey: { array: { type: 'u8', len: 32 } },
twitterHandle: 'string',
},
};
constructor(obj: { twitterRegistryKey: Uint8Array; twitterHandle: string }) {
this.twitterRegistryKey = obj.twitterRegistryKey;
this.twitterHandle = obj.twitterHandle;
}
public static async retrieve(
connection: Connection,
reverseTwitterAccountKey: PublicKey,
): Promise<ReverseTwitterRegistryState> {
const reverseTwitterAccount = await connection.getAccountInfo(
reverseTwitterAccountKey,
'processed',
);
if (!reverseTwitterAccount) {
throw new Error('Invalid reverse Twitter account provided');
}
const res = deserialize(
this.schema,
reverseTwitterAccount.data.slice(NameRegistryState.HEADER_LEN),
) as ReverseTwitterRegistryState;
return res;
}
}
export async function createReverseTwitterRegistry(
connection: Connection,
twitterHandle: string,
twitterRegistryKey: PublicKey,
verifiedPubkey: PublicKey,
payerKey: PublicKey,
): Promise<TransactionInstruction[]> {
// Create the reverse lookup registry
const hashedVerifiedPubkey = await getHashedName(verifiedPubkey.toString());
const reverseRegistryKey = await getNameAccountKey(
hashedVerifiedPubkey,
TWITTER_VERIFICATION_AUTHORITY,
TWITTER_ROOT_PARENT_REGISTRY_KEY,
);
const reverseTwitterRegistryStateBuff = serialize(
ReverseTwitterRegistryState.schema,
new ReverseTwitterRegistryState({
twitterRegistryKey: twitterRegistryKey.toBytes(),
twitterHandle,
}),
);
return [
createInstruction(
NAME_PROGRAM_ID,
SystemProgram.programId,
reverseRegistryKey,
verifiedPubkey,
payerKey,
hashedVerifiedPubkey,
new Numberu64(
await connection.getMinimumBalanceForRentExemption(
reverseTwitterRegistryStateBuff.length,
),
),
new Numberu32(reverseTwitterRegistryStateBuff.length),
TWITTER_VERIFICATION_AUTHORITY, // Twitter authority acts as class for all reverse-lookup registries
TWITTER_ROOT_PARENT_REGISTRY_KEY, // Reverse registries are also children of the root
TWITTER_VERIFICATION_AUTHORITY,
),
updateInstruction(
NAME_PROGRAM_ID,
reverseRegistryKey,
new Numberu32(0),
Buffer.from(reverseTwitterRegistryStateBuff),
TWITTER_VERIFICATION_AUTHORITY,
undefined,
),
];
}