From 58b22f0975bc789bc5b33d6359a7b5ce7b13c9c4 Mon Sep 17 00:00:00 2001 From: shubhiscoding Date: Sat, 12 Jul 2025 15:47:50 +0530 Subject: [PATCH 1/4] prohibit adding nonce account to address lookup table --- packages/instructions/src/accounts.ts | 5 +++++ .../transaction-messages/src/compress-transaction-message.ts | 3 ++- .../transaction-messages/src/durable-nonce-instruction.ts | 5 +++-- packages/transaction-messages/src/durable-nonce.ts | 3 +++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/instructions/src/accounts.ts b/packages/instructions/src/accounts.ts index ae3e2d6f7..f0361971f 100644 --- a/packages/instructions/src/accounts.ts +++ b/packages/instructions/src/accounts.ts @@ -23,6 +23,7 @@ import { AccountRole } from './roles'; export interface AccountMeta { readonly address: Address; readonly role: AccountRole; + readonly static?: boolean; } /** @@ -48,6 +49,10 @@ export type WritableSignerAccount = AccountMet role: AccountRole.WRITABLE_SIGNER; }; +export type StaticAccount = TAccount & { + readonly static: true; +}; + /** * Represents a lookup of the account's address in an address lookup table. It specifies which * lookup table account in which to perform the lookup, the index of the desired account address in diff --git a/packages/transaction-messages/src/compress-transaction-message.ts b/packages/transaction-messages/src/compress-transaction-message.ts index 12d236b0b..529224c10 100644 --- a/packages/transaction-messages/src/compress-transaction-message.ts +++ b/packages/transaction-messages/src/compress-transaction-message.ts @@ -105,7 +105,8 @@ export function compressTransactionMessageUsingAddressLookupTables< if ( 'lookupTableAddress' in account || !lookupTableAddresses.has(account.address) || - isSignerRole(account.role) + isSignerRole(account.role)|| + account.static === true ) { newAccounts.push(account); continue; diff --git a/packages/transaction-messages/src/durable-nonce-instruction.ts b/packages/transaction-messages/src/durable-nonce-instruction.ts index a81a36853..1cfcd3c15 100644 --- a/packages/transaction-messages/src/durable-nonce-instruction.ts +++ b/packages/transaction-messages/src/durable-nonce-instruction.ts @@ -8,6 +8,7 @@ import { isSignerRole, ReadonlyAccount, ReadonlySignerAccount, + StaticAccount, WritableAccount, WritableSignerAccount, } from '@solana/instructions'; @@ -19,7 +20,7 @@ export type AdvanceNonceAccountInstruction< > = Instruction<'11111111111111111111111111111111'> & InstructionWithAccounts< readonly [ - WritableAccount, + StaticAccount>, ReadonlyAccount<'SysvarRecentB1ockHashes11111111111111111111'>, ReadonlySignerAccount | WritableSignerAccount, ] @@ -54,7 +55,7 @@ export function createAdvanceNonceAccountInstruction< ): AdvanceNonceAccountInstruction { return { accounts: [ - { address: nonceAccountAddress, role: AccountRole.WRITABLE }, + { address: nonceAccountAddress, role: AccountRole.WRITABLE, static: true }, { address: RECENT_BLOCKHASHES_SYSVAR_ADDRESS, role: AccountRole.READONLY, diff --git a/packages/transaction-messages/src/durable-nonce.ts b/packages/transaction-messages/src/durable-nonce.ts index f2a9885f6..e6c453a7d 100644 --- a/packages/transaction-messages/src/durable-nonce.ts +++ b/packages/transaction-messages/src/durable-nonce.ts @@ -142,6 +142,9 @@ function isAdvanceNonceAccountInstructionForNonce< nonceAccountAddress: TNonceAccountAddress, nonceAuthorityAddress: TNonceAuthorityAddress, ): instruction is AdvanceNonceAccountInstruction { + if (!instruction.accounts[0]?.static) { + throw new Error('Nonce account must be marked as static'); + } return ( instruction.accounts[0].address === nonceAccountAddress && instruction.accounts[2].address === nonceAuthorityAddress From 8090572b57e9796a25d243a6ec0a5d43af984ef6 Mon Sep 17 00:00:00 2001 From: shubhiscoding Date: Sun, 13 Jul 2025 02:29:52 +0530 Subject: [PATCH 2/4] updated tests and convert instruction function --- .../src/__tests__/decompile-message-test.ts | 5 ++++- .../src/__tests__/durable-nonce-test.ts | 4 ++-- .../transaction-messages/src/decompile-message.ts | 15 ++++++++++++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/transaction-messages/src/__tests__/decompile-message-test.ts b/packages/transaction-messages/src/__tests__/decompile-message-test.ts index 742d8d746..87890a0e2 100644 --- a/packages/transaction-messages/src/__tests__/decompile-message-test.ts +++ b/packages/transaction-messages/src/__tests__/decompile-message-test.ts @@ -342,6 +342,7 @@ describe('decompileTransactionMessage', () => { { address: nonceAccountAddress, role: AccountRole.WRITABLE, + static: true, }, { address: recentBlockhashesSysvarAddress, @@ -437,6 +438,7 @@ describe('decompileTransactionMessage', () => { { address: nonceAccountAddress, role: AccountRole.WRITABLE, + static: true, }, { address: recentBlockhashesSysvarAddress, @@ -501,6 +503,7 @@ describe('decompileTransactionMessage', () => { { address: nonceAccountAddress, role: AccountRole.WRITABLE, + static: true, }, { address: recentBlockhashesSysvarAddress, @@ -627,7 +630,7 @@ describe('decompileTransactionMessage', () => { expect(transaction.instructions).toBeFrozenObject(); }); }); - + describe('for a transaction with address lookup tables', () => { const blockhash = 'J4yED2jcMAHyQUg61DBmm4njmEydUr2WqrV9cdEcDDgL'; const programAddress = 'HZMKVnRrWLyQLwPLTTLKtY7ET4Cf7pQugrTr9eTBrpsf' as Address; diff --git a/packages/transaction-messages/src/__tests__/durable-nonce-test.ts b/packages/transaction-messages/src/__tests__/durable-nonce-test.ts index 8e21b32d7..1f59ff4dd 100644 --- a/packages/transaction-messages/src/__tests__/durable-nonce-test.ts +++ b/packages/transaction-messages/src/__tests__/durable-nonce-test.ts @@ -1,7 +1,7 @@ import '@solana/test-matchers/toBeFrozenObject'; import { Address } from '@solana/addresses'; -import { AccountRole, Instruction, ReadonlySignerAccount, WritableAccount } from '@solana/instructions'; +import { AccountRole, Instruction, ReadonlySignerAccount, StaticAccount, WritableAccount } from '@solana/instructions'; import type { Blockhash } from '@solana/rpc-types'; import { TransactionMessageWithBlockhashLifetime } from '../blockhash'; @@ -25,7 +25,7 @@ function createMockAdvanceNonceAccountInstruction< }): TransactionMessageWithDurableNonceLifetime['instructions'][0] { return { accounts: [ - { address: nonceAccountAddress, role: AccountRole.WRITABLE } as WritableAccount, + { address: nonceAccountAddress, role: AccountRole.WRITABLE, static: true } as StaticAccount>, { address: 'SysvarRecentB1ockHashes11111111111111111111' as Address<'SysvarRecentB1ockHashes11111111111111111111'>, diff --git a/packages/transaction-messages/src/decompile-message.ts b/packages/transaction-messages/src/decompile-message.ts index 326bb7afb..898f90ef3 100644 --- a/packages/transaction-messages/src/decompile-message.ts +++ b/packages/transaction-messages/src/decompile-message.ts @@ -136,8 +136,21 @@ function convertInstruction( index: instruction.programAddressIndex, }); } + const isAdvanceNonce = programAddress === '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'> && + instruction && instruction.data && + instruction.data.length === 4 && + instruction.data[0] === 4 && + instruction.data[1] === 0 && + instruction.data[2] === 0 && + instruction.data[3] === 0; - const accounts = instruction.accountIndices?.map(accountIndex => accountMetas[accountIndex]); + const accounts = instruction.accountIndices?.map((accountIndex, idx) => { + const accountMeta = accountMetas[accountIndex]; + if (isAdvanceNonce && idx === 0) { + return { ...accountMeta, static: true }; + } + return accountMeta; + }); const { data } = instruction; return Object.freeze({ From 75e070be3a62d34671eb3711b47e91610df03e33 Mon Sep 17 00:00:00 2001 From: shubhiscoding Date: Mon, 14 Jul 2025 19:18:39 +0530 Subject: [PATCH 3/4] updated tests and getAccountMetas function --- .../src/__tests__/decompile-message-test.ts | 18 +++++++++++++++++- .../src/__tests__/durable-nonce-test.ts | 4 ++-- .../src/decompile-message.ts | 4 ++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/transaction-messages/src/__tests__/decompile-message-test.ts b/packages/transaction-messages/src/__tests__/decompile-message-test.ts index 742d8d746..e609d589e 100644 --- a/packages/transaction-messages/src/__tests__/decompile-message-test.ts +++ b/packages/transaction-messages/src/__tests__/decompile-message-test.ts @@ -138,18 +138,22 @@ describe('decompileTransactionMessage', () => { { address: 'H4RdPRWYk3pKw2CkNznxQK6J6herjgQke2pzFJW4GC6x' as Address, role: AccountRole.WRITABLE_SIGNER, + static: true, }, { address: 'G35QeFd4jpXWfRkuRKwn8g4vYrmn8DWJ5v88Kkpd8z1V' as Address, role: AccountRole.READONLY_SIGNER, + static: true, }, { address: '3LeBzRE9Yna5zi9R8vdT3MiNQYuEp4gJgVyhhwmqfCtd' as Address, role: AccountRole.WRITABLE, + static: true, }, { address: '8kud9bpNvfemXYdTFjs5cZ8fZinBkx8JAnhVmRwJZk5e' as Address, role: AccountRole.READONLY, + static: true, }, ], data: new Uint8Array([0, 1, 2, 3, 4]), @@ -342,14 +346,17 @@ describe('decompileTransactionMessage', () => { { address: nonceAccountAddress, role: AccountRole.WRITABLE, + static: true, }, { address: recentBlockhashesSysvarAddress, role: AccountRole.READONLY, + static: true, }, { address: nonceAuthorityAddress, role: AccountRole.WRITABLE_SIGNER, + static: true, }, ], data: new Uint8Array([4, 0, 0, 0]), @@ -437,14 +444,17 @@ describe('decompileTransactionMessage', () => { { address: nonceAccountAddress, role: AccountRole.WRITABLE, + static: true, }, { address: recentBlockhashesSysvarAddress, role: AccountRole.READONLY, + static: true, }, { address: nonceAuthorityAddress, role: AccountRole.READONLY_SIGNER, + static: true, }, ], data: new Uint8Array([4, 0, 0, 0]), @@ -501,14 +511,17 @@ describe('decompileTransactionMessage', () => { { address: nonceAccountAddress, role: AccountRole.WRITABLE, + static: true, }, { address: recentBlockhashesSysvarAddress, role: AccountRole.READONLY, + static: true, }, { address: nonceAuthorityAddress, role: AccountRole.WRITABLE_SIGNER, + static: true, }, ], data: new Uint8Array([4, 0, 0, 0]), @@ -519,10 +532,12 @@ describe('decompileTransactionMessage', () => { { address: nonceAuthorityAddress, role: AccountRole.WRITABLE_SIGNER, + static: true, }, { address: nonceAccountAddress, role: AccountRole.WRITABLE, + static: true, }, ], data: new Uint8Array([1, 2, 3, 4]), @@ -627,7 +642,7 @@ describe('decompileTransactionMessage', () => { expect(transaction.instructions).toBeFrozenObject(); }); }); - + describe('for a transaction with address lookup tables', () => { const blockhash = 'J4yED2jcMAHyQUg61DBmm4njmEydUr2WqrV9cdEcDDgL'; const programAddress = 'HZMKVnRrWLyQLwPLTTLKtY7ET4Cf7pQugrTr9eTBrpsf' as Address; @@ -965,6 +980,7 @@ describe('decompileTransactionMessage', () => { const expectedAccountMeta: AccountMeta = { address: staticAddress, role: AccountRole.READONLY, + static: true, }; const expectedAccountLookupMeta: AccountLookupMeta = { diff --git a/packages/transaction-messages/src/__tests__/durable-nonce-test.ts b/packages/transaction-messages/src/__tests__/durable-nonce-test.ts index 8e21b32d7..1f59ff4dd 100644 --- a/packages/transaction-messages/src/__tests__/durable-nonce-test.ts +++ b/packages/transaction-messages/src/__tests__/durable-nonce-test.ts @@ -1,7 +1,7 @@ import '@solana/test-matchers/toBeFrozenObject'; import { Address } from '@solana/addresses'; -import { AccountRole, Instruction, ReadonlySignerAccount, WritableAccount } from '@solana/instructions'; +import { AccountRole, Instruction, ReadonlySignerAccount, StaticAccount, WritableAccount } from '@solana/instructions'; import type { Blockhash } from '@solana/rpc-types'; import { TransactionMessageWithBlockhashLifetime } from '../blockhash'; @@ -25,7 +25,7 @@ function createMockAdvanceNonceAccountInstruction< }): TransactionMessageWithDurableNonceLifetime['instructions'][0] { return { accounts: [ - { address: nonceAccountAddress, role: AccountRole.WRITABLE } as WritableAccount, + { address: nonceAccountAddress, role: AccountRole.WRITABLE, static: true } as StaticAccount>, { address: 'SysvarRecentB1ockHashes11111111111111111111' as Address<'SysvarRecentB1ockHashes11111111111111111111'>, diff --git a/packages/transaction-messages/src/decompile-message.ts b/packages/transaction-messages/src/decompile-message.ts index 326bb7afb..3d13ef5fc 100644 --- a/packages/transaction-messages/src/decompile-message.ts +++ b/packages/transaction-messages/src/decompile-message.ts @@ -35,6 +35,7 @@ function getAccountMetas(message: CompiledTransactionMessage): AccountMeta[] { accountMetas.push({ address: message.staticAccounts[accountIndex], role: AccountRole.WRITABLE_SIGNER, + static: true, }); accountIndex++; } @@ -43,6 +44,7 @@ function getAccountMetas(message: CompiledTransactionMessage): AccountMeta[] { accountMetas.push({ address: message.staticAccounts[accountIndex], role: AccountRole.READONLY_SIGNER, + static: true, }); accountIndex++; } @@ -51,6 +53,7 @@ function getAccountMetas(message: CompiledTransactionMessage): AccountMeta[] { accountMetas.push({ address: message.staticAccounts[accountIndex], role: AccountRole.WRITABLE, + static: true, }); accountIndex++; } @@ -59,6 +62,7 @@ function getAccountMetas(message: CompiledTransactionMessage): AccountMeta[] { accountMetas.push({ address: message.staticAccounts[accountIndex], role: AccountRole.READONLY, + static: true, }); accountIndex++; } From 65802ca460a6a11a2c5b16484933187a1425164e Mon Sep 17 00:00:00 2001 From: shubhiscoding Date: Tue, 15 Jul 2025 10:53:42 +0530 Subject: [PATCH 4/4] reverted changes in converInstruction function --- .../transaction-messages/src/decompile-message.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/transaction-messages/src/decompile-message.ts b/packages/transaction-messages/src/decompile-message.ts index 84177dab1..3d13ef5fc 100644 --- a/packages/transaction-messages/src/decompile-message.ts +++ b/packages/transaction-messages/src/decompile-message.ts @@ -140,21 +140,8 @@ function convertInstruction( index: instruction.programAddressIndex, }); } - const isAdvanceNonce = programAddress === '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'> && - instruction && instruction.data && - instruction.data.length === 4 && - instruction.data[0] === 4 && - instruction.data[1] === 0 && - instruction.data[2] === 0 && - instruction.data[3] === 0; - const accounts = instruction.accountIndices?.map((accountIndex, idx) => { - const accountMeta = accountMetas[accountIndex]; - if (isAdvanceNonce && idx === 0) { - return { ...accountMeta, static: true }; - } - return accountMeta; - }); + const accounts = instruction.accountIndices?.map(accountIndex => accountMetas[accountIndex]); const { data } = instruction; return Object.freeze({