Skip to content

order_and_execute() TypeError on transactions with multiple signatures #3

Description

@moratb

The Bug

UltraApiClient.order_and_execute() fails on transactions with multiple signature slots (common with larger swap amounts).

Error:

TypeError: argument 'keypairs': failed to extract enum Signer
'Signature' object cannot be converted to 'Keypair'

Root Cause

Bug in JupiterClient._sign_versioned_transaction() (lines 87-99):

signers=list(versioned_transaction.signatures) # List of Signature objectssigners[wallet_index] =wallet# ❌ Puts Keypair into Signature list - TYPE MISMATCHreturnVersionedTransaction(versioned_transaction.message, signers) # Fails!

The issue: The code replaces a Signature with a Keypair object, creating a mixed-type list. This works for single-signature transactions but fails for multi-signature transactions.

Reproduce

fromjup_python_sdk.clients.ultra_api_clientimportUltraApiClientfromjup_python_sdk.models.ultra_api.ultra_order_request_modelimportUltraOrderRequestclient=UltraApiClient(private_key_env_var="PRIVATE_KEY")
order_request=UltraOrderRequest(
input_mint="85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ",
output_mint="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
amount=100000000, # Large amounts trigger multi-signature transactionstaker=client._get_public_key()
)
client.order_and_execute(order_request) # ❌ TypeError

Fix

Replace _sign_versioned_transaction() in jupiter_client.py:

def_sign_versioned_transaction(
self, versioned_transaction: VersionedTransaction
) ->VersionedTransaction:
wallet=Keypair.from_bytes(self._load_private_key_bytes())
# Actually sign the message to create a Signature objectmy_signature=wallet.sign_message(bytes(versioned_transaction.message))
account_keys=versioned_transaction.message.account_keyswallet_index=account_keys.index(wallet.pubkey())
# Replace with Signature (not Keypair)signatures=list(versioned_transaction.signatures)
signatures[wallet_index] =my_signature# Use populate() method with all Signature objectsreturnVersionedTransaction.populate(
versioned_transaction.message,
signatures
)

Key change: Sign the message to create a Signature object, then use .populate() instead of the constructor. This maintains type consistency: all elements remain Signature objects.

Workaround

Use separate order() and execute() calls with manual signing

 order_request = UltraOrderRequest(
input_mint=in_address,
output_mint=out_address,
amount=in_amount,
taker=self.address
)
order_response = self.client.order(order_request)
request_id = order_response["requestId"]
## manual signing of the transaction
tx_bytes = base64.b64decode(order_response["transaction"])
raw_tx = VersionedTransaction.from_bytes(tx_bytes)
wallet = Keypair.from_bytes(self.client._load_private_key_bytes())
my_signature = wallet.sign_message(message.to_bytes_versioned(raw_tx.message))
wallet_index = raw_tx.message.account_keys.index(wallet.pubkey())
signatures = list(raw_tx.signatures)
signatures[wallet_index] = my_signature
signed_tx = VersionedTransaction.populate(raw_tx.message, signatures)
signed_transaction = base64.b64encode(bytes(signed_tx)).decode("utf-8")
execute_request = UltraExecuteRequest(
request_id=request_id,
signed_transaction=signed_transaction
)
client_response = self.client.execute(execute_request)
tx_signature = str(client_response["signature"])

Environment

  • Python: 3.13.5
  • jup-python-sdk: 1.1.0
  • solders: 0.26.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions