Skip to content

Commit 62d189e

Browse files
authored
feat(solana_utils): support account lookup table (#1424)
* feat: support account lookup table * remove console log * Support ALT * Commas * feat: support lta * Go * Bump
1 parent bb830e1 commit 62d189e

File tree

4 files changed

+90
-22
lines changed

4 files changed

+90
-22
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

target_chains/solana/sdk/js/solana_utils/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/solana-utils",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"description": "Utility functions for Solana",
55
"homepage": "https://pyth.network",
66
"main": "lib/index.js",

target_chains/solana/sdk/js/solana_utils/src/__tests__/TransactionSize.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
AddressLookupTableAccount,
23
ComputeBudgetProgram,
34
Keypair,
45
PublicKey,
@@ -81,4 +82,33 @@ it("Unit test for getSizeOfTransaction", async () => {
8182
expect(versionedTransaction.serialize().length).toBe(
8283
getSizeOfTransaction(ixsToSend)
8384
);
85+
86+
const addressLookupTable: AddressLookupTableAccount =
87+
new AddressLookupTableAccount({
88+
key: PublicKey.unique(),
89+
state: {
90+
lastExtendedSlot: 0,
91+
lastExtendedSlotStartIndex: 0,
92+
deactivationSlot: BigInt(0),
93+
addresses: [
94+
SystemProgram.programId,
95+
ComputeBudgetProgram.programId,
96+
...ixsToSend[0].keys.map((key) => key.pubkey),
97+
...ixsToSend[1].keys.map((key) => key.pubkey),
98+
...ixsToSend[2].keys.map((key) => key.pubkey),
99+
],
100+
},
101+
});
102+
103+
const versionedTransactionWithAlt = new VersionedTransaction(
104+
new TransactionMessage({
105+
recentBlockhash: transaction.recentBlockhash,
106+
payerKey: payer.publicKey,
107+
instructions: ixsToSend,
108+
}).compileToV0Message([addressLookupTable])
109+
);
110+
111+
expect(versionedTransactionWithAlt.serialize().length).toBe(
112+
getSizeOfTransaction(ixsToSend, true, addressLookupTable)
113+
);
84114
});

target_chains/solana/sdk/js/solana_utils/src/transaction.ts

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Wallet } from "@coral-xyz/anchor";
22
import {
3+
AddressLookupTableAccount,
34
ComputeBudgetProgram,
45
Connection,
56
PACKET_DATA_SIZE,
@@ -63,7 +64,7 @@ export const DEFAULT_PRIORITY_FEE_CONFIG: PriorityFeeConfig = {
6364
* - A compact array of instructions
6465
*
6566
* If the transaction is a `VersionedTransaction`, it also contains an extra byte at the beginning, indicating the version and an array of `MessageAddressTableLookup` at the end.
66-
* We don't support Account Lookup Tables, so that array has a size of 0.
67+
* After this field there is an array of indexes into the address lookup table that represents the accounts from the address lookup table used in the transaction.
6768
*
6869
* Each instruction has the following layout :
6970
* - One byte indicating the index of the program in the account addresses array
@@ -72,19 +73,22 @@ export const DEFAULT_PRIORITY_FEE_CONFIG: PriorityFeeConfig = {
7273
*/
7374
export function getSizeOfTransaction(
7475
instructions: TransactionInstruction[],
75-
versionedTransaction = true
76+
versionedTransaction = true,
77+
addressLookupTable?: AddressLookupTableAccount
7678
): number {
79+
const programs = new Set<string>();
7780
const signers = new Set<string>();
78-
const accounts = new Set<string>();
81+
let accounts = new Set<string>();
7982

8083
instructions.map((ix) => {
81-
accounts.add(ix.programId.toBase58()),
82-
ix.keys.map((key) => {
83-
if (key.isSigner) {
84-
signers.add(key.pubkey.toBase58());
85-
}
86-
accounts.add(key.pubkey.toBase58());
87-
});
84+
programs.add(ix.programId.toBase58());
85+
accounts.add(ix.programId.toBase58());
86+
ix.keys.map((key) => {
87+
if (key.isSigner) {
88+
signers.add(key.pubkey.toBase58());
89+
}
90+
accounts.add(key.pubkey.toBase58());
91+
});
8892
});
8993

9094
const instruction_sizes: number = instructions
@@ -98,6 +102,19 @@ export function getSizeOfTransaction(
98102
)
99103
.reduce((a, b) => a + b, 0);
100104

105+
let numberOfAddressLookups = 0;
106+
if (addressLookupTable) {
107+
const lookupTableAddresses = addressLookupTable.state.addresses.map(
108+
(address) => address.toBase58()
109+
);
110+
const totalNumberOfAccounts = accounts.size;
111+
accounts = new Set(
112+
[...accounts].filter((account) => !lookupTableAddresses.includes(account))
113+
);
114+
accounts = new Set([...accounts, ...programs, ...signers]);
115+
numberOfAddressLookups = totalNumberOfAccounts - accounts.size; // This number is equal to the number of accounts that are in the lookup table and are neither signers nor programs
116+
}
117+
101118
return (
102119
getSizeOfCompressedU16(signers.size) +
103120
signers.size * 64 + // array of signatures
@@ -107,7 +124,10 @@ export function getSizeOfTransaction(
107124
32 + // recent blockhash
108125
getSizeOfCompressedU16(instructions.length) +
109126
instruction_sizes + // array of instructions
110-
(versionedTransaction ? 1 + getSizeOfCompressedU16(0) : 0) // we don't support Account Lookup Tables
127+
(versionedTransaction ? 1 + getSizeOfCompressedU16(0) : 0) + // transaction version and number of address lookup tables
128+
(versionedTransaction && addressLookupTable ? 32 : 0) + // address lookup table address (we only support 1 address lookup table)
129+
(versionedTransaction && addressLookupTable ? 2 : 0) + // number of address lookup indexes
130+
numberOfAddressLookups // address lookup indexes
111131
);
112132
}
113133

@@ -130,11 +150,17 @@ export class TransactionBuilder {
130150
}[] = [];
131151
readonly payer: PublicKey;
132152
readonly connection: Connection;
153+
readonly addressLookupTable: AddressLookupTableAccount | undefined;
133154

134155
/** Make a new `TransactionBuilder`. It requires a `payer` to populate the `payerKey` field and a connection to populate `recentBlockhash` in the versioned transactions. */
135-
constructor(payer: PublicKey, connection: Connection) {
156+
constructor(
157+
payer: PublicKey,
158+
connection: Connection,
159+
accountLookupTable?: AddressLookupTableAccount
160+
) {
136161
this.payer = payer;
137162
this.connection = connection;
163+
this.addressLookupTable = accountLookupTable;
138164
}
139165

140166
/**
@@ -149,11 +175,16 @@ export class TransactionBuilder {
149175
computeUnits: computeUnits ?? 0,
150176
});
151177
} else if (
152-
getSizeOfTransaction([
153-
...this.transactionInstructions[this.transactionInstructions.length - 1]
154-
.instructions,
155-
instruction,
156-
]) <= PACKET_DATA_SIZE_WITH_ROOM_FOR_COMPUTE_BUDGET
178+
getSizeOfTransaction(
179+
[
180+
...this.transactionInstructions[
181+
this.transactionInstructions.length - 1
182+
].instructions,
183+
instruction,
184+
],
185+
true,
186+
this.addressLookupTable
187+
) <= PACKET_DATA_SIZE_WITH_ROOM_FOR_COMPUTE_BUDGET
157188
) {
158189
this.transactionInstructions[
159190
this.transactionInstructions.length - 1
@@ -218,7 +249,9 @@ export class TransactionBuilder {
218249
recentBlockhash: blockhash,
219250
instructions: instructionsWithComputeBudget,
220251
payerKey: this.payer,
221-
}).compileToV0Message()
252+
}).compileToV0Message(
253+
this.addressLookupTable ? [this.addressLookupTable] : []
254+
)
222255
),
223256
signers: signers,
224257
};
@@ -289,9 +322,14 @@ export class TransactionBuilder {
289322
payer: PublicKey,
290323
connection: Connection,
291324
instructions: InstructionWithEphemeralSigners[],
292-
priorityFeeConfig: PriorityFeeConfig
325+
priorityFeeConfig: PriorityFeeConfig,
326+
addressLookupTable?: AddressLookupTableAccount
293327
): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> {
294-
const transactionBuilder = new TransactionBuilder(payer, connection);
328+
const transactionBuilder = new TransactionBuilder(
329+
payer,
330+
connection,
331+
addressLookupTable
332+
);
295333
transactionBuilder.addInstructions(instructions);
296334
return transactionBuilder.buildVersionedTransactions(priorityFeeConfig);
297335
}

0 commit comments

Comments
 (0)