-
Couldn't load subscription status.
- Fork 6
Description
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 objects
signers[wallet_index] = wallet # ❌ Puts Keypair into Signature list - TYPE MISMATCH
return VersionedTransaction(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
from jup_python_sdk.clients.ultra_api_client import UltraApiClient
from jup_python_sdk.models.ultra_api.ultra_order_request_model import UltraOrderRequest
client = UltraApiClient(private_key_env_var="PRIVATE_KEY")
order_request = UltraOrderRequest(
input_mint="85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ",
output_mint="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
amount=100000000, # Large amounts trigger multi-signature transactions
taker=client._get_public_key()
)
client.order_and_execute(order_request) # ❌ TypeErrorFix
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 object
my_signature = wallet.sign_message(bytes(versioned_transaction.message))
account_keys = versioned_transaction.message.account_keys
wallet_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 objects
return VersionedTransaction.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