Skip to content

Commit 45def06

Browse files
v14.1.1 (#32)
1 parent 3c9c994 commit 45def06

File tree

4 files changed

+99
-75
lines changed

4 files changed

+99
-75
lines changed

.coverage

520 KB
Binary file not shown.

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ config = {
111111
"private_key": "my-private-key", # Required - base58 string
112112
"platform_fee_bps": 50, # Optional - platform fee in basis points (e.g., 50 = 0.5%)
113113
"fee_account": "your-fee-token-account", # Optional - token account to receive platform fees
114-
"referral_account": "your-referral-account", # Optional - referral account for tracking
115114
"payer_private_key": "payer-private-key", # Optional - for gasless/sponsored transactions
116115
"rpc_url": "https://api.mainnet-beta.solana.com", # Optional - RPC URL (defaults to mainnet)
117116
},
@@ -375,7 +374,6 @@ config = {
375374
"signing_key": "wallet-auth:your-signing-key", # Required - your Privy wallet authorization signing key
376375
"platform_fee_bps": 50, # Optional - platform fee in basis points (e.g., 50 = 0.5%)
377376
"fee_account": "your-fee-token-account", # Optional - token account to receive platform fees
378-
"referral_account": "your-referral-account", # Optional - referral account for tracking
379377
"payer_private_key": "payer-private-key", # Optional - for gasless/sponsored transactions
380378
},
381379
},

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "sakit"
3-
version = "14.1.0"
3+
version = "14.1.1"
44
description = "Solana Agent Kit"
55
authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
66
license = "MIT"

sakit/privy_dflow_swap.py

Lines changed: 98 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -282,82 +282,108 @@ async def execute( # pragma: no cover
282282
payer_keypair = Keypair.from_base58_string(self._payer_private_key)
283283
sponsor = str(payer_keypair.pubkey())
284284

285-
# Get order from DFlow
286-
order_result = await dflow.get_order(
287-
input_mint=input_mint,
288-
output_mint=output_mint,
289-
amount=int(amount),
290-
user_public_key=public_key,
291-
slippage_bps=slippage_bps if slippage_bps > 0 else None,
292-
platform_fee_bps=self._platform_fee_bps,
293-
platform_fee_mode="outputMint",
294-
fee_account=self._fee_account,
295-
referral_account=self._referral_account,
296-
sponsor=sponsor,
297-
)
298-
299-
if not order_result.success:
300-
return {"status": "error", "message": order_result.error}
301-
302-
if not order_result.transaction:
303-
return {
304-
"status": "error",
305-
"message": "No transaction returned from DFlow.",
306-
}
307-
308-
# Sign transaction
309-
tx_to_sign = order_result.transaction
310-
311-
# If gasless, sign with payer first
312-
if self._payer_private_key:
313-
payer_keypair = Keypair.from_base58_string(self._payer_private_key)
314-
tx_bytes = base64.b64decode(order_result.transaction)
315-
transaction = VersionedTransaction.from_bytes(tx_bytes)
316-
message_bytes = to_bytes_versioned(transaction.message)
317-
payer_signature = payer_keypair.sign_message(message_bytes)
318-
319-
# Create partially signed transaction
320-
partially_signed = VersionedTransaction.populate(
321-
transaction.message,
322-
[payer_signature, transaction.signatures[1]],
285+
# Retry logic for blockhash expiration
286+
max_retries = 3
287+
last_error = None
288+
289+
for attempt in range(max_retries):
290+
# Get fresh order from DFlow (includes fresh blockhash)
291+
order_result = await dflow.get_order(
292+
input_mint=input_mint,
293+
output_mint=output_mint,
294+
amount=int(amount),
295+
user_public_key=public_key,
296+
slippage_bps=slippage_bps if slippage_bps > 0 else None,
297+
platform_fee_bps=self._platform_fee_bps,
298+
platform_fee_mode="outputMint",
299+
fee_account=self._fee_account,
300+
referral_account=self._referral_account,
301+
sponsor=sponsor,
323302
)
324-
tx_to_sign = base64.b64encode(bytes(partially_signed)).decode("utf-8")
325-
326-
# Sign and send via Privy (uses signAndSendTransaction RPC method)
327-
send_result = await _privy_sign_and_send_transaction(
328-
privy_client,
329-
wallet_id,
330-
tx_to_sign,
331-
self._signing_key,
332-
)
333-
334-
if not send_result.get("success"):
335-
return {
336-
"status": "error",
337-
"message": send_result.get(
338-
"error", "Failed to sign and send transaction via Privy."
339-
),
340-
}
341303

342-
signature = send_result.get("hash")
343-
if not signature:
344-
return {
345-
"status": "error",
346-
"message": "No transaction signature returned from Privy.",
347-
}
304+
if not order_result.success:
305+
return {"status": "error", "message": order_result.error}
306+
307+
if not order_result.transaction:
308+
return {
309+
"status": "error",
310+
"message": "No transaction returned from DFlow.",
311+
}
312+
313+
# Sign transaction
314+
tx_to_sign = order_result.transaction
315+
316+
# If gasless, sign with payer first
317+
if self._payer_private_key:
318+
payer_keypair = Keypair.from_base58_string(self._payer_private_key)
319+
tx_bytes = base64.b64decode(order_result.transaction)
320+
transaction = VersionedTransaction.from_bytes(tx_bytes)
321+
message_bytes = to_bytes_versioned(transaction.message)
322+
payer_signature = payer_keypair.sign_message(message_bytes)
323+
324+
# Create partially signed transaction
325+
partially_signed = VersionedTransaction.populate(
326+
transaction.message,
327+
[payer_signature, transaction.signatures[1]],
328+
)
329+
tx_to_sign = base64.b64encode(bytes(partially_signed)).decode(
330+
"utf-8"
331+
)
332+
333+
# Sign and send via Privy (uses signAndSendTransaction RPC method)
334+
send_result = await _privy_sign_and_send_transaction(
335+
privy_client,
336+
wallet_id,
337+
tx_to_sign,
338+
self._signing_key,
339+
)
348340

341+
if send_result.get("success"):
342+
signature = send_result.get("hash")
343+
if not signature:
344+
return {
345+
"status": "error",
346+
"message": "No transaction signature returned from Privy.",
347+
}
348+
349+
return {
350+
"status": "success",
351+
"signature": signature,
352+
"input_amount": order_result.in_amount,
353+
"output_amount": order_result.out_amount,
354+
"min_output_amount": order_result.min_out_amount,
355+
"input_mint": order_result.input_mint,
356+
"output_mint": order_result.output_mint,
357+
"price_impact": order_result.price_impact_pct,
358+
"platform_fee": order_result.platform_fee,
359+
"execution_mode": order_result.execution_mode,
360+
"message": f"Swap successful! Signature: {signature}",
361+
}
362+
363+
# Check if it's a blockhash error - retry with fresh transaction
364+
error_msg = send_result.get("error", "")
365+
if (
366+
"blockhash" in error_msg.lower()
367+
or "Blockhash not found" in error_msg
368+
):
369+
last_error = error_msg
370+
logger.warning(
371+
f"Blockhash expired on attempt {attempt + 1}/{max_retries}, retrying with fresh transaction..."
372+
)
373+
continue
374+
else:
375+
# Non-blockhash error, don't retry
376+
return {
377+
"status": "error",
378+
"message": send_result.get(
379+
"error", "Failed to sign and send transaction via Privy."
380+
),
381+
}
382+
383+
# All retries exhausted
349384
return {
350-
"status": "success",
351-
"signature": signature,
352-
"input_amount": order_result.in_amount,
353-
"output_amount": order_result.out_amount,
354-
"min_output_amount": order_result.min_out_amount,
355-
"input_mint": order_result.input_mint,
356-
"output_mint": order_result.output_mint,
357-
"price_impact": order_result.price_impact_pct,
358-
"platform_fee": order_result.platform_fee,
359-
"execution_mode": order_result.execution_mode,
360-
"message": f"Swap successful! Signature: {signature}",
385+
"status": "error",
386+
"message": f"Transaction failed after {max_retries} attempts. Last error: {last_error}",
361387
}
362388

363389
except Exception as e:

0 commit comments

Comments
 (0)