Skip to content

Commit b6e4837

Browse files
committed
Refactor FullySignedTransaction helpers
1 parent 0128277 commit b6e4837

File tree

8 files changed

+96
-21
lines changed

8 files changed

+96
-21
lines changed

.changeset/fresh-ghosts-sniff.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@solana/transactions': major
3+
---
4+
5+
Renames `assertTransactionIsFullySigned` to `assertIsFullySignedTransaction` and adds `isFullySignedTransaction` helper

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ const signedTransaction = await signTransaction([], transactionMessage);
11381138

11391139
// Asserts the transaction is a `FullySignedTransaction`
11401140
// Throws an error if any signatures are missing!
1141-
assertTransactionIsFullySigned(signedTransaction);
1141+
assertIsFullySignedTransaction(signedTransaction);
11421142

11431143
await sendAndConfirmTransaction(signedTransaction);
11441144
```

packages/errors/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@ import {
6060
SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING,
6161
isSolanaError,
6262
} from '@solana/errors';
63-
import { assertTransactionIsFullySigned, getSignatureFromTransaction } from '@solana/transactions';
63+
import { assertIsFullySignedTransaction, getSignatureFromTransaction } from '@solana/transactions';
6464

6565
try {
6666
const transactionSignature = getSignatureFromTransaction(tx);
67-
assertTransactionIsFullySigned(tx);
67+
assertIsFullySignedTransaction(tx);
6868
/* ... */
6969
} catch (e) {
7070
if (isSolanaError(e, SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING)) {

packages/errors/src/error.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ import { getErrorMessage } from './message-formatter';
1818
* SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING,
1919
* isSolanaError,
2020
* } from '@solana/errors';
21-
* import { assertTransactionIsFullySigned, getSignatureFromTransaction } from '@solana/transactions';
21+
* import { assertIsFullySignedTransaction, getSignatureFromTransaction } from '@solana/transactions';
2222
*
2323
* try {
2424
* const transactionSignature = getSignatureFromTransaction(tx);
25-
* assertTransactionIsFullySigned(tx);
25+
* assertIsFullySignedTransaction(tx);
2626
* /* ... *\/
2727
* } catch (e) {
2828
* if (isSolanaError(e, SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING)) {

packages/signers/src/sign-transaction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SOLANA_ERROR__SIGNER__TRANSACTION_SENDING_SIGNER_MISSING, SolanaError }
22
import { SignatureBytes } from '@solana/keys';
33
import { CompilableTransactionMessage } from '@solana/transaction-messages';
44
import {
5-
assertTransactionIsFullySigned,
5+
assertIsFullySignedTransaction,
66
compileTransaction,
77
FullySignedTransaction,
88
Transaction,
@@ -118,7 +118,7 @@ export async function signTransactionMessageWithSigners<
118118
config?: TransactionPartialSignerConfig,
119119
): Promise<FullySignedTransaction & TransactionFromCompilableTransactionMessage<TTransactionMessage>> {
120120
const signedTransaction = await partiallySignTransactionMessageWithSigners(transactionMessage, config);
121-
assertTransactionIsFullySigned(signedTransaction);
121+
assertIsFullySignedTransaction(signedTransaction);
122122
return signedTransaction;
123123
}
124124

packages/transactions/src/__tests__/signatures-test.ts

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import {
1111
import { SignatureBytes, signBytes } from '@solana/keys';
1212

1313
import {
14-
assertTransactionIsFullySigned,
14+
assertIsFullySignedTransaction,
1515
getSignatureFromTransaction,
16+
isFullySignedTransaction,
1617
partiallySignTransaction,
1718
signTransaction,
1819
} from '../signatures';
@@ -398,7 +399,47 @@ describe('signTransaction', () => {
398399
});
399400
});
400401

401-
describe('assertTransactionIsFullySigned', () => {
402+
describe('isFullySignedTransaction', () => {
403+
const mockPublicKeyAddressA = 'A' as Address;
404+
const mockSignatureA = new Uint8Array(0) as SignatureBytes;
405+
const mockPublicKeyAddressB = 'B' as Address;
406+
const mockSignatureB = new Uint8Array(1) as SignatureBytes;
407+
408+
it('returns false if the transaction has missing signatures', () => {
409+
const signatures: SignaturesMap = {};
410+
signatures[mockPublicKeyAddressA] = null;
411+
const transaction: Transaction = {
412+
messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes,
413+
signatures,
414+
};
415+
416+
expect(isFullySignedTransaction(transaction)).toBe(false);
417+
});
418+
419+
it('returns true if the transaction is signed by all its signers', () => {
420+
const signatures: SignaturesMap = {};
421+
signatures[mockPublicKeyAddressA] = mockSignatureA;
422+
signatures[mockPublicKeyAddressB] = mockSignatureB;
423+
const transaction: Transaction = {
424+
messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes,
425+
signatures,
426+
};
427+
428+
expect(isFullySignedTransaction(transaction)).toBe(true);
429+
});
430+
431+
it('return true if the transaction has no signatures', () => {
432+
const signatures: SignaturesMap = {};
433+
const transaction: Transaction = {
434+
messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes,
435+
signatures,
436+
};
437+
438+
expect(isFullySignedTransaction(transaction)).toBe(true);
439+
});
440+
});
441+
442+
describe('assertIsFullySignedTransaction', () => {
402443
const mockPublicKeyAddressA = 'A' as Address;
403444
const mockSignatureA = new Uint8Array(0) as SignatureBytes;
404445
const mockPublicKeyAddressB = 'B' as Address;
@@ -412,7 +453,7 @@ describe('assertTransactionIsFullySigned', () => {
412453
signatures,
413454
};
414455

415-
expect(() => assertTransactionIsFullySigned(transaction)).toThrow(
456+
expect(() => assertIsFullySignedTransaction(transaction)).toThrow(
416457
new SolanaError(SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, {
417458
addresses: [mockPublicKeyAddressA],
418459
}),
@@ -428,7 +469,7 @@ describe('assertTransactionIsFullySigned', () => {
428469
signatures,
429470
};
430471

431-
expect(() => assertTransactionIsFullySigned(transaction)).toThrow(
472+
expect(() => assertIsFullySignedTransaction(transaction)).toThrow(
432473
new SolanaError(SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, {
433474
addresses: [mockPublicKeyAddressA, mockPublicKeyAddressB],
434475
}),
@@ -443,7 +484,7 @@ describe('assertTransactionIsFullySigned', () => {
443484
signatures,
444485
};
445486

446-
expect(() => assertTransactionIsFullySigned(transaction)).not.toThrow();
487+
expect(() => assertIsFullySignedTransaction(transaction)).not.toThrow();
447488
});
448489

449490
it('does not throw if the transaction is signed by all its signers', () => {
@@ -455,7 +496,7 @@ describe('assertTransactionIsFullySigned', () => {
455496
signatures,
456497
};
457498

458-
expect(() => assertTransactionIsFullySigned(transaction)).not.toThrow();
499+
expect(() => assertIsFullySignedTransaction(transaction)).not.toThrow();
459500
});
460501

461502
it('does not throw if the transaction has no signatures', () => {
@@ -464,6 +505,6 @@ describe('assertTransactionIsFullySigned', () => {
464505
messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes,
465506
signatures,
466507
};
467-
expect(() => assertTransactionIsFullySigned(transaction)).not.toThrow();
508+
expect(() => assertIsFullySignedTransaction(transaction)).not.toThrow();
468509
});
469510
});

packages/transactions/src/__typetests__/signatures-typetests.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
import { Signature } from '@solana/keys';
33

44
import {
5-
assertTransactionIsFullySigned,
5+
assertIsFullySignedTransaction,
66
FullySignedTransaction,
77
getSignatureFromTransaction,
8+
isFullySignedTransaction,
89
partiallySignTransaction,
910
signTransaction,
1011
} from '..';
@@ -28,9 +29,17 @@ import { Transaction } from '../transaction';
2829
signTransaction([], transaction) satisfies Promise<FullySignedTransaction & Transaction & { some: 1 }>;
2930
}
3031

31-
// assertTransactionIsFullySigned
32+
// isFullySignedTransaction
3233
{
3334
const transaction = null as unknown as Transaction & { some: 1 };
34-
assertTransactionIsFullySigned(transaction);
35+
if (isFullySignedTransaction(transaction)) {
36+
transaction satisfies FullySignedTransaction & Transaction & { some: 1 };
37+
}
38+
}
39+
40+
// assertIsFullySignedTransaction
41+
{
42+
const transaction = null as unknown as Transaction & { some: 1 };
43+
assertIsFullySignedTransaction(transaction);
3544
transaction satisfies FullySignedTransaction & Transaction & { some: 1 };
3645
}

packages/transactions/src/signatures.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,25 +150,44 @@ export async function signTransaction<TTransaction extends Transaction>(
150150
transaction: TTransaction,
151151
): Promise<FullySignedTransaction & TTransaction> {
152152
const out = await partiallySignTransaction(keyPairs, transaction);
153-
assertTransactionIsFullySigned(out);
153+
assertIsFullySignedTransaction(out);
154154
Object.freeze(out);
155155
return out;
156156
}
157157

158+
/**
159+
* Checks whether a given {@link Transaction} is fully signed.
160+
*
161+
* @example
162+
* ```ts
163+
* import { isFullySignedTransaction } from '@solana/transactions';
164+
*
165+
* const transaction = getTransactionDecoder().decode(transactionBytes);
166+
* if (isFullySignedTransaction(transaction)) {
167+
* // At this point we know that the transaction is signed and can be sent to the network.
168+
* }
169+
* ```
170+
*/
171+
export function isFullySignedTransaction<TTransaction extends Transaction>(
172+
transaction: TTransaction,
173+
): transaction is FullySignedTransaction & TTransaction {
174+
return Object.entries(transaction.signatures).every(([_, signatureBytes]) => !!signatureBytes);
175+
}
176+
158177
/**
159178
* From time to time you might acquire a {@link Transaction}, that you expect to be fully signed,
160179
* from an untrusted network API or user input. Use this function to assert that such a transaction
161180
* is fully signed.
162181
*
163182
* @example
164183
* ```ts
165-
* import { assertTransactionIsFullySigned } from '@solana/transactions';
184+
* import { assertIsFullySignedTransaction } from '@solana/transactions';
166185
*
167186
* const transaction = getTransactionDecoder().decode(transactionBytes);
168187
* try {
169188
* // If this type assertion function doesn't throw, then Typescript will upcast `transaction`
170189
* // to `FullySignedTransaction`.
171-
* assertTransactionIsFullySigned(transaction);
190+
* assertIsFullySignedTransaction(transaction);
172191
* // At this point we know that the transaction is signed and can be sent to the network.
173192
* await sendAndConfirmTransaction(transaction, { commitment: 'confirmed' });
174193
* } catch(e) {
@@ -177,8 +196,9 @@ export async function signTransaction<TTransaction extends Transaction>(
177196
* }
178197
* throw;
179198
* }
199+
* ```
180200
*/
181-
export function assertTransactionIsFullySigned<TTransaction extends Transaction>(
201+
export function assertIsFullySignedTransaction<TTransaction extends Transaction>(
182202
transaction: TTransaction,
183203
): asserts transaction is FullySignedTransaction & TTransaction {
184204
const missingSigs: Address[] = [];

0 commit comments

Comments
 (0)