diff --git a/frontend/hooks/useStakeConnection.ts b/frontend/hooks/useStakeConnection.ts index de0cd1f7..771d1cf7 100644 --- a/frontend/hooks/useStakeConnection.ts +++ b/frontend/hooks/useStakeConnection.ts @@ -1,6 +1,7 @@ import { Wallet } from '@coral-xyz/anchor' import { STAKING_ADDRESS, StakeConnection } from '@pythnetwork/staking' import { useAnchorWallet, useConnection } from '@solana/wallet-adapter-react' +import { PublicKey } from '@solana/web3.js' import toast from 'react-hot-toast' import { useQuery } from 'react-query' import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' @@ -17,7 +18,10 @@ export function useStakeConnection() { connection, // anchor wallet is defined, as we have used enabled below anchorWallet as Wallet, - STAKING_ADDRESS + STAKING_ADDRESS, + process.env.ADDRESS_LOOKUP_TABLE + ? new PublicKey(process.env.ADDRESS_LOOKUP_TABLE) + : undefined ) }, { diff --git a/frontend/next.config.js b/frontend/next.config.js index 64ade807..c08eb53b 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -11,6 +11,7 @@ module.exports = { env: { ENDPOINT: process.env.ENDPOINT, CLUSTER: process.env.CLUSTER, + ADDRESS_LOOKUP_TABLE: process.env.ADDRESS_LOOKUP_TABLE, }, webpack(config, { isServer }) { config.experiments = { asyncWebAssembly: true, layers: true } diff --git a/staking/app/StakeConnection.ts b/staking/app/StakeConnection.ts index 8c2acdbe..841fbf9d 100644 --- a/staking/app/StakeConnection.ts +++ b/staking/app/StakeConnection.ts @@ -17,6 +17,9 @@ import { SYSVAR_CLOCK_PUBKEY, ComputeBudgetProgram, SystemProgram, + TransactionMessage, + VersionedTransaction, + AddressLookupTableAccount, } from "@solana/web3.js"; import * as wasm2 from "@pythnetwork/staking-wasm"; import { @@ -76,6 +79,7 @@ export class StakeConnection { votingProduct = { voting: {} }; votingAccountMetadataWasm: any; governanceAddress: PublicKey; + addressLookupTable: PublicKey | undefined; private constructor( program: Program, @@ -83,7 +87,8 @@ export class StakeConnection { config: GlobalConfig, configAddress: PublicKey, votingProductMetadataAccount: PublicKey, - votingAccountMetadataWasm: any + votingAccountMetadataWasm: any, + addressLookupTable: PublicKey | undefined ) { this.program = program; this.provider = provider; @@ -92,6 +97,7 @@ export class StakeConnection { this.votingProductMetadataAccount = votingProductMetadataAccount; this.governanceAddress = GOVERNANCE_ADDRESS(); this.votingAccountMetadataWasm = votingAccountMetadataWasm; + this.addressLookupTable = addressLookupTable; } public static async connect( @@ -110,7 +116,8 @@ export class StakeConnection { public static async createStakeConnection( connection: Connection, wallet: Wallet, - stakingProgramAddress: PublicKey + stakingProgramAddress: PublicKey, + addressLookupTable?: PublicKey ): Promise { const provider = new AnchorProvider(connection, wallet, {}); const program = new Program( @@ -157,10 +164,39 @@ export class StakeConnection { config, configAddress, votingProductMetadataAccount, - votingAccountMetadataWasm + votingAccountMetadataWasm, + addressLookupTable ); } + private async sendAndConfirmAsVersionedTransaction( + instructions: TransactionInstruction[], + signers: Signer[] + ) { + const addressLookupTables: AddressLookupTableAccount[] | undefined = this + .addressLookupTable + ? [ + ( + await this.program.provider.connection.getAddressLookupTable( + new PublicKey(this.addressLookupTable) + ) + ).value, + ] + : []; + const message = new TransactionMessage({ + instructions: instructions, + recentBlockhash: (await this.provider.connection.getLatestBlockhash()) + .blockhash, + payerKey: this.provider.wallet.publicKey, + }); + const versionedTransaction = new VersionedTransaction( + message.compileToV0Message(addressLookupTables) + ); + return await this.provider.sendAndConfirm(versionedTransaction, signers, { + skipPreflight: true, + }); + } + /** The public key of the user of the staking program. This connection sends transactions as this user. */ public userPublicKey(): PublicKey { return this.provider.wallet.publicKey; @@ -634,9 +670,11 @@ export class StakeConnection { .instruction() ); - await this.provider.sendAndConfirm(transaction); + await this.sendAndConfirmAsVersionedTransaction( + transaction.instructions, + [] + ); } - public async setupVestingAccount( amount: PythBalance, owner: PublicKey, @@ -816,15 +854,17 @@ export class StakeConnection { // Each of these instructions is 27 bytes (<< 1232) so we don't cap how many of them we fit in the transaction ixs.push(...(await this.buildCleanupUnlockedPositions(stakeAccount))); // Try to make room by closing unlocked positions } - await this.program.methods - .createPosition(this.votingProduct, amount.toBN()) - .preInstructions(ixs) - .accounts({ - stakeAccountPositions: stakeAccountAddress, - targetAccount: this.votingProductMetadataAccount, - }) - .signers(signers) - .rpc({ skipPreflight: true }); + ixs.push( + await this.program.methods + .createPosition(this.votingProduct, amount.toBN()) + .accounts({ + stakeAccountPositions: stakeAccountAddress, + targetAccount: this.votingProductMetadataAccount, + }) + .instruction() + ); + + await this.sendAndConfirmAsVersionedTransaction(ixs, signers); } public async buildCleanupUnlockedPositions( diff --git a/staking/app/deploy/create_account_lookup_table.ts b/staking/app/deploy/create_account_lookup_table.ts new file mode 100644 index 00000000..239c67bc --- /dev/null +++ b/staking/app/deploy/create_account_lookup_table.ts @@ -0,0 +1,16 @@ +import { Wallet, AnchorProvider } from "@coral-xyz/anchor"; +import { Connection } from "@solana/web3.js"; +import { AUTHORITY_KEYPAIR, PYTH_TOKEN, RPC_NODE } from "./devnet"; +import { initAddressLookupTable } from "../../tests/utils/utils"; +async function main() { + const client = new Connection(RPC_NODE); + const provider = new AnchorProvider( + client, + new Wallet(AUTHORITY_KEYPAIR), + {} + ); + const lookupTableAddress = await initAddressLookupTable(provider, PYTH_TOKEN); + console.log("Lookup table address: ", lookupTableAddress.toBase58()); +} + +main(); diff --git a/staking/tests/utils/before.ts b/staking/tests/utils/before.ts index 8b0a813f..a3f37f5b 100644 --- a/staking/tests/utils/before.ts +++ b/staking/tests/utils/before.ts @@ -35,7 +35,11 @@ import path from "path"; import os from "os"; import { StakeConnection, PythBalance, PYTH_DECIMALS } from "../../app"; import { GlobalConfig } from "../../app/StakeConnection"; -import { createMint, getTargetAccount as getTargetAccount } from "./utils"; +import { + createMint, + getTargetAccount as getTargetAccount, + initAddressLookupTable, +} from "./utils"; import { loadKeypair } from "./keys"; export const ANCHOR_CONFIG_PATH = "./Anchor.toml"; @@ -503,6 +507,12 @@ export async function standardSetup( await initGovernanceProduct(program, user); + const lookupTableAddress = await initAddressLookupTable( + provider, + pythMintAccount.publicKey + ); + console.log("Lookup table address: ", lookupTableAddress.toBase58()); + // Give the power back to the people await program.methods .updateGovernanceAuthority(globalConfig.governanceAuthority) diff --git a/staking/tests/utils/utils.ts b/staking/tests/utils/utils.ts index 0af8483c..c7a704aa 100644 --- a/staking/tests/utils/utils.ts +++ b/staking/tests/utils/utils.ts @@ -1,18 +1,31 @@ -import { Token, MintLayout } from "@solana/spl-token"; +import { Token, MintLayout, TOKEN_PROGRAM_ID } from "@solana/spl-token"; import { PublicKey, Keypair, Transaction, SystemProgram, + AddressLookupTableProgram, + ComputeBudgetProgram, + SYSVAR_RENT_PUBKEY, + VersionedTransaction, + TransactionMessage, } from "@solana/web3.js"; import * as anchor from "@coral-xyz/anchor"; -import { AnchorError, ProgramError } from "@coral-xyz/anchor"; +import { AnchorError } from "@coral-xyz/anchor"; import assert from "assert"; import * as wasm from "@pythnetwork/staking-wasm"; import { Staking } from "../../target/types/staking"; +import { GOVERNANCE_ADDRESS, REALM_ID, STAKING_ADDRESS } from "../../app"; type StakeTarget = anchor.IdlTypes["Target"]; +export function getConfigAccount(programId: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync( + [anchor.utils.bytes.utf8.encode(wasm.Constants.CONFIG_SEED())], + programId + )[0]; +} + export async function getTargetAccount( stakeTarget: StakeTarget, programId: PublicKey @@ -76,6 +89,50 @@ export async function createMint( }); } +export async function initAddressLookupTable( + provider: anchor.AnchorProvider, + mint: PublicKey +) { + const configAccount = getConfigAccount(STAKING_ADDRESS); + const targetAccount = await getTargetAccount({ voting: {} }, STAKING_ADDRESS); + + const [loookupTableInstruction, lookupTableAddress] = + AddressLookupTableProgram.createLookupTable({ + authority: provider.publicKey, + payer: provider.publicKey, + recentSlot: await provider.connection.getSlot(), + }); + const extendInstruction = AddressLookupTableProgram.extendLookupTable({ + payer: provider.publicKey, + authority: provider.publicKey, + lookupTable: lookupTableAddress, + addresses: [ + ComputeBudgetProgram.programId, + SystemProgram.programId, + STAKING_ADDRESS, + REALM_ID, + mint, + configAccount, + SYSVAR_RENT_PUBKEY, + TOKEN_PROGRAM_ID, + GOVERNANCE_ADDRESS(), + targetAccount, + ], + }); + const createLookupTableTx = new VersionedTransaction( + new TransactionMessage({ + instructions: [loookupTableInstruction, extendInstruction], + payerKey: provider.publicKey, + recentBlockhash: (await provider.connection.getLatestBlockhash()) + .blockhash, + }).compileToV0Message() + ); + await provider.sendAndConfirm(createLookupTableTx, [], { + skipPreflight: true, + }); + return lookupTableAddress; +} + /** * Sends the rpc call and check whether the error message matches the provided string * @param rpcCall : anchor rpc call