The Solana wallet can already sign versioned transactions and work with both legacy SPL tokens and Token-2022 tokens through #48 – #50.
We now need a separate Jupiter Swap module that can exchange one Solana token for another. Swap logic must not become part of SolanaWallet: the wallet owns keys and signing, while the Jupiter service owns quote/order creation and swap execution.
The service must use Jupiters keyless Swap V2 API. No Jupiter account or API key should be required.
The initial implementation only needs to support exact-input swaps:
Spend exactly the specified input-token amount and receive as much as possible of the output token, subject to the configured maximum slippage.
Amounts exposed through the public API must use human-readable token units. Callers must not need to convert amounts into Solana base units themselves.
The returned amounts must reflect the amounts reported for the executed swap, including relevant fees charged in either token.
Implementation
Add a reference implementation under:
com.r35157.libs.jupiter.swap.impl.ref
The initial implementation must exclude the jupiterz router by adding excludeRouters=jupiterz to every Jupiter /order request. JupiterZ/RFQ orders, expireAt expiration and partial multi-party transaction signing are deferred to #66.
The implementation must:
Validate the token mints, amount and slippage.
Resolve the input and output mint decimals through SolanaBlockChain.
Support mints owned by both the legacy SPL Token program and Token-2022.
Convert the human-readable input amount into the exact raw integer amount expected by Jupiter.
Request an exact-input order from Jupiter Swap V2.
Validate the returned order before signing it.
Pass the unsigned transaction to SolanaWallet.signTransaction(...).
Submit the signed transaction through Jupiters execute endpoint.
Validate that Jupiter reports a successful execution.
Convert the reported raw input and output amounts back into human-readable token amounts.
Return the transaction signature and actual amounts as JupiterSwapResult.
Transaction construction and protocol DTOs must remain implementation details. Callers should only see the high-level swap(...) operation.
Keyless access and privacy
The service must use Jupiters keyless API access:
Do not require a Jupiter Developer Portal account.
Do not require or send an API key.
Do not add API-key configuration as part of this issue.
Respect the keyless /order rate limit of 0.5 requests per second, meaning at most one order request every two seconds.
The reference implementation must contain a thread-safe, JVM-wide rate limiter for Jupiter /order requests. Across all JupiterSwapServiceImpl instances in the same JVM, at least two seconds must elapse between /order requests. This is an implementation-specific Jupiter safeguard and must not introduce a general throttling framework. /execute requests must not be delayed by this limiter.
Validation and error handling
Once the /execute request has been sent, a timeout, lost response, interrupted response or other communication failure must be treated as an unknown execution result. The transaction may have landed even though no response was received.
The service must never automatically retry a submitted swap. Before deciding whether another swap should be attempted, the caller must reload the relevant wallet balances and base the decision on the current on-chain state.
The swap must fail before signing when:
The input and output mint are identical.
The input amount is zero or negative.
The amount cannot be represented exactly using the input token decimals.
The slippage value is invalid.
A mint does not exist.
A mint is not owned by a supported SPL token program.
Jupiter returns an order for different mints or a different input amount.
The returned transaction, request ID or block-height information is missing or invalid.
Clear failures must also be produced for:
Non-successful HTTP responses.
Malformed Jupiter responses.
Expired or rejected orders.
Blank signed transactions.
Failed Jupiter execution.
Interrupted HTTP requests.
Tests
Add integration-style tests using a local/mock Jupiter HTTP endpoint and test doubles for the wallet and Solana blockchain.
Tests should verify at least:
Correct /order request parameters.
Human-readable to raw-amount conversion.
Legacy SPL Token mint support.
Token-2022 mint support.
The complete order → sign → execute flow.
Correct construction of JupiterSwapResult.
Rejection of mismatching or malformed Jupiter orders.
Propagation of failed execution responses.
Acceptance criteria
JupiterSwapService is available in com.r35157.libs.jupiter.swap.
An exact-input swap can exchange one supported Solana token for another.
Callers work exclusively with human-readable amounts.
Both legacy SPL Token and Token-2022 mints are supported.
SolanaWallet performs the signing.
Jupiter performs transaction construction and execution.
No Jupiter account or API key is required.
Successful swaps return the signature and actual spent/received amounts.
Invalid or failed swaps produce clear exceptions.
Existing tests and the new Jupiter Swap tests pass.
An unknown /execute result is never automatically retried and is reported clearly to the caller.
Every /order request excludes the jupiterz router.
Out of scope
Exact-output swaps.
Automatic retries.
API-key-based Jupiter access.
Automatic token selection or route selection by the caller.
Direct integration with individual decentralized exchanges.
Native SOL handling beyond what Jupiter represents through its supported mint-based swap flow.
All unittests are correctly postproned
## Background
The Solana wallet can already sign versioned transactions and work with both legacy SPL tokens and Token-2022 tokens through #48 – #50.
We now need a separate Jupiter Swap module that can exchange one Solana token for another. Swap logic must not become part of `SolanaWallet`: the wallet owns keys and signing, while the Jupiter service owns quote/order creation and swap execution.
The service must use Jupiters keyless Swap V2 API. No Jupiter account or API key should be required.
## Public API
Add the public service interface:
```java
package com.r35157.libs.jupiter.swap;
public interface JupiterSwapService {
JupiterSwapResult swap(
ΩSPLMintAddressΩ inputTokenMint,
ΩAmountΩ inputTokenAmount,
ΩSPLMintAddressΩ outputTokenMint,
int maxSlippageBps
) throws IOException, InterruptedException;
}
```
The initial implementation only needs to support **exact-input swaps**:
> Spend exactly the specified input-token amount and receive as much as possible of the output token, subject to the configured maximum slippage.
Amounts exposed through the public API must use human-readable token units. Callers must not need to convert amounts into Solana base units themselves.
## Result
Add a result type containing:
```java
public record JupiterSwapResult(
ΩSolanaTransactionSignatureΩ transactionSignature,
ΩAmountΩ spentInputTokenAmount,
ΩAmountΩ receivedOutputTokenAmount
) {
}
```
The returned amounts must reflect the amounts reported for the executed swap, including relevant fees charged in either token.
## Implementation
Add a reference implementation under:
```text
com.r35157.libs.jupiter.swap.impl.ref
```
The initial implementation must exclude the jupiterz router by adding excludeRouters=jupiterz to every Jupiter /order request. JupiterZ/RFQ orders, expireAt expiration and partial multi-party transaction signing are deferred to #66.
The implementation must:
1. Validate the token mints, amount and slippage.
2. Resolve the input and output mint decimals through `SolanaBlockChain`.
3. Support mints owned by both the legacy SPL Token program and Token-2022.
4. Convert the human-readable input amount into the exact raw integer amount expected by Jupiter.
5. Request an exact-input order from Jupiter Swap V2.
6. Validate the returned order before signing it.
7. Pass the unsigned transaction to `SolanaWallet.signTransaction(...)`.
8. Submit the signed transaction through Jupiters execute endpoint.
9. Validate that Jupiter reports a successful execution.
10. Convert the reported raw input and output amounts back into human-readable token amounts.
11. Return the transaction signature and actual amounts as `JupiterSwapResult`.
Transaction construction and protocol DTOs must remain implementation details. Callers should only see the high-level `swap(...)` operation.
## Keyless access and privacy
The service must use Jupiters keyless API access:
* Do not require a Jupiter Developer Portal account.
* Do not require or send an API key.
* Do not add API-key configuration as part of this issue.
* Respect the keyless `/order` rate limit of 0.5 requests per second, meaning at most one order request every two seconds.
The reference implementation must contain a thread-safe, JVM-wide rate limiter for Jupiter /order requests. Across all JupiterSwapServiceImpl instances in the same JVM, at least two seconds must elapse between /order requests. This is an implementation-specific Jupiter safeguard and must not introduce a general throttling framework. /execute requests must not be delayed by this limiter.
## Validation and error handling
Once the /execute request has been sent, a timeout, lost response, interrupted response or other communication failure must be treated as an unknown execution result. The transaction may have landed even though no response was received.
The service must never automatically retry a submitted swap. Before deciding whether another swap should be attempted, the caller must reload the relevant wallet balances and base the decision on the current on-chain state.
The swap must fail before signing when:
* The input and output mint are identical.
* The input amount is zero or negative.
* The amount cannot be represented exactly using the input token decimals.
* The slippage value is invalid.
* A mint does not exist.
* A mint is not owned by a supported SPL token program.
* Jupiter returns an order for different mints or a different input amount.
* The returned transaction, request ID or block-height information is missing or invalid.
Clear failures must also be produced for:
* Non-successful HTTP responses.
* Malformed Jupiter responses.
* Expired or rejected orders.
* Blank signed transactions.
* Failed Jupiter execution.
* Interrupted HTTP requests.
## Tests
Add integration-style tests using a local/mock Jupiter HTTP endpoint and test doubles for the wallet and Solana blockchain.
Tests should verify at least:
* Correct `/order` request parameters.
* Human-readable to raw-amount conversion.
* Legacy SPL Token mint support.
* Token-2022 mint support.
* The complete `order → sign → execute` flow.
* Correct construction of `JupiterSwapResult`.
* Rejection of mismatching or malformed Jupiter orders.
* Propagation of failed execution responses.
## Acceptance criteria
* `JupiterSwapService` is available in `com.r35157.libs.jupiter.swap`.
* An exact-input swap can exchange one supported Solana token for another.
* Callers work exclusively with human-readable amounts.
* Both legacy SPL Token and Token-2022 mints are supported.
* `SolanaWallet` performs the signing.
* Jupiter performs transaction construction and execution.
* No Jupiter account or API key is required.
* Successful swaps return the signature and actual spent/received amounts.
* Invalid or failed swaps produce clear exceptions.
* Existing tests and the new Jupiter Swap tests pass.
* An unknown /execute result is never automatically retried and is reported clearly to the caller.
* Every /order request excludes the jupiterz router.
## Out of scope
* Exact-output swaps.
* Automatic retries.
* API-key-based Jupiter access.
* Automatic token selection or route selection by the caller.
* Direct integration with individual decentralized exchanges.
* Native SOL handling beyond what Jupiter represents through its supported mint-based swap flow.
* All unittests are correctly postproned
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
The Solana wallet can already sign versioned transactions and work with both legacy SPL tokens and Token-2022 tokens through #48 – #50.
We now need a separate Jupiter Swap module that can exchange one Solana token for another. Swap logic must not become part of
SolanaWallet: the wallet owns keys and signing, while the Jupiter service owns quote/order creation and swap execution.The service must use Jupiters keyless Swap V2 API. No Jupiter account or API key should be required.
Public API
Add the public service interface:
The initial implementation only needs to support exact-input swaps:
Amounts exposed through the public API must use human-readable token units. Callers must not need to convert amounts into Solana base units themselves.
Result
Add a result type containing:
The returned amounts must reflect the amounts reported for the executed swap, including relevant fees charged in either token.
Implementation
Add a reference implementation under:
The initial implementation must exclude the jupiterz router by adding excludeRouters=jupiterz to every Jupiter /order request. JupiterZ/RFQ orders, expireAt expiration and partial multi-party transaction signing are deferred to #66.
The implementation must:
SolanaBlockChain.SolanaWallet.signTransaction(...).JupiterSwapResult.Transaction construction and protocol DTOs must remain implementation details. Callers should only see the high-level
swap(...)operation.Keyless access and privacy
The service must use Jupiters keyless API access:
/orderrate limit of 0.5 requests per second, meaning at most one order request every two seconds.The reference implementation must contain a thread-safe, JVM-wide rate limiter for Jupiter /order requests. Across all JupiterSwapServiceImpl instances in the same JVM, at least two seconds must elapse between /order requests. This is an implementation-specific Jupiter safeguard and must not introduce a general throttling framework. /execute requests must not be delayed by this limiter.
Validation and error handling
Once the /execute request has been sent, a timeout, lost response, interrupted response or other communication failure must be treated as an unknown execution result. The transaction may have landed even though no response was received.
The service must never automatically retry a submitted swap. Before deciding whether another swap should be attempted, the caller must reload the relevant wallet balances and base the decision on the current on-chain state.
The swap must fail before signing when:
Clear failures must also be produced for:
Tests
Add integration-style tests using a local/mock Jupiter HTTP endpoint and test doubles for the wallet and Solana blockchain.
Tests should verify at least:
/orderrequest parameters.order → sign → executeflow.JupiterSwapResult.Acceptance criteria
JupiterSwapServiceis available incom.r35157.libs.jupiter.swap.SolanaWalletperforms the signing.Out of scope