1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
use serde::{Deserialize, Serialize};
use super::QuoteResponse;
/// SwapRequest is a struct that represents the request body for the swap transaction.
///
/// user SwapRequest::new() and the fluent setters to configure parameters.
///
/// [Official API docs](https://dev.jup.ag/docs/api/swap-api/swap)
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SwapRequest {
/// Rquired. The public key of the user initiating the swap.
pub user_public_key: String,
/// Automatically wrap/unwrap native SOL to/from WSOL Default (true)
/// When true, uses SOL and unwraps WSOL post-swap.
/// When false, uses WSOL only and leaves it wrapped.
/// Ignored if `destination_token_account` is set.
#[serde(skip_serializing_if = "Option::is_none")]
pub wrap_and_unwrap_sol: Option<bool>,
/// Enables use of shared intermediate token accounts.
/// Helps simplify swaps that use complex routing.
/// Default: decided by routing engine
#[serde(skip_serializing_if = "Option::is_none")]
pub use_shared_accounts: Option<bool>,
/// Associated Token account (must be input/output mint) to collect fees.
#[serde(skip_serializing_if = "Option::is_none")]
pub fee_account: Option<String>,
/// Tracking key to identify integrator or user swaps.
#[serde(skip_serializing_if = "Option::is_none")]
pub tracking_account: Option<String>,
/// Optional prioritization fee configuration
#[serde(skip_serializing_if = "Option::is_none")]
pub prioritization_fee_lamports: Option<PrioritizationFeeLamports>,
/// Build a legacy transaction instead of a versioned one.
/// Should be consistent with the `/quote` response.
/// Default: false
#[serde(skip_serializing_if = "Option::is_none")]
pub as_legacy_transaction: Option<bool>,
/// Public key of a token account that will be used to receive the token out of the swap
/// If not provided, the signer's token account will be used
/// If provided, we assume that the token account is already initialized
#[serde(skip_serializing_if = "Option::is_none")]
pub destination_token_account: Option<String>,
/// When enabled, it will do a swap simulation to get the compute unit used and set it in ComputeBudget's compute unit limit
/// This incurs one extra RPC call to simulate this
/// We recommend to enable this to estimate compute unit correctly and reduce priority fees needed or have higher chance to be included in a block
#[serde(skip_serializing_if = "Option::is_none")]
pub dynamic_compute_unit_limit: Option<bool>,
/// When enabled, it will not do any additional RPC calls to check on required accounts
/// Enable it only when you already setup all the accounts needed for the trasaction, like wrapping or unwrapping sol, or destination account is already created
/// Default: false
#[serde(skip_serializing_if = "Option::is_none")]
pub skip_user_account_rpc_calls: Option<bool>,
/// When enabled, it estimates slippage and apply it in the swap transaction directly, overwriting the slippageBps parameter in the quote response.
/// Used together with dynamicSlippage in /quote, otherwise the slippage used will be the one in the /quote's slippageBps
#[serde(skip_serializing_if = "Option::is_none")]
pub dyanmic_slippage: Option<bool>,
/// To use an exact compute unit price to calculate priority fee
/// computeUnitLimit (1400000) * computeUnitPriceMicroLamports
#[serde(skip_serializing_if = "Option::is_none")]
pub compute_unit_price_micro_lamports: Option<u64>,
/// Pass in the number of slots we want the transaction to be valid for
/// Example: If you pass in 10 slots, the transaction will be valid for ~400ms * 10 = approximately 4 seconds before it expires
#[serde(skip_serializing_if = "Option::is_none")]
pub blockhash_slots_to_expiry: Option<u64>,
pub quote_response: QuoteResponse,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PrioritizationFeeLamports {
pub jito_tip_lamports: Option<u64>,
pub priority_level_with_max_lamports: PriorityLevelWithMaxLamports,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PriorityLevelWithMaxLamports {
pub max_lamports: u32,
pub priority_level: PriorityLevel,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PriorityLevel {
Medium,
High,
VeryHigh,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SwapResponse {
pub swap_transaction: String,
pub last_valid_block_height: u64,
pub prioritization_fee_lamports: u64,
}
impl SwapRequest {
/// Creates a new `SwapRequest` from a user public key and quote response.
///
/// # Arguments
/// * `input_wallet` - The user's public key as a string.
/// * `quote` - The `QuoteResponse` obtained from a quoting endpoint.
///
/// # Returns
/// A `SwapRequest` instance with default `None` values for all optional fields.
///
/// # Example
/// ```
/// let payload = SwapRequest::new("YourPubKey...", quote);
/// ```
pub fn new(input_wallet: &str, quote: QuoteResponse) -> Self {
Self {
user_public_key: input_wallet.to_string(),
wrap_and_unwrap_sol: None,
use_shared_accounts: None,
fee_account: None,
tracking_account: None,
prioritization_fee_lamports: None,
as_legacy_transaction: None,
destination_token_account: None,
dynamic_compute_unit_limit: None,
skip_user_account_rpc_calls: None,
dyanmic_slippage: None,
compute_unit_price_micro_lamports: None,
blockhash_slots_to_expiry: None,
quote_response: quote,
}
}
/// Sets whether to wrap or unwrap native SOL.
///
/// If `true`, native SOL will be wrapped into WSOL before the swap
/// and unwrapped afterward. This is the default behavior.
pub fn wrap_and_unwrap_sol(mut self, wrap: bool) -> Self {
self.wrap_and_unwrap_sol = Some(wrap);
self
}
/// Sets whether to use shared intermediate token accounts.
///
/// This can reduce transaction size and complexity for some swap routes.
pub fn use_shared_accounts(mut self, shared: bool) -> Self {
self.use_shared_accounts = Some(shared);
self
}
/// Set An token account that will be used to collect fees
///
/// The mint of the token account can only be either the input or output mint of the swap
pub fn fee_account(mut self, account: String) -> Self {
self.fee_account = Some(account);
self
}
/// Specify any public key that belongs to you to track the transactions
///
/// Useful for integrators to get all the swap transactions from this public key. Query the data using a block explorer like Solscan/SolanaFM or query like Dune/Flipside
pub fn tracking_account(mut self, account: String) -> Self {
self.tracking_account = Some(account);
self
}
/// Set prioritization fee lamports
pub fn prioritization_fee_jito_tip(mut self, fee: u64) -> Self {
self.prioritization_fee_lamports = Some(PrioritizationFeeLamports {
jito_tip_lamports: Some(fee),
priority_level_with_max_lamports: PriorityLevelWithMaxLamports {
max_lamports: 0,
priority_level: PriorityLevel::Medium,
},
});
self
}
/// set prioritization config
pub fn prioritization_fee_config(
mut self,
jito_tip: Option<u64>,
max_lamports: u32,
priority_level: PriorityLevel,
) -> Self {
self.prioritization_fee_lamports = Some(PrioritizationFeeLamports {
jito_tip_lamports: jito_tip,
priority_level_with_max_lamports: PriorityLevelWithMaxLamports {
max_lamports,
priority_level,
},
});
self
}
/// Forces the transaction to be built as a legacy (non-versioned) transaction.
pub fn as_legacy_transaction(mut self, legacy: bool) -> Self {
self.as_legacy_transaction = Some(legacy);
self
}
/// Sets a specific destination token account for the swap output.
///
/// If not set, the user's associated token account will be used.
pub fn destination_token_account(mut self, account: String) -> Self {
self.destination_token_account = Some(account);
self
}
/// Enables simulation-based estimation of compute unit usage.
///
/// This helps optimize compute budget usage and reduce priority fees. one extra RPC call
pub fn dynamic_compute_unit_limit(mut self, limit: bool) -> Self {
self.dynamic_compute_unit_limit = Some(limit);
self
}
/// Skips account-checking RPC calls.
///
/// Enable only if you have pre-configured all token accounts and SOL wrapping/unwrapping.
pub fn skip_user_account_rpc_calls(mut self, skip: bool) -> Self {
self.skip_user_account_rpc_calls = Some(skip);
self
}
/// Enables dynamic slippage estimation.
///
/// If enabled, slippage will be recalculated at swap-time instead of using a fixed value.
pub fn dyanmic_slippage(mut self, dynamic: bool) -> Self {
self.dyanmic_slippage = Some(dynamic);
self
}
/// Sets a fixed compute unit price in micro-lamports for fee calculation.
pub fn compute_unit_price_micro_lamports(mut self, price: u64) -> Self {
self.compute_unit_price_micro_lamports = Some(price);
self
}
/// Sets the number of slots until the transaction expires.
///
/// 1 slot ≈ 400ms. For example, 10 slots ≈ 4 seconds.
pub fn blockhash_slots_to_expiry(mut self, slots: u64) -> Self {
self.blockhash_slots_to_expiry = Some(slots);
self
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountMeta {
pub pubkey: String,
pub is_signer: bool,
pub is_writable: bool,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Instruction {
pub program_id: String,
pub accounts: Vec<AccountMeta>,
pub data: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SwapInstructions {
pub other_instructions: Option<Vec<Instruction>>,
pub compute_budget_instructions: Option<Vec<Instruction>>,
pub setup_instructions: Vec<Instruction>,
pub swap_instruction: Instruction,
pub cleanup_instruction: Option<Instruction>,
pub address_lookup_table_addresses: Vec<String>,
}