Signers
Accounts with signing capabilities
Introduction
Signers are an abstraction that combines an account with an implementation that computes signatures on behalf of that account. No matter what actually computes the signature – a wallet app, a network API, the SubtleCrypto API, or a userspace implementation – so long as it implements the correct signer interface for your intended purpose, you can use it with all of Kit's signer-aware functions.
Signers are designed to make it easier to build, sign, and send transactions by taking the guesswork out of which accounts' key pairs need to sign a transaction. Signer-aware APIs allow you to associate signer objects with each transaction instruction that requires them, enabling Kit's transaction planning, signing, and sending functions to automatically collect and invoke them.
Installation
Signers are included within the @solana/kit library but you may also install them using their standalone package.
What is a signer?
All signers are wrappers around an Address. This means that most APIs that require an Address can be made to accept a signer. Each specific type of signer adds one or more capabilities, such as the ability to sign a message or a transaction on behalf of the account with that address. Some even add the ability to sign and send a transaction, which is common for wallets that you use in your browser or on your phone.
As you can see, this provides a consistent API regardless of how things are being signed behind the scenes. If tomorrow we need to use a browser wallet instead, we'd simply need to swap the generateKeyPairSigner() function with the signer factory of our choice.
Types of signers
This package offers a total of five different types of signers that may be used in combination when applicable. Three of them allow us to sign transactions whereas the other two are used for regular message signing.
They are separated into three categories:
- Partial signers: Given a message or transaction, provide one or more signatures for it. These signers are not able to modify the given data which allows us to run many of them in parallel.
- Modifying signers: Can choose to modify a message or transaction before signing it with zero or more private keys. Because modifying a message or transaction invalidates any pre-existing signatures over it, modifying signers must do their work before any other signer.
- Sending signers: Given a transaction, signs it and sends it immediately to the blockchain. When applicable, the signer may also decide to modify the provided transaction before signing it. This interface accommodates wallets that simply cannot sign a transaction without sending it at the same time. This category of signers does not apply to regular messages.
Thus, we end up with the following interfaces.
| Partial signers | Modifying signers | Sending signers | |
|---|---|---|---|
TransactionSigner | TransactionPartialSigner | TransactionModifyingSigner | TransactionSendingSigner |
MessageSigner | MessagePartialSigner | MessageModifyingSigner | N/A |
We will go through each of these five signer interfaces and their respective characteristics in the documentation below.
Signing transactions
Partial signers
TransactionPartialSigner is an interface that signs an array of Transactions without modifying their content. It defines a signTransactions function that returns a SignatureDictionary for each provided transaction. Such signature dictionaries are expected to be merged with the existing ones if any.
Characteristics:
- Parallel. It returns a signature dictionary for each provided transaction without modifying them, making it possible for multiple partial signers to sign the same transaction in parallel.
- Flexible order. The order in which we use these signers for a given transaction doesn’t matter.
Modifying signers
TransactionModifyingSigner is an interface that potentially modifies the provided Transactions before signing them. E.g. this enables wallets to inject additional instructions into the transaction before signing them. For each transaction, instead of returning a SignatureDictionary, its modifyAndSignTransactions function returns an updated Transaction with a potentially modified set of instructions and signature dictionary.
Characteristics:
- Sequential. Contrary to partial signers, these cannot be executed in parallel as each call can modify the provided transactions.
- First signers. For a given transaction, a modifying signer must always be used before a partial signer as the former will likely modify the transaction and thus impact the outcome of the latter.
- Potential conflicts. If more than one modifying signer is provided, the second signer may invalidate the signature of the first one. However, modifying signers may decide not to modify a transaction based on the existence of signatures for that transaction.
Sending signers
TransactionSendingSigner is an interface that signs one or multiple transactions before sending them immediately to the blockchain. It defines a signAndSendTransactions function that returns the transaction signature (i.e. its identifier) for each provided Transaction. This interface is required for PDA wallets and other types of wallets that don't provide an interface for signing transactions without sending them.
Note that it is also possible for such signers to modify the provided transactions before signing and sending them. This enables use cases where the modified transactions cannot be shared with the app and thus must be sent directly.
Characteristics:
- Single signer. Since this signer also sends the provided transactions, we can only use a single
TransactionSendingSignerfor a given set of transactions. - Last signer. Trivially, that signer must also be the last one used.
- Potential conflicts. Since signers may decide to modify the given transactions before sending them, they may invalidate previous signatures. However, signers may decide not to modify a transaction based on the existence of signatures for that transaction.
- Potential confirmation. Whilst this is not required by this interface, it is also worth noting that most wallets will also wait for the transaction to be confirmed (typically with a
confirmedcommitment) before notifying the app that they are done.
Signing messages
Signable messages
SignableMessage defines a message with any of the signatures that might have already been provided by other signers. This interface allows modifying signers to decide on whether or not they should modify the provided message depending on whether or not signatures already exist for such message. It also helps create a more consistent API by providing a structure analogous to transactions which also keep track of their signature dictionary.
You can use the createSignableMessage function to create a SignableMessage from a Uint8Array or UTF-8 string. It optionally accepts a signature dictionary if the message already contains signatures.
Partial signers
MessagePartialSigner is an interface that signs an array of SignableMessages without modifying their content. It defines a signMessages function that returns a SignatureDictionary for each provided message. Such signature dictionaries are expected to be merged with the existing ones if any.
Characteristics:
- Parallel. When multiple signers sign the same message, we can perform this operation in parallel to obtain all their signatures.
- Flexible order. The order in which we use these signers for a given message doesn’t matter.
Modifying signers
MessageModifyingSigner is an interface that potentially modifies the content of the provided SignableMessages before signing them. E.g. this enables wallets to prefix or suffix nonces to the messages they sign. For each message, instead of returning a SignatureDictionary, its modifyAndSignMessages function returns its updated SignableMessage with a potentially modified content and signature dictionary.
Characteristics:
- Sequential. Contrary to partial signers, these cannot be executed in parallel as each call can modify the content of the message.
- First signers. For a given message, a modifying signer must always be used before a partial signer as the former will likely modify the message and thus impact the outcome of the latter.
- Potential conflicts. If more than one modifying signer is provided, the second signer may invalidate the signature of the first one. However, modifying signers may decide not to modify a message based on the existence of signatures for that message.
Available signers
No-op signers
For a given address, a no-op signer can be created to offer an implementation of both the MessagePartialSigner and TransactionPartialSigner interfaces such that they do not sign anything. Namely, signing a transaction or a message with a NoopSigner will return an empty SignatureDictionary.
This signer may be useful:
- For testing purposes.
- For indicating that a given account is a signer and taking the responsibility to provide the signature for that account ourselves. For instance, if we need to send the transaction to a server that will sign it and send it for us.
Key pair signers
A key pair signer uses a CryptoKeyPair to sign messages and transactions. It implements both the MessagePartialSigner and TransactionPartialSigner interfaces and keeps track of the CryptoKeyPair instance used to sign messages and transactions.
createSignerFromKeyPair generateKeyPairSigner createKeyPairSignerFromBytes createKeyPairSignerFromPrivateKeyBytes
createSignerFromKeyPair
Creates a KeyPairSigner from a provided Crypto KeyPair. The signMessages and signTransactions functions of the returned signer will use the private key of the provided key pair to sign messages and transactions. Note that both the signMessages and signTransactions implementations are parallelized, meaning that they will sign all provided messages and transactions in parallel.
generateKeyPairSigner
A convenience function that generates a new Crypto KeyPair and immediately creates a KeyPairSigner from it.
createKeyPairSignerFromBytes
A convenience function that creates a new KeyPair from a 64-bytes Uint8Array secret key and immediately creates a KeyPairSigner from it.
createKeyPairSignerFromPrivateKeyBytes
A convenience function that creates a new KeyPair from a 32-bytes Uint8Array private key and immediately creates a KeyPairSigner from it.
Signing with signers
Kit provides helper functions that use TransactionSigner objects to sign and optionally send transactions. There are two families of signing functions:
- Transaction message functions extract signers from the transaction message — either from the fee payer or from account metas — and handle compilation and signing automatically.
- Transaction functions accept an explicit array of signers alongside a compiled
Transaction, giving you full control over which signers are used.
Signing transaction messages
The following functions extract TransactionSigners from a transaction message's account metas, compile the message into a Transaction, and use those signers to sign it.
partiallySignTransactionMessageWithSigners
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.
This function ignores any TransactionSendingSigners in the message since it does not send the transaction. Use signAndSendTransactionMessageWithSigners if you need to sign and send in a single step.
signTransactionMessageWithSigners
Signs a transaction message and asserts that all required signatures are present. The returned transaction satisfies the FullySignedTransaction type.
This function will throw if the transaction message does not carry a 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.
signAndSendTransactionMessageWithSigners
Signs a transaction message and sends it to the network via the TransactionSendingSigner found in the message's account metas. Returns the transaction signature as SignatureBytes.
The message must contain exactly one resolvable TransactionSendingSigner. You can check this ahead of time using isTransactionMessageWithSingleSendingSigner to provide a fallback strategy:
Signing compiled transactions
When you already have a compiled 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.
partiallySignTransactionWithSigners
Signs a compiled transaction using the provided TransactionModifyingSigners and TransactionPartialSigners without requiring all signatures to be present.
This function ignores any TransactionSendingSigners in the provided array. Use signAndSendTransactionWithSigners if you need to sign and send.
signTransactionWithSigners
Signs a compiled transaction using the provided signers and asserts that all required signatures are present. The returned transaction satisfies the FullySignedTransaction type.
This function will throw if the resultant transaction is missing a signature for one of its required signers. To partially sign, use partiallySignTransactionWithSigners.
signAndSendTransactionWithSigners
Signs a compiled transaction using the provided signers and sends it to the network via the TransactionSendingSigner found in the array. Returns the transaction signature as SignatureBytes.
The provided signers must contain exactly one resolvable TransactionSendingSigner. You can validate this ahead of time using assertContainsResolvableTransactionSendingSigner.
assertContainsResolvableTransactionSendingSigner
Asserts that an array of signers contains at least one TransactionSendingSigner that can be unambiguously resolved. This is useful for validating your signers before calling signAndSendTransactionWithSigners.
How composite signers are resolved
When a signer implements multiple interfaces (e.g. both TransactionPartialSigner and TransactionSendingSigner), the signing functions automatically resolve it to the most appropriate role:
TransactionSendingSigner— Used if no other signer exclusively implements the sending interface. Only one sending signer can be active.TransactionModifyingSigner— Used if no other signer exclusively implements the modifying interface. Modifying signers run sequentially before all others.TransactionPartialSigner— The fallback. Partial signers run in parallel after all modifying signers have finished.
This means a composite signer is always demoted to the least powerful interface that avoids conflicts with other signers.