Issue #51 adds JupiterSwapService using Jupiter Swap V2.
The first implementation deliberately excludes the jupiterz router by sending:
excludeRouters=jupiterz
JupiterZ uses RFQ orders, which differ from ordinary aggregator routes in two important ways:
JupiterZ orders expire through expireAt instead of lastValidBlockHeight.
The transaction requires an additional market-maker signature, which Jupiter adds during /execute. The wallet must therefore partially sign the transaction without requiring every signature slot to be populated.
Excluding JupiterZ allows #51 to be implemented without introducing these additional transaction-signing requirements. However, JupiterZ may provide a better price than the aggregator routers, so it should be enabled in a separate follow-up.
Jupiter describes the RFQ signing and expiration flow here:
The normal aggregator-based swap flow from #51 must remain functional.
Requirements
Enable JupiterZ routing
Remove the unconditional:
excludeRouters=jupiterz
parameter from Jupiter /order requests.
After this change, Jupiter must be allowed to select any supported router, including jupiterz.
The public JupiterSwapService API must remain unchanged.
Router-aware order validation
The order response must retain and validate the router selected by Jupiter.
For ordinary aggregator routers such as Metis, Dflow and OKX:
lastValidBlockHeight must be handled as the transaction expiration.
Missing or invalid block-height information must cause the order to be rejected before signing.
For the jupiterz router:
expireAt must be handled as the RFQ quote expiration.
A missing, malformed or already expired expireAt value must cause the order to be rejected before signing.
The transaction must be signed and submitted to /execute immediately after the order has been received.
Unknown router types must fail clearly rather than silently being treated as an aggregator route.
Partial transaction signing
Verify and, if necessary, extend the Solana wallet signing implementation so it can partially sign a versioned transaction.
Partial signing must:
Add the wallet’s signature to the correct required-signer position.
Preserve any signatures already present in the transaction.
Preserve unfilled signature positions required by other signers.
Not require the transaction to be fully signed after the wallet has signed it.
Return a serialized transaction that Jupiter can complete during /execute.
The partial-signing behavior belongs in the general Solana wallet or transaction-signing layer. Jupiter-specific signing logic must not be added to the wallet.
Existing fully signable transactions used by Jupiter Perps and ordinary Solana operations must continue to work unchanged.
Execute JupiterZ orders
The partially signed transaction and its requestId must be submitted through the existing Jupiter /execute flow.
For a JupiterZ order:
Do not require lastValidBlockHeight.
Allow Jupiter to add the market-maker signature during /execute.
Validate the returned execution status and transaction signature in the same way as other swaps.
Calling JupiterZ directly without Jupiter Swap V2.
Selecting JupiterZ explicitly for individual swaps.
Changing the public JupiterSwapService API.
Automatic swap retries.
API-key configuration.
Changes to EvelynBurnerService.
## Background
Issue #51 adds `JupiterSwapService` using Jupiter Swap V2.
The first implementation deliberately excludes the `jupiterz` router by sending:
```text
excludeRouters=jupiterz
```
JupiterZ uses RFQ orders, which differ from ordinary aggregator routes in two important ways:
* JupiterZ orders expire through `expireAt` instead of `lastValidBlockHeight`.
* The transaction requires an additional market-maker signature, which Jupiter adds during `/execute`. The wallet must therefore partially sign the transaction without requiring every signature slot to be populated.
Excluding JupiterZ allows #51 to be implemented without introducing these additional transaction-signing requirements. However, JupiterZ may provide a better price than the aggregator routers, so it should be enabled in a separate follow-up.
Jupiter describes the RFQ signing and expiration flow here:
https://developers.jup.ag/docs/swap/order-and-execute
## Dependency
This issue depends on #51.
The normal aggregator-based swap flow from #51 must remain functional.
## Requirements
### Enable JupiterZ routing
Remove the unconditional:
```text
excludeRouters=jupiterz
```
parameter from Jupiter `/order` requests.
After this change, Jupiter must be allowed to select any supported router, including `jupiterz`.
The public `JupiterSwapService` API must remain unchanged.
### Router-aware order validation
The order response must retain and validate the router selected by Jupiter.
For ordinary aggregator routers such as Metis, Dflow and OKX:
* `lastValidBlockHeight` must be handled as the transaction expiration.
* Missing or invalid block-height information must cause the order to be rejected before signing.
For the `jupiterz` router:
* `expireAt` must be handled as the RFQ quote expiration.
* A missing, malformed or already expired `expireAt` value must cause the order to be rejected before signing.
* The transaction must be signed and submitted to `/execute` immediately after the order has been received.
Unknown router types must fail clearly rather than silently being treated as an aggregator route.
### Partial transaction signing
Verify and, if necessary, extend the Solana wallet signing implementation so it can partially sign a versioned transaction.
Partial signing must:
* Add the wallet’s signature to the correct required-signer position.
* Preserve any signatures already present in the transaction.
* Preserve unfilled signature positions required by other signers.
* Not require the transaction to be fully signed after the wallet has signed it.
* Return a serialized transaction that Jupiter can complete during `/execute`.
The partial-signing behavior belongs in the general Solana wallet or transaction-signing layer. Jupiter-specific signing logic must not be added to the wallet.
Existing fully signable transactions used by Jupiter Perps and ordinary Solana operations must continue to work unchanged.
### Execute JupiterZ orders
The partially signed transaction and its `requestId` must be submitted through the existing Jupiter `/execute` flow.
For a JupiterZ order:
* Do not require `lastValidBlockHeight`.
* Allow Jupiter to add the market-maker signature during `/execute`.
* Validate the returned execution status and transaction signature in the same way as other swaps.
* Continue returning the normal `JupiterSwapResult`.
### Error handling
Handle JupiterZ/RFQ execution failures clearly, including:
* RFQ failed to land.
* Unknown RFQ failure.
* Invalid RFQ payload.
* Expired quote.
* Swap rejected by the market maker.
An `/execute` timeout or lost response must still be treated as an unknown execution result.
The service must never automatically repeat the swap. The caller must reload wallet balances before deciding whether another swap should be attempted.
## Tests
Add tests covering at least:
* A JupiterZ `/order` response containing `expireAt`.
* Rejection of a missing, malformed or expired `expireAt`.
* Partial signing where the wallet signature is added while the market-maker signature remains unfilled.
* Preservation of signatures already present in the transaction.
* Submission of the partially signed transaction to `/execute`.
* Successful JupiterZ execution and construction of `JupiterSwapResult`.
* JupiterZ-specific `/order` and `/execute` failures.
* Continued support for aggregator orders using `lastValidBlockHeight`.
* Continued support for transactions that only require the wallet’s signature.
* Verification that `/order` no longer excludes `jupiterz`.
## Acceptance criteria
* JupiterZ is no longer excluded from Jupiter `/order` requests.
* `JupiterSwapService` accepts both aggregator and JupiterZ orders.
* Aggregator orders use `lastValidBlockHeight`.
* JupiterZ orders use `expireAt`.
* The wallet can add its signature without destroying or requiring other signatures.
* Jupiter can add the market-maker signature during `/execute`.
* The public `JupiterSwapService` API remains unchanged.
* JupiterZ-specific failures produce clear exceptions.
* No submitted swap is automatically retried.
* Existing Jupiter Swap, Jupiter Perps and Solana wallet behavior remains functional.
* All existing and new tests pass.
## Out of scope
* Calling JupiterZ directly without Jupiter Swap V2.
* Selecting JupiterZ explicitly for individual swaps.
* Changing the public `JupiterSwapService` API.
* Automatic swap retries.
* API-key configuration.
* Changes to `EvelynBurnerService`.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Background
Issue #51 adds
JupiterSwapServiceusing Jupiter Swap V2.The first implementation deliberately excludes the
jupiterzrouter by sending:JupiterZ uses RFQ orders, which differ from ordinary aggregator routes in two important ways:
expireAtinstead oflastValidBlockHeight./execute. The wallet must therefore partially sign the transaction without requiring every signature slot to be populated.Excluding JupiterZ allows #51 to be implemented without introducing these additional transaction-signing requirements. However, JupiterZ may provide a better price than the aggregator routers, so it should be enabled in a separate follow-up.
Jupiter describes the RFQ signing and expiration flow here:
https://developers.jup.ag/docs/swap/order-and-execute
Dependency
This issue depends on #51.
The normal aggregator-based swap flow from #51 must remain functional.
Requirements
Enable JupiterZ routing
Remove the unconditional:
parameter from Jupiter
/orderrequests.After this change, Jupiter must be allowed to select any supported router, including
jupiterz.The public
JupiterSwapServiceAPI must remain unchanged.Router-aware order validation
The order response must retain and validate the router selected by Jupiter.
For ordinary aggregator routers such as Metis, Dflow and OKX:
lastValidBlockHeightmust be handled as the transaction expiration.For the
jupiterzrouter:expireAtmust be handled as the RFQ quote expiration.expireAtvalue must cause the order to be rejected before signing./executeimmediately after the order has been received.Unknown router types must fail clearly rather than silently being treated as an aggregator route.
Partial transaction signing
Verify and, if necessary, extend the Solana wallet signing implementation so it can partially sign a versioned transaction.
Partial signing must:
/execute.The partial-signing behavior belongs in the general Solana wallet or transaction-signing layer. Jupiter-specific signing logic must not be added to the wallet.
Existing fully signable transactions used by Jupiter Perps and ordinary Solana operations must continue to work unchanged.
Execute JupiterZ orders
The partially signed transaction and its
requestIdmust be submitted through the existing Jupiter/executeflow.For a JupiterZ order:
lastValidBlockHeight./execute.JupiterSwapResult.Error handling
Handle JupiterZ/RFQ execution failures clearly, including:
An
/executetimeout or lost response must still be treated as an unknown execution result.The service must never automatically repeat the swap. The caller must reload wallet balances before deciding whether another swap should be attempted.
Tests
Add tests covering at least:
/orderresponse containingexpireAt.expireAt./execute.JupiterSwapResult./orderand/executefailures.lastValidBlockHeight./orderno longer excludesjupiterz.Acceptance criteria
/orderrequests.JupiterSwapServiceaccepts both aggregator and JupiterZ orders.lastValidBlockHeight.expireAt./execute.JupiterSwapServiceAPI remains unchanged.Out of scope
JupiterSwapServiceAPI.EvelynBurnerService.