Skip to content

Commit 77b9123

Browse files
madninjaclaude
andauthored
feat: add Jupiter V6 swap support (#516)
* feat: add Jupiter V6 swap support Add helium_lib::jupiter module for token swaps via the Jupiter V6 REST API. Calls the API directly instead of using the jup-ag crate (which requires solana-sdk v3, incompatible with our v2). - jupiter::Client with quote() and swap() methods - JupiterError dedicated error type - QuoteResponse serde types with pass-through for /swap endpoint - Transaction handling: base64 decode → bincode deserialize → fresh blockhash → re-sign - helium-wallet `swap` CLI command for interactive token swaps - Requires JUP_API_KEY env var; optional JUP_API_URL, JUP_SLIPPAGE_BPS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(swap): replace eprintln with JSON quote fields in output Include quote details (in_amount, out_amount, slippage, price_impact) in the standard JSON output instead of using eprintln, matching the pattern of all other wallet commands. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: promote futures to workspace, use TryFutureExt for async transforms - Add futures to workspace dependencies, use workspace ref in helium-lib and helium-wallet - Use TryFutureExt::map_err/map_ok before .await per project style - Fix clippy::redundant_closure in jupiter transaction decode Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(jupiter): address PR review findings Security: - Remove Debug derive from Client, implement manually with api_key redacted to prevent secret leakage in logs Error handling: - Return JupiterError from swap() instead of top-level Error, matching the onboarding module pattern of self-contained error types - Add Solana and Signing variants to JupiterError for RPC/signing failures within swap() - Fail on invalid JUP_SLIPPAGE_BPS instead of silently falling back - Truncate raw Jupiter API error bodies to 200 chars Validation: - Reject zero swap amounts in quote() - Reject same input/output mint in quote() - Add amount and finite-number validation in CLI swap command Cleanup: - Drop redundant .map_err() wrappers — plain ? with From impls - Drop anyhow! wrapping in CLI — JupiterError flows via ? into anyhow::Result like all other commands - Make RoutePlan/SwapInfo pub(crate) — not needed by crate consumers - Remove unused futures dep from helium-wallet (no longer needed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: re-export solana_transaction_status from helium-lib Allows downstream crates to use TransactionConfirmationStatus for proper enum matching instead of string-based Debug output comparison. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(jupiter): make API key optional for keyless access Jupiter offers keyless access at 0.5 RPS on api.jup.ag — sufficient for individual CLI swap use. API key is only needed for higher rate limits (production/automated use like alembic). - api_key is now Option<String> on Client - from_env() succeeds without JUP_API_KEY (keyless mode) - x-api-key header only sent when key is present - has_api_key() method for callers that require authenticated access - Env tests use Mutex to avoid parallel test pollution Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(jupiter): migrate from dead V6 API to V2 Jupiter V6 endpoint (api.jup.ag/swap/v6) returns 404 — it has been retired. Migrate to the V2 API which uses a single /order endpoint that returns both quote data and the assembled transaction. API changes: - Single GET /order call replaces separate GET /quote + POST /swap - swap() now takes input/output mints + amount directly (no separate quote step needed) - Returns (VersionedTransaction, block_height, OrderResponse) — the OrderResponse contains quote data for display/recording - taker param replaces the old SwapRequest.userPublicKey body field - OrderResponse has inline error/errorCode/errorMessage fields Also: - Fix empty error body producing "HTTP 404:" — now shows "empty response (HTTP 404)" - Add SwapError variant for Jupiter-reported errors in 200 responses - Add test for empty error body and error response deserialization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(jupiter): RoutePlan.percent is f64 in V2 API, not u8 Jupiter V2 returns percent as a float (e.g. 76.39), not an integer. Caused deserialization failure: "invalid type: floating point 76.39, expected u8". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent aa0730f commit 77b9123

10 files changed

Lines changed: 506 additions & 3 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ helium-proto = { git = "https://github.com/helium/proto", branch = "master", fea
2626
] }
2727
clap = { version = "4", features = ["derive"] }
2828
thiserror = "2.0.17"
29+
futures = "0"
2930
prost = "0.14.1"
3031
msg-signature = { git = "https://github.com/helium/proto", branch = "master" }
3132

helium-lib/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ h3o = { version = "0", features = ["serde"] }
2626
helium-crypto = { workspace = true }
2727
itertools = "0.14"
2828
jsonrpc_client = { version = "0.7", features = ["reqwest"] }
29-
futures = "*"
29+
futures.workspace = true
3030
futures-timer = "3"
3131
tracing = "0"
3232
base64 = { workspace = true }

helium-lib/src/error.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use crate::{anchor_client, anchor_lang, client, hotspot::cert, onboarding, solana_client, token};
1+
use crate::{
2+
anchor_client, anchor_lang, client, hotspot::cert, jupiter, onboarding, solana_client, token,
3+
};
24
use solana_sdk::signature::Signature;
35
use std::{array::TryFromSliceError, num::TryFromIntError, time::Duration};
46
use thiserror::Error;
@@ -52,6 +54,8 @@ pub enum Error {
5254
Encode(#[from] EncodeError),
5355
#[error("tuktuk: {0}")]
5456
Tuktuk(#[from] tuktuk_sdk::error::Error),
57+
#[error("jupiter: {0}")]
58+
Jupiter(#[from] jupiter::JupiterError),
5559
}
5660

5761
impl From<solana_client::client_error::ClientError> for Error {

0 commit comments

Comments
 (0)