When a transaction requires multiple signatures but one signer is not immediately available (or should not share their keypair), you can use the NullSigner class to create a partially signed transaction.
Workflow:
- Create a transaction including a
NullSigner for the missing account. - Serialize the transaction and send it to the missing signer.
- The missing signer deserializes the transaction using
VersionedTransaction.from_bytes. - The signer identifies the index of their public key within the message's
account_keys. - The signer generates a signature for the message using
to_bytes_versioned(message). - The signer replaces the dummy signature in the
signatures list with their real signature. - The transaction is now fully signed and ready for broadcast.
from solders.hash import Hash
from solders.instruction import AccountMeta, Instruction
from solders.keypair import Keypair
from solders.message import MessageV0, to_bytes_versioned
from solders.null_signer import NullSigner
from solders.pubkey import Pubkey
from solders.transaction import VersionedTransaction
keypair0 = Keypair()
keypair1 = Keypair()
ix = Instruction(
Pubkey.new_unique(), b"", [AccountMeta(keypair1.pubkey(), True, False)]
)
message = MessageV0.try_compile(keypair0.pubkey(), [ix], [], Hash.default())
# sign with a real signer and a null signer
signers = (keypair0, NullSigner(keypair1.pubkey()))
partially_signed = VersionedTransaction(message, signers)
serialized = bytes(partially_signed)
deserialized = VersionedTransaction.from_bytes(serialized)
assert deserialized == partially_signed
deserialized_message = deserialized.message
# find the null signer in the deserialized transaction
keypair1_sig_index = next(
i
for i, key in enumerate(deserialized_message.account_keys)
if key == keypair1.pubkey()
)
sigs = deserialized.signatures
# replace the null signature with a real signature
sigs[keypair1_sig_index] = keypair1.sign_message(
to_bytes_versioned(deserialized_message)
)
deserialized.signatures = sigs
fully_signed = VersionedTransaction(message, [keypair0, keypair1])
assert deserialized.signatures == fully_signed.signatures
assert deserialized == fully_signed
assert bytes(deserialized) == bytes(fully_signed)