Skip to content

Commit d3dbeaa

Browse files
authored
Document transaction-level signing functions and update docs to Kit 6.5.0 (anza-xyz#1494)
This PR updates the documentation website to Kit 6.5.0 and adds comprehensive documentation for the new transaction-level signing functions introduced in anza-xyz#1487 (closes anza-xyz#1488). The signers concept page now includes a "Signing with signers" section covering all six signing helper functions — both the transaction message family (`partiallySignTransactionMessageWithSigners`, `signTransactionMessageWithSigners`, `signAndSendTransactionMessageWithSigners`) and the new compiled transaction family (`partiallySignTransactionWithSigners`, `signTransactionWithSigners`, `signAndSendTransactionWithSigners`) — along with `assertContainsResolvableTransactionSendingSigner` and an explanation of composite signer resolution. The transactions concept page adds cross-references linking to the new content.
1 parent 67c1d47 commit d3dbeaa

4 files changed

Lines changed: 608 additions & 378 deletions

File tree

docs/content/docs/concepts/signers.mdx

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,227 @@ const seed = new Uint8Array(await crypto.subtle.digest('SHA-256', message));
313313

314314
const derivedSigner = await createKeyPairSignerFromPrivateKeyBytes(seed);
315315
```
316+
317+
## Signing with signers
318+
319+
Kit provides helper functions that use [`TransactionSigner`](/api/type-aliases/TransactionSigner) objects to sign and optionally send transactions. There are two families of signing functions:
320+
321+
- **Transaction message functions** extract signers from the transaction message — either from the fee payer or from account metas — and handle compilation and signing automatically.
322+
- **Transaction functions** accept an explicit array of signers alongside a compiled [`Transaction`](/api/type-aliases/Transaction), giving you full control over which signers are used.
323+
324+
### Signing transaction messages
325+
326+
The following functions extract [`TransactionSigners`](/api/type-aliases/TransactionSigner) from a transaction message's account metas, compile the message into a [`Transaction`](/api/type-aliases/Transaction), and use those signers to sign it.
327+
328+
#### partiallySignTransactionMessageWithSigners [!toc] [#partially-sign-transaction-message-with-signers]
329+
330+
Signs a transaction message without requiring all signatures to be present. This is useful when you know the message only carries a subset of the required signers and you plan to add more signatures later.
331+
332+
```ts twoslash
333+
import {
334+
TransactionMessage,
335+
TransactionMessageWithFeePayer,
336+
TransactionMessageWithSigners,
337+
} from '@solana/kit';
338+
const transactionMessage = null as unknown as TransactionMessage &
339+
TransactionMessageWithFeePayer &
340+
TransactionMessageWithSigners;
341+
// ---cut-before---
342+
import { partiallySignTransactionMessageWithSigners } from '@solana/kit';
343+
344+
const signedTransaction = await partiallySignTransactionMessageWithSigners(transactionMessage);
345+
```
346+
347+
<Callout type="info">
348+
This function ignores any [`TransactionSendingSigners`](/api/type-aliases/TransactionSendingSigner) in the message since it does not send the transaction. Use [`signAndSendTransactionMessageWithSigners`](#sign-and-send-transaction-message-with-signers) if you need to sign and send in a single step.
349+
</Callout>
350+
351+
#### signTransactionMessageWithSigners [!toc] [#sign-transaction-message-with-signers]
352+
353+
Signs a transaction message and asserts that all required signatures are present. The returned transaction satisfies the [`FullySignedTransaction`](/api/type-aliases/FullySignedTransaction) type.
354+
355+
```ts twoslash
356+
import {
357+
TransactionMessage,
358+
TransactionMessageWithFeePayer,
359+
TransactionMessageWithSigners,
360+
} from '@solana/kit';
361+
const transactionMessage = null as unknown as TransactionMessage &
362+
TransactionMessageWithFeePayer &
363+
TransactionMessageWithSigners;
364+
// ---cut-before---
365+
import { signTransactionMessageWithSigners } from '@solana/kit';
366+
367+
const fullySignedTransaction = await signTransactionMessageWithSigners(transactionMessage);
368+
```
369+
370+
<Callout type="warn">
371+
This function will throw if the transaction message does not carry a [`TransactionSigner`](/api/type-aliases/TransactionSigner) implementation for every required signer. To partially sign a message that you know to carry a strict subset of the required signers, use [`partiallySignTransactionMessageWithSigners`](#partially-sign-transaction-message-with-signers).
372+
</Callout>
373+
374+
#### signAndSendTransactionMessageWithSigners [!toc] [#sign-and-send-transaction-message-with-signers]
375+
376+
Signs a transaction message and sends it to the network via the [`TransactionSendingSigner`](/api/type-aliases/TransactionSendingSigner) found in the message's account metas. Returns the transaction signature as [`SignatureBytes`](/api/type-aliases/SignatureBytes).
377+
378+
```ts twoslash
379+
import {
380+
TransactionMessage,
381+
TransactionMessageWithFeePayer,
382+
TransactionMessageWithSigners,
383+
TransactionMessageWithSingleSendingSigner,
384+
} from '@solana/kit';
385+
const transactionMessage = null as unknown as TransactionMessage &
386+
TransactionMessageWithFeePayer &
387+
TransactionMessageWithSigners &
388+
TransactionMessageWithSingleSendingSigner;
389+
// ---cut-before---
390+
import { signAndSendTransactionMessageWithSigners } from '@solana/kit';
391+
392+
const signature = await signAndSendTransactionMessageWithSigners(transactionMessage);
393+
```
394+
395+
The message must contain exactly one resolvable [`TransactionSendingSigner`](/api/type-aliases/TransactionSendingSigner). You can check this ahead of time using [`isTransactionMessageWithSingleSendingSigner`](/api/functions/isTransactionMessageWithSingleSendingSigner) to provide a fallback strategy:
396+
397+
```ts twoslash
398+
// @noErrors: 2345
399+
import {
400+
Rpc,
401+
RpcSubscriptions,
402+
SolanaRpcApi,
403+
SolanaRpcSubscriptionsApi,
404+
TransactionMessage,
405+
TransactionMessageWithBlockhashLifetime,
406+
TransactionMessageWithFeePayer,
407+
TransactionMessageWithSigners,
408+
} from '@solana/kit';
409+
const transactionMessage = null as unknown as TransactionMessage &
410+
TransactionMessageWithBlockhashLifetime &
411+
TransactionMessageWithFeePayer &
412+
TransactionMessageWithSigners;
413+
const rpc = null as unknown as Rpc<SolanaRpcApi>;
414+
const rpcSubscriptions = null as unknown as RpcSubscriptions<SolanaRpcSubscriptionsApi>;
415+
// ---cut-before---
416+
import {
417+
isTransactionMessageWithSingleSendingSigner,
418+
sendAndConfirmTransactionFactory,
419+
signAndSendTransactionMessageWithSigners,
420+
signTransactionMessageWithSigners,
421+
} from '@solana/kit';
422+
423+
if (isTransactionMessageWithSingleSendingSigner(transactionMessage)) {
424+
// A sending signer is available — sign and send in one step.
425+
const signature = await signAndSendTransactionMessageWithSigners(transactionMessage);
426+
} else {
427+
// No sending signer — sign locally and use sendAndConfirmTransaction.
428+
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
429+
const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
430+
await sendAndConfirm(signedTransaction, { commitment: 'confirmed' });
431+
}
432+
```
433+
434+
### Signing compiled transactions
435+
436+
When you already have a compiled [`Transaction`](/api/type-aliases/Transaction) and an explicit set of signers — for example, when signers are not embedded in the transaction message or when working with an externally provided transaction — you can use the following lower-level functions.
437+
438+
#### partiallySignTransactionWithSigners [!toc] [#partially-sign-transaction-with-signers]
439+
440+
Signs a compiled transaction using the provided [`TransactionModifyingSigners`](/api/type-aliases/TransactionModifyingSigner) and [`TransactionPartialSigners`](/api/type-aliases/TransactionPartialSigner) without requiring all signatures to be present.
441+
442+
```ts twoslash
443+
import {
444+
Transaction,
445+
TransactionWithLifetime,
446+
TransactionPartialSigner,
447+
TransactionModifyingSigner,
448+
} from '@solana/kit';
449+
const compiledTransaction = null as unknown as Transaction & TransactionWithLifetime;
450+
const signerA = null as unknown as TransactionPartialSigner;
451+
const signerB = null as unknown as TransactionPartialSigner;
452+
// ---cut-before---
453+
import { partiallySignTransactionWithSigners } from '@solana/kit';
454+
455+
const signedTransaction = await partiallySignTransactionWithSigners(
456+
[signerA, signerB],
457+
compiledTransaction,
458+
);
459+
```
460+
461+
<Callout type="info">
462+
This function ignores any [`TransactionSendingSigners`](/api/type-aliases/TransactionSendingSigner) in the provided array. Use [`signAndSendTransactionWithSigners`](#sign-and-send-transaction-with-signers) if you need to sign and send.
463+
</Callout>
464+
465+
#### signTransactionWithSigners [!toc] [#sign-transaction-with-signers]
466+
467+
Signs a compiled transaction using the provided signers and asserts that all required signatures are present. The returned transaction satisfies the [`FullySignedTransaction`](/api/type-aliases/FullySignedTransaction) type.
468+
469+
```ts twoslash
470+
import {
471+
Transaction,
472+
TransactionWithLifetime,
473+
TransactionPartialSigner,
474+
} from '@solana/kit';
475+
const compiledTransaction = null as unknown as Transaction & TransactionWithLifetime;
476+
const signerA = null as unknown as TransactionPartialSigner;
477+
const signerB = null as unknown as TransactionPartialSigner;
478+
// ---cut-before---
479+
import { signTransactionWithSigners } from '@solana/kit';
480+
481+
const fullySignedTransaction = await signTransactionWithSigners(
482+
[signerA, signerB],
483+
compiledTransaction,
484+
);
485+
```
486+
487+
<Callout type="warn">
488+
This function will throw if the resultant transaction is missing a signature for one of its required signers. To partially sign, use [`partiallySignTransactionWithSigners`](#partially-sign-transaction-with-signers).
489+
</Callout>
490+
491+
#### signAndSendTransactionWithSigners [!toc] [#sign-and-send-transaction-with-signers]
492+
493+
Signs a compiled transaction using the provided signers and sends it to the network via the [`TransactionSendingSigner`](/api/type-aliases/TransactionSendingSigner) found in the array. Returns the transaction signature as [`SignatureBytes`](/api/type-aliases/SignatureBytes).
494+
495+
```ts twoslash
496+
import {
497+
Transaction,
498+
TransactionWithLifetime,
499+
TransactionPartialSigner,
500+
TransactionSendingSigner,
501+
} from '@solana/kit';
502+
const compiledTransaction = null as unknown as Transaction & TransactionWithLifetime;
503+
const partialSigner = null as unknown as TransactionPartialSigner;
504+
const sendingSigner = null as unknown as TransactionSendingSigner;
505+
// ---cut-before---
506+
import { signAndSendTransactionWithSigners } from '@solana/kit';
507+
508+
const signature = await signAndSendTransactionWithSigners(
509+
[partialSigner, sendingSigner],
510+
compiledTransaction,
511+
);
512+
```
513+
514+
The provided signers must contain exactly one resolvable [`TransactionSendingSigner`](/api/type-aliases/TransactionSendingSigner). You can validate this ahead of time using [`assertContainsResolvableTransactionSendingSigner`](#assert-contains-resolvable-transaction-sending-signer).
515+
516+
#### assertContainsResolvableTransactionSendingSigner [!toc] [#assert-contains-resolvable-transaction-sending-signer]
517+
518+
Asserts that an array of signers contains at least one [`TransactionSendingSigner`](/api/type-aliases/TransactionSendingSigner) that can be unambiguously resolved. This is useful for validating your signers before calling [`signAndSendTransactionWithSigners`](#sign-and-send-transaction-with-signers).
519+
520+
```ts twoslash
521+
import { TransactionSigner } from '@solana/kit';
522+
const mySigners = null as unknown as TransactionSigner[];
523+
// ---cut-before---
524+
import { assertContainsResolvableTransactionSendingSigner } from '@solana/kit';
525+
526+
// Throws if no resolvable sending signer is found or if
527+
// multiple sending-only signers conflict with each other.
528+
assertContainsResolvableTransactionSendingSigner(mySigners);
529+
```
530+
531+
### How composite signers are resolved
532+
533+
When a signer implements multiple interfaces (e.g. both [`TransactionPartialSigner`](/api/type-aliases/TransactionPartialSigner) and [`TransactionSendingSigner`](/api/type-aliases/TransactionSendingSigner)), the signing functions automatically resolve it to the most appropriate role:
534+
535+
1. **[`TransactionSendingSigner`](/api/type-aliases/TransactionSendingSigner)** — Used if no other signer exclusively implements the sending interface. Only one sending signer can be active.
536+
2. **[`TransactionModifyingSigner`](/api/type-aliases/TransactionModifyingSigner)** — Used if no other signer exclusively implements the modifying interface. Modifying signers run sequentially before all others.
537+
3. **[`TransactionPartialSigner`](/api/type-aliases/TransactionPartialSigner)** — The fallback. Partial signers run in parallel after all modifying signers have finished.
538+
539+
This means a composite signer is always demoted to the least powerful interface that avoids conflicts with other signers.

docs/content/docs/concepts/transactions.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,8 @@ try {
340340
method to sign and send the transaction in a single step.
341341
</Callout>
342342

343+
For a comprehensive guide on all signing functions — including the lower-level [`partiallySignTransactionWithSigners`](/api/functions/partiallySignTransactionWithSigners), [`signTransactionWithSigners`](/api/functions/signTransactionWithSigners), and [`signAndSendTransactionWithSigners`](/api/functions/signAndSendTransactionWithSigners) functions that accept an explicit array of signers and a compiled transaction — see the [Signing with signers](/docs/concepts/signers#signing-with-signers) section.
344+
343345
Building transaction messages using `TransactionSigners` is the recommended way to create self-signable transaction messages. To sign with a `CryptoKey` directly, you first have to compile the transaction message.
344346

345347
```ts twoslash
@@ -391,6 +393,10 @@ try {
391393
[`partiallySignTransaction`](/api/functions/partiallySignTransaction) method.
392394
</Callout>
393395

396+
<Callout type="info">
397+
If you have [`TransactionSigner`](/api/type-aliases/TransactionSigner) objects rather than raw `CryptoKeyPairs`, you can use [`signTransactionWithSigners`](/api/functions/signTransactionWithSigners) and [`partiallySignTransactionWithSigners`](/api/functions/partiallySignTransactionWithSigners) instead. See the [Signing with signers](/docs/concepts/signers#signing-compiled-transactions) section for details.
398+
</Callout>
399+
394400
## Serializing transactions
395401

396402
If you would like to send a transaction to the network manually, you must first serialize it in a particular way. The [`Base64EncodedWireTransaction`](/api/type-aliases/Base64EncodedWireTransaction) represents the wire format of a transaction as a base64-encoded string.

docs/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@
3232
"@solana-program/memo": "^0.11.0",
3333
"@solana-program/system": "^0.12.0",
3434
"@solana-program/token": "^0.12.0",
35-
"@solana/compat": "6.3.0",
36-
"@solana/kit": "6.3.0",
37-
"@solana/react": "6.3.0",
35+
"@solana/compat": "6.5.0",
36+
"@solana/kit": "6.5.0",
37+
"@solana/react": "6.5.0",
3838
"@solana/web3.js": "^1.98.4",
39-
"@solana/webcrypto-ed25519-polyfill": "6.3.0",
39+
"@solana/webcrypto-ed25519-polyfill": "6.5.0",
4040
"@tailwindcss/postcss": "^4.1.4",
4141
"@types/mdx": "^2.0.13",
4242
"@types/node": "22.13.8",

0 commit comments

Comments
 (0)