Skip to content

Commit b26b471

Browse files
committed
Make FullySignedTransaction composable
1 parent 222886c commit b26b471

13 files changed

+59
-56
lines changed

.changeset/sour-beans-hear.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@solana/transactions': minor
3+
'@solana/signers': minor
4+
'@solana/kit': minor
5+
---
6+
7+
The `FullySignedTransaction` no longer extends the `Transaction` type so it can be composed with other flags that also narrow transaction types.

docs/content/docs/getting-started/send-transaction.mdx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ The most common way to send transactions is via the `sendTransaction` RPC method
1212
One way to tackle this would be to encode the transaction ourselves using `getBase64EncodedWireTransaction` and pass it to the `sendTransaction` RPC method like so.
1313

1414
```ts twoslash
15-
import { FullySignedTransaction, TransactionWithBlockhashLifetime, Rpc, SolanaRpcApi } from "@solana/kit";
16-
const signedTransaction = null as unknown as FullySignedTransaction & TransactionWithBlockhashLifetime;
15+
import { Transaction, FullySignedTransaction, TransactionWithBlockhashLifetime, Rpc, SolanaRpcApi } from "@solana/kit";
16+
const signedTransaction = null as unknown as Transaction & FullySignedTransaction & TransactionWithBlockhashLifetime;
1717
const rpc = null as unknown as Rpc<SolanaRpcApi>;
1818
// ---cut-before---
1919
import { getBase64EncodedWireTransaction } from "@solana/kit";
@@ -25,8 +25,8 @@ await rpc.sendTransaction(encodedTransaction, { preflightCommitment: "confirmed"
2525
However, Kit offers a helper function that does that for us whilst providing some sensible default values. This function is called `sendTransactionWithoutConfirmingFactory` and, given an RPC object, it returns a function that sends transactions without waiting for confirmation.
2626

2727
```ts twoslash
28-
import { FullySignedTransaction, TransactionWithBlockhashLifetime, Rpc, SolanaRpcApi } from "@solana/kit";
29-
const signedTransaction = null as unknown as FullySignedTransaction & TransactionWithBlockhashLifetime;
28+
import { Transaction, FullySignedTransaction, TransactionWithBlockhashLifetime, Rpc, SolanaRpcApi } from "@solana/kit";
29+
const signedTransaction = null as unknown as Transaction & FullySignedTransaction & TransactionWithBlockhashLifetime;
3030
const rpc = null as unknown as Rpc<SolanaRpcApi>;
3131
// ---cut-before---
3232
import { sendTransactionWithoutConfirmingFactory } from "@solana/kit";
@@ -50,14 +50,15 @@ Both accept RPC and RPC Subscriptions objects and return a function that sends a
5050

5151
```ts twoslash
5252
import {
53+
Transaction,
5354
FullySignedTransaction,
5455
TransactionWithBlockhashLifetime,
5556
Rpc,
5657
SolanaRpcApi,
5758
RpcSubscriptions,
5859
SolanaRpcSubscriptionsApi,
5960
} from "@solana/kit";
60-
const signedTransaction = null as unknown as FullySignedTransaction & TransactionWithBlockhashLifetime;
61+
const signedTransaction = null as unknown as Transaction & FullySignedTransaction & TransactionWithBlockhashLifetime;
6162
const rpc = null as unknown as Rpc<SolanaRpcApi>;
6263
const rpcSubscriptions = null as unknown as RpcSubscriptions<SolanaRpcSubscriptionsApi>;
6364
// ---cut-before---
@@ -77,14 +78,15 @@ As such, Kit decouples these two distinct concepts by offering a `getSignatureFr
7778

7879
```ts twoslash
7980
import {
81+
Transaction,
8082
FullySignedTransaction,
8183
TransactionWithBlockhashLifetime,
8284
Rpc,
8385
SolanaRpcApi,
8486
RpcSubscriptions,
8587
SolanaRpcSubscriptionsApi,
8688
} from "@solana/kit";
87-
const signedTransaction = null as unknown as FullySignedTransaction & TransactionWithBlockhashLifetime;
89+
const signedTransaction = null as unknown as Transaction & FullySignedTransaction & TransactionWithBlockhashLifetime;
8890
const rpc = null as unknown as Rpc<SolanaRpcApi>;
8991
const rpcSubscriptions = null as unknown as RpcSubscriptions<SolanaRpcSubscriptionsApi>;
9092
// ---cut-before---

docs/content/docs/upgrade-guide.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,10 +473,11 @@ import {
473473
createSolanaRpcSubscriptions,
474474
FullySignedTransaction,
475475
TransactionWithBlockhashLifetime,
476+
Transaction,
476477
} from "@solana/kit";
477478
const rpc = createSolanaRpc("https://api.devnet.solana.com");
478479
const rpcSubscriptions = createSolanaRpcSubscriptions("wss://api.devnet.solana.com");
479-
const signedTransaction = null as unknown as FullySignedTransaction & TransactionWithBlockhashLifetime;
480+
const signedTransaction = null as unknown as Transaction & FullySignedTransaction & TransactionWithBlockhashLifetime;
480481
// ---cut-end---
481482

482483
// Create a send and confirm function from your RPC and RPC Subscriptions objects.

packages/kit/src/__tests__/send-transaction-internal-test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
Base64EncodedWireTransaction,
66
FullySignedTransaction,
77
getBase64EncodedWireTransaction,
8+
Transaction,
89
TransactionWithBlockhashLifetime,
910
TransactionWithDurableNonceLifetime,
1011
} from '@solana/transactions';
@@ -21,7 +22,7 @@ const FOREVER_PROMISE = new Promise(() => {
2122
});
2223

2324
describe('sendAndConfirmTransaction', () => {
24-
const MOCK_TRANSACTION = {} as FullySignedTransaction & TransactionWithBlockhashLifetime;
25+
const MOCK_TRANSACTION = {} as FullySignedTransaction & Transaction & TransactionWithBlockhashLifetime;
2526
let confirmRecentTransaction: jest.Mock;
2627
let createPendingRequest: jest.Mock;
2728
let rpc: Rpc<SendTransactionApi>;
@@ -176,6 +177,7 @@ describe('sendAndConfirmTransaction', () => {
176177

177178
describe('sendAndConfirmDurableNonceTransaction', () => {
178179
const MOCK_DURABLE_NONCE_TRANSACTION = {} as unknown as FullySignedTransaction &
180+
Transaction &
179181
TransactionWithDurableNonceLifetime;
180182
let confirmDurableNonceTransaction: jest.Mock;
181183
let createPendingRequest: jest.Mock;

packages/kit/src/send-and-confirm-durable-nonce-transaction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import {
55
createRecentSignatureConfirmationPromiseFactory,
66
waitForDurableNonceTransactionConfirmation,
77
} from '@solana/transaction-confirmation';
8-
import { FullySignedTransaction, TransactionWithDurableNonceLifetime } from '@solana/transactions';
8+
import { FullySignedTransaction, Transaction, TransactionWithDurableNonceLifetime } from '@solana/transactions';
99

1010
import { sendAndConfirmDurableNonceTransaction_INTERNAL_ONLY_DO_NOT_EXPORT } from './send-transaction-internal';
1111

1212
type SendAndConfirmDurableNonceTransactionFunction = (
13-
transaction: FullySignedTransaction & TransactionWithDurableNonceLifetime,
13+
transaction: FullySignedTransaction & Transaction & TransactionWithDurableNonceLifetime,
1414
config: Omit<
1515
Parameters<typeof sendAndConfirmDurableNonceTransaction_INTERNAL_ONLY_DO_NOT_EXPORT>[0],
1616
'confirmDurableNonceTransaction' | 'rpc' | 'transaction'

packages/kit/src/send-and-confirm-transaction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import {
66
TransactionWithLastValidBlockHeight,
77
waitForRecentTransactionConfirmation,
88
} from '@solana/transaction-confirmation';
9-
import { FullySignedTransaction } from '@solana/transactions';
9+
import { FullySignedTransaction, Transaction } from '@solana/transactions';
1010

1111
import { sendAndConfirmTransactionWithBlockhashLifetime_INTERNAL_ONLY_DO_NOT_EXPORT } from './send-transaction-internal';
1212

1313
type SendAndConfirmTransactionWithBlockhashLifetimeFunction = (
14-
transaction: FullySignedTransaction & TransactionWithLastValidBlockHeight,
14+
transaction: FullySignedTransaction & Transaction & TransactionWithLastValidBlockHeight,
1515
config: Omit<
1616
Parameters<typeof sendAndConfirmTransactionWithBlockhashLifetime_INTERNAL_ONLY_DO_NOT_EXPORT>[0],
1717
'confirmRecentTransaction' | 'rpc' | 'transaction'

packages/kit/src/send-transaction-internal.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import {
1010
FullySignedTransaction,
1111
getBase64EncodedWireTransaction,
12+
Transaction,
1213
TransactionWithDurableNonceLifetime,
1314
} from '@solana/transactions';
1415

@@ -21,7 +22,7 @@ interface SendAndConfirmDurableNonceTransactionConfig
2122
'getNonceInvalidationPromise' | 'getRecentSignatureConfirmationPromise'
2223
>,
2324
) => Promise<void>;
24-
transaction: FullySignedTransaction & TransactionWithDurableNonceLifetime;
25+
transaction: FullySignedTransaction & Transaction & TransactionWithDurableNonceLifetime;
2526
}
2627

2728
interface SendAndConfirmTransactionWithBlockhashLifetimeConfig
@@ -33,14 +34,14 @@ interface SendAndConfirmTransactionWithBlockhashLifetimeConfig
3334
'getBlockHeightExceedencePromise' | 'getRecentSignatureConfirmationPromise'
3435
>,
3536
) => Promise<void>;
36-
transaction: FullySignedTransaction & TransactionWithLastValidBlockHeight;
37+
transaction: FullySignedTransaction & Transaction & TransactionWithLastValidBlockHeight;
3738
}
3839

3940
interface SendTransactionBaseConfig extends SendTransactionConfigWithoutEncoding {
4041
abortSignal?: AbortSignal;
4142
commitment: Commitment;
4243
rpc: Rpc<SendTransactionApi>;
43-
transaction: FullySignedTransaction;
44+
transaction: FullySignedTransaction & Transaction;
4445
}
4546

4647
type SendTransactionConfigWithoutEncoding = Omit<

packages/kit/src/send-transaction-without-confirming.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { Rpc, SendTransactionApi } from '@solana/rpc';
2-
import { FullySignedTransaction } from '@solana/transactions';
2+
import { FullySignedTransaction, Transaction } from '@solana/transactions';
33

44
import { sendTransaction_INTERNAL_ONLY_DO_NOT_EXPORT } from './send-transaction-internal';
55

66
type SendTransactionWithoutConfirmingFunction = (
7-
transaction: FullySignedTransaction,
7+
transaction: FullySignedTransaction & Transaction,
88
config: Omit<Parameters<typeof sendTransaction_INTERNAL_ONLY_DO_NOT_EXPORT>[0], 'rpc' | 'transaction'>,
99
) => Promise<void>;
1010

packages/signers/src/__tests__/sign-transaction-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ describe('signTransactionMessageWithSigners', () => {
441441
});
442442

443443
// And the transaction is fully signed.
444-
signedTransaction satisfies FullySignedTransaction;
444+
signedTransaction satisfies FullySignedTransaction & Transaction;
445445

446446
// And the signers were called with the expected parameters.
447447
expect(signerA.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], mockOptions);

packages/signers/src/__typetests__/sign-transaction-typetest.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type CompilableTransactionMessageWithSigners = CompilableTransactionMessage & Tr
5454
const transactionMessage = null as unknown as CompilableTransactionMessageWithSigners &
5555
TransactionMessageWithBlockhashLifetime;
5656
signTransactionMessageWithSigners(transactionMessage) satisfies Promise<
57-
Readonly<FullySignedTransaction & TransactionWithBlockhashLifetime>
57+
Readonly<FullySignedTransaction & Transaction & TransactionWithBlockhashLifetime>
5858
>;
5959
}
6060

@@ -63,15 +63,15 @@ type CompilableTransactionMessageWithSigners = CompilableTransactionMessage & Tr
6363
const transactionMessage = null as unknown as CompilableTransactionMessageWithSigners &
6464
TransactionMessageWithDurableNonceLifetime;
6565
signTransactionMessageWithSigners(transactionMessage) satisfies Promise<
66-
Readonly<FullySignedTransaction & TransactionWithDurableNonceLifetime>
66+
Readonly<FullySignedTransaction & Transaction & TransactionWithDurableNonceLifetime>
6767
>;
6868
}
6969

7070
{
7171
// [signTransactionMessageWithSigners]: returns a fully signed transaction with an unknown lifetime
7272
const transactionMessage = null as unknown as CompilableTransactionMessageWithSigners;
7373
signTransactionMessageWithSigners(transactionMessage) satisfies Promise<
74-
Readonly<FullySignedTransaction & TransactionWithLifetime>
74+
Readonly<FullySignedTransaction & Transaction & TransactionWithLifetime>
7575
>;
7676
}
7777

packages/signers/src/sign-transaction.ts

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -72,32 +72,28 @@ type CompilableTransactionMessageWithSigners = CompilableTransactionMessage & Tr
7272
* @see {@link signAndSendTransactionMessageWithSigners}
7373
*/
7474
export async function partiallySignTransactionMessageWithSigners<
75-
TTransactionMessage extends CompilableTransactionMessageWithSigners &
76-
TransactionMessageWithBlockhashLifetime = CompilableTransactionMessageWithSigners &
77-
TransactionMessageWithBlockhashLifetime,
75+
TTransactionMessage extends CompilableTransactionMessageWithSigners & TransactionMessageWithBlockhashLifetime,
7876
>(
7977
transactionMessage: TTransactionMessage,
8078
config?: TransactionPartialSignerConfig,
8179
): Promise<Transaction & TransactionWithBlockhashLifetime>;
8280

8381
export async function partiallySignTransactionMessageWithSigners<
84-
TTransactionMessage extends CompilableTransactionMessageWithSigners &
85-
TransactionMessageWithDurableNonceLifetime = CompilableTransactionMessageWithSigners &
86-
TransactionMessageWithDurableNonceLifetime,
82+
TTransactionMessage extends CompilableTransactionMessageWithSigners & TransactionMessageWithDurableNonceLifetime,
8783
>(
8884
transactionMessage: TTransactionMessage,
8985
config?: TransactionPartialSignerConfig,
9086
): Promise<Readonly<Transaction & TransactionWithDurableNonceLifetime>>;
9187

9288
export async function partiallySignTransactionMessageWithSigners<
93-
TTransactionMessage extends CompilableTransactionMessageWithSigners = CompilableTransactionMessageWithSigners,
89+
TTransactionMessage extends CompilableTransactionMessageWithSigners,
9490
>(
9591
transactionMessage: TTransactionMessage,
9692
config?: TransactionPartialSignerConfig,
9793
): Promise<Readonly<Transaction & TransactionWithLifetime>>;
9894

9995
export async function partiallySignTransactionMessageWithSigners<
100-
TTransactionMessage extends CompilableTransactionMessageWithSigners = CompilableTransactionMessageWithSigners,
96+
TTransactionMessage extends CompilableTransactionMessageWithSigners,
10197
>(
10298
transactionMessage: TTransactionMessage,
10399
config?: TransactionPartialSignerConfig,
@@ -142,36 +138,32 @@ export async function partiallySignTransactionMessageWithSigners<
142138
* @see {@link signAndSendTransactionMessageWithSigners}
143139
*/
144140
export async function signTransactionMessageWithSigners<
145-
TTransactionMessage extends CompilableTransactionMessageWithSigners &
146-
TransactionMessageWithBlockhashLifetime = CompilableTransactionMessageWithSigners &
147-
TransactionMessageWithBlockhashLifetime,
141+
TTransactionMessage extends CompilableTransactionMessageWithSigners & TransactionMessageWithBlockhashLifetime,
148142
>(
149143
transactionMessage: TTransactionMessage,
150144
config?: TransactionPartialSignerConfig,
151-
): Promise<Readonly<FullySignedTransaction & TransactionWithBlockhashLifetime>>;
145+
): Promise<Readonly<FullySignedTransaction & Transaction & TransactionWithBlockhashLifetime>>;
152146

153147
export async function signTransactionMessageWithSigners<
154-
TTransactionMessage extends CompilableTransactionMessageWithSigners &
155-
TransactionMessageWithDurableNonceLifetime = CompilableTransactionMessageWithSigners &
156-
TransactionMessageWithDurableNonceLifetime,
148+
TTransactionMessage extends CompilableTransactionMessageWithSigners & TransactionMessageWithDurableNonceLifetime,
157149
>(
158150
transactionMessage: TTransactionMessage,
159151
config?: TransactionPartialSignerConfig,
160-
): Promise<Readonly<FullySignedTransaction & TransactionWithDurableNonceLifetime>>;
152+
): Promise<Readonly<FullySignedTransaction & Transaction & TransactionWithDurableNonceLifetime>>;
161153

162154
export async function signTransactionMessageWithSigners<
163-
TTransactionMessage extends CompilableTransactionMessageWithSigners = CompilableTransactionMessageWithSigners,
155+
TTransactionMessage extends CompilableTransactionMessageWithSigners,
164156
>(
165157
transactionMessage: TTransactionMessage,
166158
config?: TransactionPartialSignerConfig,
167-
): Promise<Readonly<FullySignedTransaction & TransactionWithLifetime>>;
159+
): Promise<Readonly<FullySignedTransaction & Transaction & TransactionWithLifetime>>;
168160

169161
export async function signTransactionMessageWithSigners<
170-
TTransactionMessage extends CompilableTransactionMessageWithSigners = CompilableTransactionMessageWithSigners,
162+
TTransactionMessage extends CompilableTransactionMessageWithSigners,
171163
>(
172164
transactionMessage: TTransactionMessage,
173165
config?: TransactionPartialSignerConfig,
174-
): Promise<Readonly<FullySignedTransaction & TransactionWithLifetime>> {
166+
): Promise<Readonly<FullySignedTransaction & Transaction & TransactionWithLifetime>> {
175167
const signedTransaction = await partiallySignTransactionMessageWithSigners(transactionMessage, config);
176168
assertTransactionIsFullySigned(signedTransaction);
177169
return signedTransaction;
@@ -228,7 +220,7 @@ export async function signTransactionMessageWithSigners<
228220
*
229221
*/
230222
export async function signAndSendTransactionMessageWithSigners<
231-
TTransactionMessage extends CompilableTransactionMessageWithSigners = CompilableTransactionMessageWithSigners,
223+
TTransactionMessage extends CompilableTransactionMessageWithSigners,
232224
>(transaction: TTransactionMessage, config?: TransactionSendingSignerConfig): Promise<SignatureBytes> {
233225
assertIsTransactionMessageWithSingleSendingSigner(transaction);
234226

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ import { Transaction } from '../transaction';
2525
// signTransaction
2626
{
2727
const transaction = null as unknown as Transaction & { some: 1 };
28-
signTransaction([], transaction) satisfies Promise<FullySignedTransaction & { some: 1 }>;
28+
signTransaction([], transaction) satisfies Promise<FullySignedTransaction & Transaction & { some: 1 }>;
2929
}
3030

3131
// assertTransactionIsFullySigned
3232
{
3333
const transaction = null as unknown as Transaction & { some: 1 };
3434
assertTransactionIsFullySigned(transaction);
35-
transaction satisfies FullySignedTransaction & { some: 1 };
35+
transaction satisfies FullySignedTransaction & Transaction & { some: 1 };
3636
}

packages/transactions/src/signatures.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ import { Transaction } from './transaction';
1515
* Represents a transaction that is signed by all of its required signers. Being fully signed is a
1616
* prerequisite of functions designed to land transactions on the network.
1717
*/
18-
export interface FullySignedTransaction extends Transaction {
19-
readonly __brand: unique symbol;
20-
}
18+
export type FullySignedTransaction = { readonly _fully_signed: unique symbol };
2119

2220
let base58Decoder: Decoder<string> | undefined;
2321

@@ -71,10 +69,10 @@ function uint8ArraysEqual(arr1: Uint8Array, arr2: Uint8Array) {
7169
* @see {@link signTransaction} if you want to assert that the transaction has all of its required
7270
* signatures after signing.
7371
*/
74-
export async function partiallySignTransaction<T extends Transaction>(
72+
export async function partiallySignTransaction<TTransaction extends Transaction>(
7573
keyPairs: CryptoKeyPair[],
76-
transaction: T,
77-
): Promise<T> {
74+
transaction: TTransaction,
75+
): Promise<TTransaction> {
7876
let newSignatures: Record<Address, SignatureBytes> | undefined;
7977
let unexpectedSigners: Set<Address> | undefined;
8078

@@ -147,10 +145,10 @@ export async function partiallySignTransaction<T extends Transaction>(
147145
* @see {@link partiallySignTransaction} if you want to sign the transaction without asserting that
148146
* the resulting transaction is fully signed.
149147
*/
150-
export async function signTransaction<T extends Transaction>(
148+
export async function signTransaction<TTransaction extends Transaction>(
151149
keyPairs: CryptoKeyPair[],
152-
transaction: T,
153-
): Promise<FullySignedTransaction & T> {
150+
transaction: TTransaction,
151+
): Promise<FullySignedTransaction & TTransaction> {
154152
const out = await partiallySignTransaction(keyPairs, transaction);
155153
assertTransactionIsFullySigned(out);
156154
Object.freeze(out);
@@ -180,9 +178,9 @@ export async function signTransaction<T extends Transaction>(
180178
* throw;
181179
* }
182180
*/
183-
export function assertTransactionIsFullySigned(
184-
transaction: Transaction,
185-
): asserts transaction is FullySignedTransaction {
181+
export function assertTransactionIsFullySigned<TTransaction extends Transaction>(
182+
transaction: TTransaction,
183+
): asserts transaction is FullySignedTransaction & TTransaction {
186184
const missingSigs: Address[] = [];
187185
Object.entries(transaction.signatures).forEach(([address, signatureBytes]) => {
188186
if (!signatureBytes) {

0 commit comments

Comments
 (0)