Skip to content

Commit a71a2db

Browse files
authored
refactor(experimental): errors: compat package (#2202)
Adds custom `SolanaError` throws to the `@solana/compat` package.
1 parent 91a360d commit a71a2db

File tree

7 files changed

+68
-20
lines changed

7 files changed

+68
-20
lines changed

packages/compat/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"node": ">=17.4"
6767
},
6868
"dependencies": {
69+
"@solana/errors": "workspace:*",
6970
"@solana/functional": "workspace:*",
7071
"@solana/instructions": "workspace:*",
7172
"@solana/keys": "workspace:*",

packages/compat/src/__tests__/transaction-test.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { Buffer } from 'node:buffer';
22

33
import { Address } from '@solana/addresses';
4+
import {
5+
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE,
6+
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS,
7+
SolanaError,
8+
} from '@solana/errors';
49
import { AccountRole, IInstruction } from '@solana/instructions';
510
import { SignatureBytes } from '@solana/keys';
611
import { ITransactionWithSignatures, Nonce } from '@solana/transactions';
@@ -639,7 +644,7 @@ describe('fromVersionedTransactionWithDurableNonce', () => {
639644

640645
expect(() => {
641646
fromVersionedTransactionWithDurableNonce(oldTransaction);
642-
}).toThrow('transaction with no instructions cannot be durable nonce transaction');
647+
}).toThrow(new SolanaError(SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS));
643648
});
644649

645650
it('throws for a transaction with one instruction which is not advance nonce', () => {
@@ -660,7 +665,11 @@ describe('fromVersionedTransactionWithDurableNonce', () => {
660665

661666
expect(() => {
662667
fromVersionedTransactionWithDurableNonce(oldTransaction);
663-
}).toThrow('transaction first instruction is not advance nonce account instruction');
668+
}).toThrow(
669+
new SolanaError(
670+
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE,
671+
),
672+
);
664673
});
665674

666675
it('converts a transaction with one instruction which is advance nonce', () => {
@@ -871,7 +880,7 @@ describe('fromVersionedTransactionWithDurableNonce', () => {
871880

872881
expect(() => {
873882
fromVersionedTransactionWithDurableNonce(oldTransaction);
874-
}).toThrow('transaction with no instructions cannot be durable nonce transaction');
883+
}).toThrow(new SolanaError(SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS));
875884
});
876885

877886
it('throws for a transaction with one instruction which is not advance nonce', () => {
@@ -892,7 +901,11 @@ describe('fromVersionedTransactionWithDurableNonce', () => {
892901

893902
expect(() => {
894903
fromVersionedTransactionWithDurableNonce(oldTransaction);
895-
}).toThrow('transaction first instruction is not advance nonce account instruction');
904+
}).toThrow(
905+
new SolanaError(
906+
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE,
907+
),
908+
);
896909
});
897910

898911
it('converts a transaction with one instruction which is advance nonce', () => {

packages/compat/src/transaction.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
import { type Address, assertIsAddress } from '@solana/addresses';
2+
import {
3+
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE,
4+
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS,
5+
SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS,
6+
SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER,
7+
SolanaError,
8+
} from '@solana/errors';
29
import { pipe } from '@solana/functional';
310
import type { IAccountMeta, IInstruction } from '@solana/instructions';
411
import { AccountRole } from '@solana/instructions';
@@ -33,8 +40,9 @@ function convertAccount(
3340
): IAccountMeta {
3441
const accountPublicKey = accountKeys.get(accountIndex);
3542
if (!accountPublicKey) {
36-
// TODO coded error
37-
throw new Error(`Could not find account address at index ${accountIndex}`);
43+
throw new SolanaError(SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS, {
44+
index: accountIndex,
45+
});
3846
}
3947
const isSigner = message.isAccountSigner(accountIndex);
4048
const isWritable = message.isAccountWritable(accountIndex);
@@ -60,8 +68,9 @@ function convertInstruction(
6068
): IInstruction {
6169
const programAddressPublicKey = accountKeys.get(instruction.programIdIndex);
6270
if (!programAddressPublicKey) {
63-
// TODO coded error
64-
throw new Error(`Could not find program address at index ${instruction.programIdIndex}`);
71+
throw new SolanaError(SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS, {
72+
index: instruction.programIdIndex,
73+
});
6574
}
6675

6776
const accounts = instruction.accountKeyIndexes.map(accountIndex =>
@@ -99,15 +108,16 @@ export function fromVersionedTransactionWithBlockhash(
99108
// - will need to convert account instructions to `IAccountLookupMeta` when appropriate
100109
if (transaction.message.addressTableLookups.length > 0) {
101110
// TODO coded error
111+
// This should probably not be a `SolanaError`, since we need to add
112+
// this functionality.
102113
throw new Error('Cannot convert transaction with addressTableLookups');
103114
}
104115

105116
const accountKeys = transaction.message.getAccountKeys();
106117

107118
// Fee payer is first account
108119
const feePayer = accountKeys.staticAccountKeys[0];
109-
// TODO: coded error
110-
if (!feePayer) throw new Error('No fee payer set in VersionedTransaction');
120+
if (!feePayer) throw new SolanaError(SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER);
111121

112122
const blockhashLifetime = {
113123
blockhash: transaction.message.recentBlockhash as Blockhash,
@@ -140,29 +150,28 @@ export function fromVersionedTransactionWithDurableNonce(
140150
// - will need to convert account instructions to `IAccountLookupMeta` when appropriate
141151
if (transaction.message.addressTableLookups.length > 0) {
142152
// TODO coded error
153+
// This should probably not be a `SolanaError`, since we need to add
154+
// this functionality.
143155
throw new Error('Cannot convert transaction with addressTableLookups');
144156
}
145157

146158
const accountKeys = transaction.message.getAccountKeys();
147159

148160
// Fee payer is first account
149161
const feePayer = accountKeys.staticAccountKeys[0];
150-
// TODO: coded error
151-
if (!feePayer) throw new Error('No fee payer set in VersionedTransaction');
162+
if (!feePayer) throw new SolanaError(SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER);
152163

153164
const instructions = transaction.message.compiledInstructions.map(instruction =>
154165
convertInstruction(transaction.message, accountKeys, instruction),
155166
);
156167

157168
// Check first instruction is durable nonce + extract params
158169
if (instructions.length === 0) {
159-
// TODO: coded error
160-
throw new Error('transaction with no instructions cannot be durable nonce transaction');
170+
throw new SolanaError(SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS);
161171
}
162172

163173
if (!isAdvanceNonceAccountInstruction(instructions[0])) {
164-
// TODO: coded error
165-
throw new Error('transaction first instruction is not advance nonce account instruction');
174+
throw new SolanaError(SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE);
166175
}
167176

168177
// We know these accounts are defined because we checked `isAdvanceNonceAccountInstruction`

packages/errors/src/codes.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ export const SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_A
119119
export const SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_FEE_PAYER_MISSING = 5663009 as const;
120120
export const SOLANA_ERROR__TRANSACTION_MISSING_SIGNATURES = 5663010 as const;
121121
export const SOLANA_ERROR__TRANSACTION_SIGNATURE_NOT_COMPUTABLE = 5663011 as const;
122+
export const SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS = 5663012 as const;
123+
export const SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER = 5663013 as const;
124+
export const SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS = 5663014 as const;
125+
export const SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE = 5663015 as const;
122126
// Reserve error codes starting with [7050000-7050999] for the Rust enum `TransactionError`
123127
export const SOLANA_ERROR__TRANSACTION_ERROR_UNKNOWN = 7050000 as const;
124128
export const SOLANA_ERROR__TRANSACTION_ERROR_ACCOUNT_IN_USE = 7050001 as const;
@@ -314,12 +318,16 @@ export type SolanaErrorCode =
314318
| typeof SOLANA_ERROR__TRANSACTION_ERROR_RESANITIZATION_NEEDED
315319
| typeof SOLANA_ERROR__TRANSACTION_ERROR_PROGRAM_EXECUTION_TEMPORARILY_RESTRICTED
316320
| typeof SOLANA_ERROR__TRANSACTION_ERROR_UNBALANCED_TRANSACTION
317-
| typeof SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_CANNOT_PAY_FEES
318-
| typeof SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE
319321
| typeof SOLANA_ERROR__TRANSACTION_EXPECTED_BLOCKHASH_LIFETIME
320322
| typeof SOLANA_ERROR__TRANSACTION_EXPECTED_NONCE_LIFETIME
321-
| typeof SOLANA_ERROR__TRANSACTION_VERSION_NUMBER_OUT_OF_RANGE
322323
| typeof SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_CONTENTS_MISSING
323324
| typeof SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_INDEX_OUT_OF_RANGE
325+
| typeof SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_FEE_PAYER_MISSING
324326
| typeof SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND
325-
| typeof SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_FEE_PAYER_MISSING;
327+
| typeof SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE
328+
| typeof SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS
329+
| typeof SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_CANNOT_PAY_FEES
330+
| typeof SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE
331+
| typeof SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS
332+
| typeof SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER
333+
| typeof SOLANA_ERROR__TRANSACTION_VERSION_NUMBER_OUT_OF_RANGE;

packages/errors/src/context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ import {
9090
SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND,
9191
SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_CANNOT_PAY_FEES,
9292
SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE,
93+
SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS,
9394
SOLANA_ERROR__TRANSACTION_MISSING_SIGNATURES,
9495
SOLANA_ERROR__TRANSACTION_VERSION_NUMBER_OUT_OF_RANGE,
9596
SolanaErrorCode,
@@ -317,6 +318,9 @@ export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined<
317318
[SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE]: {
318319
programAddress: string;
319320
};
321+
[SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS]: {
322+
index: number;
323+
};
320324
[SOLANA_ERROR__TRANSACTION_MISSING_SIGNATURES]: {
321325
addresses: string[];
322326
};

packages/errors/src/messages.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,12 @@ import {
142142
SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_INDEX_OUT_OF_RANGE,
143143
SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_FEE_PAYER_MISSING,
144144
SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND,
145+
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE,
146+
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS,
145147
SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_CANNOT_PAY_FEES,
146148
SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE,
149+
SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS,
150+
SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER,
147151
SOLANA_ERROR__TRANSACTION_MISSING_SIGNATURES,
148152
SOLANA_ERROR__TRANSACTION_SIGNATURE_NOT_COMPUTABLE,
149153
SOLANA_ERROR__TRANSACTION_VERSION_NUMBER_OUT_OF_RANGE,
@@ -377,12 +381,18 @@ export const SolanaErrorMessages: Readonly<{
377381
[SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_FEE_PAYER_MISSING]: 'No fee payer set in CompiledTransaction',
378382
[SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND]:
379383
'Could not find program address at index $index',
384+
[SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE]:
385+
'Transaction first instruction is not advance nonce account instruction.',
386+
[SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS]:
387+
'Transaction with no instructions cannot be durable nonce transaction.',
380388
[SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_CANNOT_PAY_FEES]:
381389
'This transaction includes an address (`$programAddress`) which is both ' +
382390
'invoked and set as the fee payer. Program addresses may not pay fees',
383391
[SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE]:
384392
'This transaction includes an address (`$programAddress`) which is both invoked and ' +
385393
'marked writable. Program addresses may not be writable',
394+
[SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS]: 'Transaction is missing an address at index: $index.',
395+
[SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER]: 'Transaction is missing a fee payer.',
386396
[SOLANA_ERROR__TRANSACTION_MISSING_SIGNATURES]: 'Transaction is missing signatures for addresses: $addresses.',
387397
[SOLANA_ERROR__TRANSACTION_SIGNATURE_NOT_COMPUTABLE]:
388398
"Could not determine this transaction's signature. Make sure that the transaction has " +

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)