439 lines
11 KiB
Plaintext
439 lines
11 KiB
Plaintext
import * as BufferLayout from '@solana/buffer-layout';
|
|
import {getU64Encoder} from '@solana/codecs-numbers';
|
|
|
|
import * as Layout from '../../layout';
|
|
import {PublicKey} from '../../publickey';
|
|
import * as bigintLayout from '../../utils/bigint';
|
|
import {SystemProgram} from '../system';
|
|
import {TransactionInstruction} from '../../transaction';
|
|
import {decodeData, encodeData, IInstructionInputData} from '../../instruction';
|
|
|
|
export * from './state';
|
|
|
|
export type CreateLookupTableParams = {
|
|
/** Account used to derive and control the new address lookup table. */
|
|
authority: PublicKey;
|
|
/** Account that will fund the new address lookup table. */
|
|
payer: PublicKey;
|
|
/** A recent slot must be used in the derivation path for each initialized table. */
|
|
recentSlot: bigint | number;
|
|
};
|
|
|
|
export type FreezeLookupTableParams = {
|
|
/** Address lookup table account to freeze. */
|
|
lookupTable: PublicKey;
|
|
/** Account which is the current authority. */
|
|
authority: PublicKey;
|
|
};
|
|
|
|
export type ExtendLookupTableParams = {
|
|
/** Address lookup table account to extend. */
|
|
lookupTable: PublicKey;
|
|
/** Account which is the current authority. */
|
|
authority: PublicKey;
|
|
/** Account that will fund the table reallocation.
|
|
* Not required if the reallocation has already been funded. */
|
|
payer?: PublicKey;
|
|
/** List of Public Keys to be added to the lookup table. */
|
|
addresses: Array<PublicKey>;
|
|
};
|
|
|
|
export type DeactivateLookupTableParams = {
|
|
/** Address lookup table account to deactivate. */
|
|
lookupTable: PublicKey;
|
|
/** Account which is the current authority. */
|
|
authority: PublicKey;
|
|
};
|
|
|
|
export type CloseLookupTableParams = {
|
|
/** Address lookup table account to close. */
|
|
lookupTable: PublicKey;
|
|
/** Account which is the current authority. */
|
|
authority: PublicKey;
|
|
/** Recipient of closed account lamports. */
|
|
recipient: PublicKey;
|
|
};
|
|
|
|
/**
|
|
* An enumeration of valid LookupTableInstructionType's
|
|
*/
|
|
export type LookupTableInstructionType =
|
|
| 'CreateLookupTable'
|
|
| 'ExtendLookupTable'
|
|
| 'CloseLookupTable'
|
|
| 'FreezeLookupTable'
|
|
| 'DeactivateLookupTable';
|
|
|
|
type LookupTableInstructionInputData = {
|
|
CreateLookupTable: IInstructionInputData &
|
|
Readonly<{
|
|
recentSlot: bigint;
|
|
bumpSeed: number;
|
|
}>;
|
|
FreezeLookupTable: IInstructionInputData;
|
|
ExtendLookupTable: IInstructionInputData &
|
|
Readonly<{
|
|
numberOfAddresses: bigint;
|
|
addresses: Array<Uint8Array>;
|
|
}>;
|
|
DeactivateLookupTable: IInstructionInputData;
|
|
CloseLookupTable: IInstructionInputData;
|
|
};
|
|
|
|
/**
|
|
* An enumeration of valid address lookup table InstructionType's
|
|
* @internal
|
|
*/
|
|
export const LOOKUP_TABLE_INSTRUCTION_LAYOUTS = Object.freeze({
|
|
CreateLookupTable: {
|
|
index: 0,
|
|
layout: BufferLayout.struct<
|
|
LookupTableInstructionInputData['CreateLookupTable']
|
|
>([
|
|
BufferLayout.u32('instruction'),
|
|
bigintLayout.u64('recentSlot'),
|
|
BufferLayout.u8('bumpSeed'),
|
|
]),
|
|
},
|
|
FreezeLookupTable: {
|
|
index: 1,
|
|
layout: BufferLayout.struct<
|
|
LookupTableInstructionInputData['FreezeLookupTable']
|
|
>([BufferLayout.u32('instruction')]),
|
|
},
|
|
ExtendLookupTable: {
|
|
index: 2,
|
|
layout: BufferLayout.struct<
|
|
LookupTableInstructionInputData['ExtendLookupTable']
|
|
>([
|
|
BufferLayout.u32('instruction'),
|
|
bigintLayout.u64(),
|
|
BufferLayout.seq(
|
|
Layout.publicKey(),
|
|
BufferLayout.offset(BufferLayout.u32(), -8),
|
|
'addresses',
|
|
),
|
|
]),
|
|
},
|
|
DeactivateLookupTable: {
|
|
index: 3,
|
|
layout: BufferLayout.struct<
|
|
LookupTableInstructionInputData['DeactivateLookupTable']
|
|
>([BufferLayout.u32('instruction')]),
|
|
},
|
|
CloseLookupTable: {
|
|
index: 4,
|
|
layout: BufferLayout.struct<
|
|
LookupTableInstructionInputData['CloseLookupTable']
|
|
>([BufferLayout.u32('instruction')]),
|
|
},
|
|
});
|
|
|
|
export class AddressLookupTableInstruction {
|
|
/**
|
|
* @internal
|
|
*/
|
|
constructor() {}
|
|
|
|
static decodeInstructionType(
|
|
instruction: TransactionInstruction,
|
|
): LookupTableInstructionType {
|
|
this.checkProgramId(instruction.programId);
|
|
|
|
const instructionTypeLayout = BufferLayout.u32('instruction');
|
|
const index = instructionTypeLayout.decode(instruction.data);
|
|
|
|
let type: LookupTableInstructionType | undefined;
|
|
for (const [layoutType, layout] of Object.entries(
|
|
LOOKUP_TABLE_INSTRUCTION_LAYOUTS,
|
|
)) {
|
|
if ((layout as any).index == index) {
|
|
type = layoutType as LookupTableInstructionType;
|
|
break;
|
|
}
|
|
}
|
|
if (!type) {
|
|
throw new Error(
|
|
'Invalid Instruction. Should be a LookupTable Instruction',
|
|
);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
static decodeCreateLookupTable(
|
|
instruction: TransactionInstruction,
|
|
): CreateLookupTableParams {
|
|
this.checkProgramId(instruction.programId);
|
|
this.checkKeysLength(instruction.keys, 4);
|
|
|
|
const {recentSlot} = decodeData(
|
|
LOOKUP_TABLE_INSTRUCTION_LAYOUTS.CreateLookupTable,
|
|
instruction.data,
|
|
);
|
|
|
|
return {
|
|
authority: instruction.keys[1].pubkey,
|
|
payer: instruction.keys[2].pubkey,
|
|
recentSlot: Number(recentSlot),
|
|
};
|
|
}
|
|
|
|
static decodeExtendLookupTable(
|
|
instruction: TransactionInstruction,
|
|
): ExtendLookupTableParams {
|
|
this.checkProgramId(instruction.programId);
|
|
if (instruction.keys.length < 2) {
|
|
throw new Error(
|
|
`invalid instruction; found ${instruction.keys.length} keys, expected at least 2`,
|
|
);
|
|
}
|
|
|
|
const {addresses} = decodeData(
|
|
LOOKUP_TABLE_INSTRUCTION_LAYOUTS.ExtendLookupTable,
|
|
instruction.data,
|
|
);
|
|
return {
|
|
lookupTable: instruction.keys[0].pubkey,
|
|
authority: instruction.keys[1].pubkey,
|
|
payer:
|
|
instruction.keys.length > 2 ? instruction.keys[2].pubkey : undefined,
|
|
addresses: addresses.map(buffer => new PublicKey(buffer)),
|
|
};
|
|
}
|
|
|
|
static decodeCloseLookupTable(
|
|
instruction: TransactionInstruction,
|
|
): CloseLookupTableParams {
|
|
this.checkProgramId(instruction.programId);
|
|
this.checkKeysLength(instruction.keys, 3);
|
|
|
|
return {
|
|
lookupTable: instruction.keys[0].pubkey,
|
|
authority: instruction.keys[1].pubkey,
|
|
recipient: instruction.keys[2].pubkey,
|
|
};
|
|
}
|
|
|
|
static decodeFreezeLookupTable(
|
|
instruction: TransactionInstruction,
|
|
): FreezeLookupTableParams {
|
|
this.checkProgramId(instruction.programId);
|
|
this.checkKeysLength(instruction.keys, 2);
|
|
|
|
return {
|
|
lookupTable: instruction.keys[0].pubkey,
|
|
authority: instruction.keys[1].pubkey,
|
|
};
|
|
}
|
|
|
|
static decodeDeactivateLookupTable(
|
|
instruction: TransactionInstruction,
|
|
): DeactivateLookupTableParams {
|
|
this.checkProgramId(instruction.programId);
|
|
this.checkKeysLength(instruction.keys, 2);
|
|
|
|
return {
|
|
lookupTable: instruction.keys[0].pubkey,
|
|
authority: instruction.keys[1].pubkey,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
static checkProgramId(programId: PublicKey) {
|
|
if (!programId.equals(AddressLookupTableProgram.programId)) {
|
|
throw new Error(
|
|
'invalid instruction; programId is not AddressLookupTable Program',
|
|
);
|
|
}
|
|
}
|
|
/**
|
|
* @internal
|
|
*/
|
|
static checkKeysLength(keys: Array<any>, expectedLength: number) {
|
|
if (keys.length < expectedLength) {
|
|
throw new Error(
|
|
`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class AddressLookupTableProgram {
|
|
/**
|
|
* @internal
|
|
*/
|
|
constructor() {}
|
|
|
|
static programId: PublicKey = new PublicKey(
|
|
'AddressLookupTab1e1111111111111111111111111',
|
|
);
|
|
|
|
static createLookupTable(params: CreateLookupTableParams) {
|
|
const [lookupTableAddress, bumpSeed] = PublicKey.findProgramAddressSync(
|
|
[
|
|
params.authority.toBuffer(),
|
|
getU64Encoder().encode(params.recentSlot) as Uint8Array,
|
|
],
|
|
this.programId,
|
|
);
|
|
|
|
const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.CreateLookupTable;
|
|
const data = encodeData(type, {
|
|
recentSlot: BigInt(params.recentSlot),
|
|
bumpSeed: bumpSeed,
|
|
});
|
|
|
|
const keys = [
|
|
{
|
|
pubkey: lookupTableAddress,
|
|
isSigner: false,
|
|
isWritable: true,
|
|
},
|
|
{
|
|
pubkey: params.authority,
|
|
isSigner: true,
|
|
isWritable: false,
|
|
},
|
|
{
|
|
pubkey: params.payer,
|
|
isSigner: true,
|
|
isWritable: true,
|
|
},
|
|
{
|
|
pubkey: SystemProgram.programId,
|
|
isSigner: false,
|
|
isWritable: false,
|
|
},
|
|
];
|
|
|
|
return [
|
|
new TransactionInstruction({
|
|
programId: this.programId,
|
|
keys: keys,
|
|
data: data,
|
|
}),
|
|
lookupTableAddress,
|
|
] as [TransactionInstruction, PublicKey];
|
|
}
|
|
|
|
static freezeLookupTable(params: FreezeLookupTableParams) {
|
|
const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.FreezeLookupTable;
|
|
const data = encodeData(type);
|
|
|
|
const keys = [
|
|
{
|
|
pubkey: params.lookupTable,
|
|
isSigner: false,
|
|
isWritable: true,
|
|
},
|
|
{
|
|
pubkey: params.authority,
|
|
isSigner: true,
|
|
isWritable: false,
|
|
},
|
|
];
|
|
|
|
return new TransactionInstruction({
|
|
programId: this.programId,
|
|
keys: keys,
|
|
data: data,
|
|
});
|
|
}
|
|
|
|
static extendLookupTable(params: ExtendLookupTableParams) {
|
|
const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.ExtendLookupTable;
|
|
const data = encodeData(type, {
|
|
addresses: params.addresses.map(addr => addr.toBytes()),
|
|
});
|
|
|
|
const keys = [
|
|
{
|
|
pubkey: params.lookupTable,
|
|
isSigner: false,
|
|
isWritable: true,
|
|
},
|
|
{
|
|
pubkey: params.authority,
|
|
isSigner: true,
|
|
isWritable: false,
|
|
},
|
|
];
|
|
|
|
if (params.payer) {
|
|
keys.push(
|
|
{
|
|
pubkey: params.payer,
|
|
isSigner: true,
|
|
isWritable: true,
|
|
},
|
|
{
|
|
pubkey: SystemProgram.programId,
|
|
isSigner: false,
|
|
isWritable: false,
|
|
},
|
|
);
|
|
}
|
|
|
|
return new TransactionInstruction({
|
|
programId: this.programId,
|
|
keys: keys,
|
|
data: data,
|
|
});
|
|
}
|
|
|
|
static deactivateLookupTable(params: DeactivateLookupTableParams) {
|
|
const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.DeactivateLookupTable;
|
|
const data = encodeData(type);
|
|
|
|
const keys = [
|
|
{
|
|
pubkey: params.lookupTable,
|
|
isSigner: false,
|
|
isWritable: true,
|
|
},
|
|
{
|
|
pubkey: params.authority,
|
|
isSigner: true,
|
|
isWritable: false,
|
|
},
|
|
];
|
|
|
|
return new TransactionInstruction({
|
|
programId: this.programId,
|
|
keys: keys,
|
|
data: data,
|
|
});
|
|
}
|
|
|
|
static closeLookupTable(params: CloseLookupTableParams) {
|
|
const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.CloseLookupTable;
|
|
const data = encodeData(type);
|
|
|
|
const keys = [
|
|
{
|
|
pubkey: params.lookupTable,
|
|
isSigner: false,
|
|
isWritable: true,
|
|
},
|
|
{
|
|
pubkey: params.authority,
|
|
isSigner: true,
|
|
isWritable: false,
|
|
},
|
|
{
|
|
pubkey: params.recipient,
|
|
isSigner: false,
|
|
isWritable: true,
|
|
},
|
|
];
|
|
|
|
return new TransactionInstruction({
|
|
programId: this.programId,
|
|
keys: keys,
|
|
data: data,
|
|
});
|
|
}
|
|
}
|