@shogun-sdk/intents-sdk 1.4.1 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shogun-sdk/intents-sdk",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "type": "module",
5
5
  "description": "Shogun Network Intent-based cross-chain swaps SDK",
6
6
  "author": "Shogun network",
@@ -48,6 +48,7 @@
48
48
  "@types/node": "22.13.10",
49
49
  "codama": "1.3.4",
50
50
  "tsup": "^8.5.0",
51
+ "tsx": "^4.22.4",
51
52
  "typescript": "5.8.2",
52
53
  "vitest": "^2.1.9"
53
54
  },
@@ -92,6 +93,7 @@
92
93
  "pre-publish": "pnpm node ./scripts/prePublish.js",
93
94
  "generate-solana-idl": "pnpm node ./scripts/codama.js",
94
95
  "test": "vitest run",
95
- "test:watch": "vitest"
96
+ "test:watch": "vitest",
97
+ "smoke:quote": "tsx scripts/net-quote-smoke.ts"
96
98
  }
97
99
  }
@@ -39,6 +39,8 @@ export type CreateCrossChainOrderParams = {
39
39
  deadline: number;
40
40
  /** Extra transfers to be made */
41
41
  extraTransfers?: ExtraTransfer[];
42
+ /** Slippage tolerance in bps for the auto-quote when min amounts are not provided. Defaults to DEFAULT_CROSS_CHAIN_SLIPPAGE_BPS (25) per swap leg. */
43
+ slippageBps?: number;
42
44
 
43
45
  /** Stop loss */
44
46
  stopLossType?: StopLossType;
@@ -168,12 +170,14 @@ export class CrossChainOrder {
168
170
 
169
171
  // TODO: Change this
170
172
  if (!minStablecoinAmount || !destinationTokenMinAmount) {
171
- const quote = await QuoteProvider.getQuote({
173
+ const quote = await QuoteProvider.getCrossChainNetQuote({
172
174
  sourceChainId: input.sourceChainId,
173
175
  tokenIn: input.sourceTokenAddress,
174
176
  amount: input.sourceTokenAmount,
175
177
  destChainId: input.destinationChainId,
176
178
  tokenOut: input.destinationTokenAddress,
179
+ extraTransfers: input.extraTransfers,
180
+ slippageBps: input.slippageBps,
177
181
  });
178
182
 
179
183
  return {
@@ -4,9 +4,6 @@ import type { ApiResponse } from '../../types/api.js';
4
4
  import type { SingleChainPreparedData, SingleChainUserIntentRequest } from '../../types/intent.js';
5
5
  import { SingleChainOrderValidator } from '../../utils/order-validator.js';
6
6
  import { QuoteProvider } from '../../utils/quote/aggregator.js';
7
- import { estimateAmountOut } from '../../utils/quote/pricing/estimate-amount-out.js';
8
- import { estimateExpensesInTokenOut } from '../../utils/quote/pricing/expenses.js';
9
- import { DEFAULT_COMMISSION_BPS, EVM_SINGLE_CHAIN_GAS_LIMIT, SINGLE_CHAIN_COMMISSION_DENOMINATOR } from '../../utils/quote/pricing/constants.js';
10
7
  import { getEVMSingleChainOrderTypedData } from '../evm/order-signature.js';
11
8
  import { BaseSDK } from '../sdk.js';
12
9
  import { getSolanaSingleChainOrderInstructions } from '../solana/order-instructions.js';
@@ -21,6 +18,8 @@ export type CreateSingleChainOrderParams = {
21
18
  destinationAddress: string;
22
19
  extraTransfers?: ExtraTransfer[];
23
20
  deadline: number;
21
+ /** Slippage tolerance in bps for the auto-quote when amountOutMin is not provided. Defaults to DEFAULT_SINGLE_CHAIN_SLIPPAGE_BPS (50). */
22
+ slippageBps?: number;
24
23
 
25
24
  stopLossType?: StopLossType;
26
25
  stopLossTriggerPrice?: string;
@@ -119,24 +118,15 @@ export class SingleChainOrder {
119
118
 
120
119
  switch (scenario) {
121
120
  case 'QUOTE_REQUIRED': {
122
- const [quote, expenses] = await Promise.all([
123
- QuoteProvider.getSingleChainQuote({
124
- tokenIn: input.tokenIn,
125
- amount: input.amountIn,
126
- chainId: input.chainId,
127
- tokenOut: input.tokenOut,
128
- }),
129
- estimateExpensesInTokenOut({
130
- chainId: input.chainId,
131
- tokenOut: input.tokenOut,
132
- gasLimit: EVM_SINGLE_CHAIN_GAS_LIMIT,
133
- getQuote: (p) => QuoteProvider.getSingleChainQuote(p),
134
- }),
135
- ]);
136
-
137
- // Worst-case output already carries provider slippage; never use the nominal amountOut.
138
- const swapOutput = quote.amountOutMin ?? quote.amountOut;
139
- return estimateAmountOut(swapOutput, DEFAULT_COMMISSION_BPS, expenses, SINGLE_CHAIN_COMMISSION_DENOMINATOR);
121
+ const { netAmountOut } = await QuoteProvider.getSingleChainNetQuote({
122
+ chainId: input.chainId,
123
+ amount: input.amountIn,
124
+ tokenIn: input.tokenIn,
125
+ tokenOut: input.tokenOut,
126
+ extraTransfers: input.extraTransfers,
127
+ slippageBps: input.slippageBps,
128
+ });
129
+ return netAmountOut;
140
130
  }
141
131
 
142
132
  case 'USE_PROVIDED_AMOUNT':
package/src/index.ts CHANGED
@@ -77,7 +77,16 @@ export { getSolanaDcaSingleChainOrderInstructions } from './core/solana/dca/crea
77
77
  export { signSingleChainDcaOrder } from './core/evm/single-chain-dca-order.js';
78
78
  export { signSingleChainLimitOrder } from './core/evm/single-chain-limit-order.js';
79
79
 
80
- export { QuoteProvider, type IntentsQuoteParams, type QuoteResponse } from './utils/quote/aggregator.js';
80
+ export {
81
+ QuoteProvider,
82
+ type IntentsQuoteParams,
83
+ type QuoteResponse,
84
+ type Quote,
85
+ type SingleChainNetQuote,
86
+ type SingleChainNetQuoteParams,
87
+ } from './utils/quote/aggregator.js';
88
+ export { IntentNotViableError, type Expenses } from './utils/quote/pricing/estimate-amount-out.js';
89
+ export { type ExtraTransferAmount } from './utils/quote/pricing/expenses.js';
81
90
 
82
91
  export { CROSS_CHAIN_GUARD_ADDRESSES, SINGLE_CHAIN_GUARD_ADDRESSES, PERMIT2_ADDRESS } from './constants.js';
83
92
 
@@ -10,9 +10,16 @@ import { RaydiumQuoteProvider, type RaydiumPumpQuoteParams, type RaydiumQuoteRes
10
10
  import { PumpFunQuoteProvider } from './pumpfun/index.js';
11
11
  import { RelayQuoteProvider } from './relay.js';
12
12
  import { convertDecimals } from './pricing/decimals.js';
13
- import { estimateAmountOut, IntentNotViableError } from './pricing/estimate-amount-out.js';
14
- import { estimateExpensesInTokenOut } from './pricing/expenses.js';
15
- import { DEFAULT_COMMISSION_BPS, CROSS_CHAIN_COMMISSION_DENOMINATOR, EVM_CROSS_CHAIN_GAS_LIMIT } from './pricing/constants.js';
13
+ import { estimateAmountOut, IntentNotViableError, type Expenses } from './pricing/estimate-amount-out.js';
14
+ import { estimateExpensesInTokenOut, type ExtraTransferAmount } from './pricing/expenses.js';
15
+ import { exactInLimitAmount } from './pricing/limit-amount.js';
16
+ import {
17
+ DEFAULT_COMMISSION_BPS,
18
+ CROSS_CHAIN_COMMISSION_DENOMINATOR,
19
+ SINGLE_CHAIN_COMMISSION_DENOMINATOR,
20
+ DEFAULT_SINGLE_CHAIN_SLIPPAGE_BPS,
21
+ DEFAULT_CROSS_CHAIN_SLIPPAGE_BPS,
22
+ } from './pricing/constants.js';
16
23
  import { compareAddresses } from './address.js';
17
24
 
18
25
  type SingleChainQuoteParams = {
@@ -51,6 +58,9 @@ export type IntentsQuoteParams = {
51
58
  amount: bigint;
52
59
  tokenIn: string;
53
60
  tokenOut: string;
61
+ extraTransfers?: ExtraTransferAmount[];
62
+ /** Slippage tolerance in basis points, applied to both swap legs. Defaults to DEFAULT_CROSS_CHAIN_SLIPPAGE_BPS (25). */
63
+ slippageBps?: number;
54
64
  };
55
65
 
56
66
  export type QuoteResponseInternal = {
@@ -79,29 +89,110 @@ export type QuoteResponse = QuoteResponseInternal & {
79
89
  };
80
90
  };
81
91
 
92
+ export type SingleChainNetQuoteParams = SingleChainQuoteParams & {
93
+ extraTransfers?: ExtraTransferAmount[];
94
+ protocolFeeInTokenOut?: bigint;
95
+ };
96
+
97
+ /** A single-chain quote plus the net amount the user receives after gas, extra transfers and commission. */
98
+ export type SingleChainNetQuote = {
99
+ quote: Quote;
100
+ netAmountOut: bigint;
101
+ expenses: Expenses;
102
+ };
103
+
82
104
  export class QuoteProvider {
105
+ /**
106
+ * Gross cross-chain quote: two nominal swap legs joined by the bridge stablecoin,
107
+ * with no solver expense model (no gas, commission or amount_limit haircuts).
108
+ * For the amount the solver will actually accept as amountOutMin, use
109
+ * getCrossChainNetQuote instead.
110
+ */
83
111
  public static async getQuote(params: IntentsQuoteParams): Promise<QuoteResponse> {
84
112
  try {
85
- const quote = await this.getQuoteFromRouters(params);
86
- // Adapt QuoteResponseInternal to QuoteResponse by adding tokenIn and tokenOut metadata fields
87
- return {
88
- ...quote,
89
- tokenIn: {
90
- address: params.tokenIn,
91
- chainId: params.sourceChainId,
92
- },
93
- tokenOut: {
94
- address: params.tokenOut,
95
- chainId: params.destChainId,
96
- },
97
- };
113
+ return this.adaptQuoteResponse(await this.getGrossQuoteFromRouters(params), params);
98
114
  } catch (e) {
99
- if (e instanceof IntentNotViableError) throw e;
100
115
  console.error('Error getting quote from routers:', e);
101
116
  throw new Error('Failed to get quote from routers');
102
117
  }
103
118
  }
104
119
 
120
+ /**
121
+ * Net cross-chain quote mirroring the solver's estimation exactly: swap legs credited
122
+ * at amount_limit, source/dest gas, commission and extra transfers subtracted from the
123
+ * bridged stablecoin amount. estimatedAmountOutReduced is the max amountOutMin the
124
+ * solver will accept; throws IntentNotViableError when expenses exceed a leg.
125
+ */
126
+ public static async getCrossChainNetQuote(params: IntentsQuoteParams): Promise<QuoteResponse> {
127
+ try {
128
+ return this.adaptQuoteResponse(await this.getNetQuoteFromRouters(params), params);
129
+ } catch (e) {
130
+ if (e instanceof IntentNotViableError) throw e;
131
+ console.error('Error getting net quote from routers:', e);
132
+ throw new Error('Failed to get net quote from routers');
133
+ }
134
+ }
135
+
136
+ /** Adds the tokenIn/tokenOut metadata fields QuoteResponse carries on top of the internal shape. */
137
+ private static adaptQuoteResponse(quote: QuoteResponseInternal, params: IntentsQuoteParams): QuoteResponse {
138
+ return {
139
+ ...quote,
140
+ tokenIn: {
141
+ address: params.tokenIn,
142
+ chainId: params.sourceChainId,
143
+ },
144
+ tokenOut: {
145
+ address: params.tokenOut,
146
+ chainId: params.destChainId,
147
+ },
148
+ };
149
+ }
150
+
151
+ /**
152
+ * The pre-solver-parity quote path: nominal source leg, decimal-normalized bridge,
153
+ * nominal dest leg. estimatedAmountOutReduced only reflects the dest provider's own
154
+ * amountOutMin when it reports one.
155
+ */
156
+ private static async getGrossQuoteFromRouters(params: IntentsQuoteParams): Promise<QuoteResponseInternal> {
157
+ const sourceStable = getStableForChain(params.sourceChainId);
158
+ const destStable = getStableForChain(params.destChainId);
159
+
160
+ // Step 1: quote tokenIn -> bridge stablecoin on the source chain
161
+ const sourceQuote = await this.getSingleChainQuote({
162
+ chainId: params.sourceChainId,
163
+ amount: params.amount,
164
+ tokenIn: params.tokenIn,
165
+ tokenOut: sourceStable.address,
166
+ slippageBps: params.slippageBps,
167
+ });
168
+
169
+ // Step 2: normalize decimals between source and destination stablecoins
170
+ const normalizedBridgeAmount = convertDecimals(sourceQuote.amountOut, sourceStable.decimals, destStable.decimals);
171
+
172
+ // Step 3: quote bridge stablecoin -> target token on the dest chain
173
+ const destQuote = await this.getSingleChainQuote({
174
+ chainId: params.destChainId,
175
+ amount: normalizedBridgeAmount,
176
+ tokenIn: destStable.address,
177
+ tokenOut: params.tokenOut,
178
+ slippageBps: params.slippageBps,
179
+ });
180
+
181
+ // Step 4: stablecoin min-amount from the source leg's USD value
182
+ const estimatedAmountInAsMinStablecoinAmount = BigInt(
183
+ Math.floor(sourceQuote.amountInUsd * 10 ** sourceStable.decimals),
184
+ );
185
+
186
+ return {
187
+ estimatedAmountOut: destQuote.amountOut,
188
+ estimatedAmountOutUsd: destQuote.amountOutUsd,
189
+ amountInUsd: sourceQuote.amountInUsd,
190
+ estimatedAmountInAsMinStablecoinAmount,
191
+ estimatedAmountOutReduced: destQuote.amountOutMin || destQuote.amountOut,
192
+ estimatedAmountOutUsdReduced: destQuote.amountOutUsd,
193
+ };
194
+ }
195
+
105
196
  /**
106
197
  * Get a quote for Solana with explicit provider selection
107
198
  * @param params - Quote parameters including provider preference
@@ -126,58 +217,77 @@ export class QuoteProvider {
126
217
  return this.getSingleChainQuote(singleChainParams);
127
218
  }
128
219
 
129
- private static async getQuoteFromRouters(params: IntentsQuoteParams): Promise<QuoteResponseInternal> {
220
+ private static async getNetQuoteFromRouters(params: IntentsQuoteParams): Promise<QuoteResponseInternal> {
130
221
  const sourceStable = getStableForChain(params.sourceChainId);
131
222
  const destStable = getStableForChain(params.destChainId);
132
-
133
- // Step 1: Get quote from tokenIn → bridge stable on source chain ---
134
- const sourceSingleChainQuoteParams: SingleChainQuoteParams = {
135
- chainId: params.sourceChainId,
136
- amount: params.amount,
137
- tokenIn: params.tokenIn,
138
- tokenOut: sourceStable.address,
139
- };
140
-
141
- const [sourceQuote, expenses] = await Promise.all([
142
- this.getSingleChainQuote(sourceSingleChainQuoteParams),
223
+ const getQuote = (p: SingleChainQuoteParams) => this.getSingleChainQuote(p);
224
+ const sourceIsStable = compareAddresses(params.tokenIn, sourceStable.address);
225
+ const destIsStable = compareAddresses(params.tokenOut, destStable.address);
226
+
227
+ // Steps 1-2 & 4 inputs are independent (source quote, source-leg gas, dest-leg gas + extra
228
+ // transfers depend only on params), so fetch them concurrently; only the dest swap must wait.
229
+ const [sourceQuote, sourceExpenses, destExpenses] = await Promise.all([
230
+ // Step 1: source quote tokenIn -> source stablecoin
231
+ this.getSingleChainQuote({
232
+ chainId: params.sourceChainId,
233
+ amount: params.amount,
234
+ tokenIn: params.tokenIn,
235
+ tokenOut: sourceStable.address,
236
+ slippageBps: params.slippageBps ?? DEFAULT_CROSS_CHAIN_SLIPPAGE_BPS,
237
+ }),
238
+ // Step 2 input: source-chain gas valued in the source stablecoin. A stablecoin
239
+ // token_in needs no source swap, so the solver charges start_without_swap gas.
240
+ estimateExpensesInTokenOut({
241
+ chainId: params.sourceChainId,
242
+ tokenOut: sourceStable.address,
243
+ getQuote,
244
+ flow: sourceIsStable ? 'crossChainSourceNoSwap' : 'crossChainSource',
245
+ }),
246
+ // Step 4 input: dest gas + extra transfers valued in the dest stablecoin
143
247
  estimateExpensesInTokenOut({
144
248
  chainId: params.destChainId,
145
- tokenOut: params.tokenOut,
146
- gasLimit: EVM_CROSS_CHAIN_GAS_LIMIT,
147
- getQuote: (p) => this.getSingleChainQuote(p),
249
+ tokenOut: destStable.address,
250
+ getQuote,
251
+ extraTransfers: params.extraTransfers,
252
+ flow: 'crossChainDest',
148
253
  }),
149
254
  ]);
150
255
 
151
- // Step 2: Normalize stable amount between source and destination ---
152
- // Adjust for decimal difference between stables (e.g. 6 18)
153
- const normalizedBridgeAmount = convertDecimals(
154
- sourceQuote.amountOut,
155
- sourceStable.decimals,
156
- destStable.decimals,
256
+ // Step 2: subtract source-chain gas from the source leg, crediting the swap at
257
+ // amount_limit like the solver (cross_chain_estimation.rs:155). When token_in is
258
+ // already the stablecoin the solver's router short-circuits 1:1 (SimpleTransfer,
259
+ // estimate.rs:141-157) with no slippage haircut.
260
+ // commission 0 here: the solver applies commission only on the destination leg
261
+ const sourceStableOut = sourceIsStable ? sourceQuote.amountOut : exactInLimitAmount(sourceQuote.amountOut);
262
+ const sourceStableAfterGas = estimateAmountOut(sourceStableOut, 0n, sourceExpenses);
263
+
264
+ // Step 3: normalize decimals between source and destination stablecoins
265
+ const normalizedBridgeAmount = convertDecimals(sourceStableAfterGas, sourceStable.decimals, destStable.decimals);
266
+
267
+ // Step 4: subtract commission + dest gas + extra transfers on the bridged stablecoin amount
268
+ const bridgedNet = estimateAmountOut(
269
+ normalizedBridgeAmount,
270
+ DEFAULT_COMMISSION_BPS,
271
+ destExpenses,
272
+ CROSS_CHAIN_COMMISSION_DENOMINATOR,
157
273
  );
158
274
 
159
- // Step 3: Get quote from dest bridge stable target token ---
160
- const destSingleChainQuoteParams: SingleChainQuoteParams = {
275
+ // Step 5: swap the net bridged stablecoin amount into the target token
276
+ const destQuote = await this.getSingleChainQuote({
161
277
  chainId: params.destChainId,
162
- amount: normalizedBridgeAmount,
278
+ amount: bridgedNet,
163
279
  tokenIn: destStable.address,
164
280
  tokenOut: params.tokenOut,
165
- };
166
-
167
- const destQuote = await this.getSingleChainQuote(destSingleChainQuoteParams);
281
+ slippageBps: params.slippageBps ?? DEFAULT_CROSS_CHAIN_SLIPPAGE_BPS,
282
+ });
168
283
 
169
- // Step 4: Compute stable min-amount with correct decimals ---
170
- const estimatedAmountInAsMinStablecoinAmount = BigInt(
171
- Math.floor(sourceQuote.amountInUsd * 10 ** sourceStable.decimals),
172
- );
284
+ // Step 6: stablecoin min-amount the solver accepts a --min-stablecoins floor only up
285
+ // to its own source-swap amount_limit (ws/helpers/mod.rs:32-51), so expose exactly that.
286
+ const estimatedAmountInAsMinStablecoinAmount = sourceStableOut;
173
287
 
174
- const swapOutput = destQuote.amountOutMin ?? destQuote.amountOut;
175
- const estimatedAmountOutReduced = estimateAmountOut(
176
- swapOutput,
177
- DEFAULT_COMMISSION_BPS,
178
- expenses,
179
- CROSS_CHAIN_COMMISSION_DENOMINATOR,
180
- );
288
+ // The solver's max deliverable is the dest swap's amount_limit (cross_chain_estimation.rs:243),
289
+ // 1:1 when token_out is the stablecoin itself (SimpleTransfer short-circuit).
290
+ const estimatedAmountOutReduced = destIsStable ? destQuote.amountOut : exactInLimitAmount(destQuote.amountOut);
181
291
 
182
292
  return {
183
293
  estimatedAmountOut: destQuote.amountOut,
@@ -270,6 +380,25 @@ export class QuoteProvider {
270
380
  throw new Error(`Unsupported chain for quote: ${params.chainId}`);
271
381
  }
272
382
 
383
+ public static async getSingleChainNetQuote(params: SingleChainNetQuoteParams): Promise<SingleChainNetQuote> {
384
+ const [quote, expenses] = await Promise.all([
385
+ this.getSingleChainQuote(params),
386
+ estimateExpensesInTokenOut({
387
+ chainId: params.chainId,
388
+ tokenOut: params.tokenOut,
389
+ getQuote: (p) => this.getSingleChainQuote(p),
390
+ extraTransfers: params.extraTransfers,
391
+ protocolFeeInTokenOut: params.protocolFeeInTokenOut,
392
+ flow: 'singleChain',
393
+ }),
394
+ ]);
395
+ // The solver credits the swap at amount_limit (nominal minus DEFAULT_SLIPPAGE, rounded
396
+ // up), regardless of any amountOutMin our provider reports — mirror that exactly.
397
+ const swapOutput = exactInLimitAmount(quote.amountOut);
398
+ const netAmountOut = estimateAmountOut(swapOutput, DEFAULT_COMMISSION_BPS, expenses, SINGLE_CHAIN_COMMISSION_DENOMINATOR);
399
+ return { quote, netAmountOut, expenses };
400
+ }
401
+
273
402
  static transformLiquidSwapQuote(liquidSwapQuote: LiquidSwapQuoteResponse): Quote {
274
403
  return {
275
404
  amountIn: liquidSwapQuote.execution.details.amountIn,
@@ -319,7 +448,7 @@ export class QuoteProvider {
319
448
  tokenIn: params.tokenIn,
320
449
  tokenOut: params.tokenOut,
321
450
  swapMode: 'ExactIn',
322
- slippageBps: params.slippageBps ? params.slippageBps : 20, // Default 0.2% slippage
451
+ slippageBps: params.slippageBps ? params.slippageBps : DEFAULT_SINGLE_CHAIN_SLIPPAGE_BPS,
323
452
  });
324
453
  }
325
454
 
@@ -360,7 +489,7 @@ export class QuoteProvider {
360
489
  inputMint: params.tokenIn,
361
490
  outputMint: params.tokenOut,
362
491
  amount: params.amount.toString(),
363
- slippageBps: params.slippageBps || 20, // Default 0.2% slippage
492
+ slippageBps: params.slippageBps || DEFAULT_SINGLE_CHAIN_SLIPPAGE_BPS,
364
493
  };
365
494
  try {
366
495
  return this.transformRaydiumQuote(await raydiumQuoter.getQuote(request));
@@ -377,7 +506,7 @@ export class QuoteProvider {
377
506
  inputMint: params.tokenIn,
378
507
  outputMint: params.tokenOut,
379
508
  amount: params.amount.toString(),
380
- slippageBps: params.slippageBps || 20, // Default 0.2% slippage
509
+ slippageBps: params.slippageBps || DEFAULT_SINGLE_CHAIN_SLIPPAGE_BPS,
381
510
  };
382
511
  return simpleQuoteToQuote(await pumpfunQuoter.getQuote(request), 'pumpfun');
383
512
  }
@@ -50,6 +50,13 @@ export class ParaswapQuoteProvider {
50
50
 
51
51
  private async getQuote(paraswapParams: ParaswapQuoteRequestParams): Promise<OptimalRate> {
52
52
  const params = { ...paraswapParams };
53
+ // Native sentinels (0xEee…/0x0…) aren't real contracts, so `decimals()` reverts.
54
+ // Normalize both sides to wrapped native before quoting — mirrors how the swap layer
55
+ // normalizes tokenIn, and covers internal callers (e.g. gas-expense quotes) that pass
56
+ // the native sentinel as srcToken.
57
+ if (isNativeEvmToken(params.srcToken)) {
58
+ params.srcToken = WRAPPED_ETH_ADDRESSES[this.chainId];
59
+ }
53
60
  if (isNativeEvmToken(params.destToken)) {
54
61
  params.destToken = WRAPPED_ETH_ADDRESSES[this.chainId];
55
62
  }
@@ -4,7 +4,20 @@ import { ChainID } from '../../../chains.js';
4
4
  * Solver commission in basis points. MUST equal the solver's COMMISSION env value
5
5
  * (solver/src/config.rs). Update both together to avoid quote divergence.
6
6
  */
7
- export const DEFAULT_COMMISSION_BPS = 50n; // 0.5% placeholder, confirm against solver COMMISSION
7
+ export const DEFAULT_COMMISSION_BPS = 0n; // mirrors solver production COMMISSION=0 (environment/production.yaml)
8
+
9
+ /**
10
+ * The solver's DEFAULT_SLIPPAGE=0.05% (environment/production.yaml) in bps. Applied via
11
+ * get_limit_amount (swap_estimator_rust/src/utils/limit_amount.rs): subtracted from swap
12
+ * outputs (ExactIn) and added to every expense valuation (ExactOut) — see limit-amount.ts.
13
+ */
14
+ export const SOLVER_DEFAULT_SLIPPAGE_BPS = 5n;
15
+
16
+ /** Default slippage tolerance (bps) for single-chain quotes when the caller does not provide one. */
17
+ export const DEFAULT_SINGLE_CHAIN_SLIPPAGE_BPS = 50;
18
+
19
+ /** Default slippage tolerance (bps) applied to each cross-chain swap leg when the caller does not provide one. */
20
+ export const DEFAULT_CROSS_CHAIN_SLIPPAGE_BPS = 25;
8
21
 
9
22
  /** Single-chain divisor — mirrors evaluate.rs:130-131 (`commission / 10_000`). */
10
23
  export const SINGLE_CHAIN_COMMISSION_DENOMINATOR = 10_000n;
@@ -19,15 +32,46 @@ export const CROSS_CHAIN_COMMISSION_DENOMINATOR = 100_000n;
19
32
  /** EVM fulfillment gas units — mirrors solver/src/constants/chains.rs (EVM single-chain). */
20
33
  export const EVM_SINGLE_CHAIN_GAS_LIMIT = 3_000_000n;
21
34
 
22
- /** EVM cross-chain fulfillment gas units — mirrors solver/src/constants/chains.rs (EVM cross-chain). */
35
+ /** EVM cross-chain fulfillment (DEST-leg) gas units — mirrors solver/src/constants/chains.rs (EVM cross-chain). */
23
36
  export const EVM_CROSS_CHAIN_GAS_LIMIT = 2_000_000n;
24
37
 
25
38
  /**
26
- * Fixed native-token gas cost for non-EVM chains, in the chain's smallest unit.
27
- * Mirrors solver/src/constants/chains.rs: Sui = 20_000_000 MIST; Solana base = 200_000 lamports
28
- * (single-receiver default; per-receiver ATA cost is not modeled at quote time).
39
+ * EVM cross-chain SOURCE-leg gas units mirrors solver chains.rs:
40
+ * start_order_execution_with_swap_cost (1_000_000) + claim_tokens_cost (220_000).
41
+ * The DEST-leg fulfill cost is EVM_CROSS_CHAIN_GAS_LIMIT above.
42
+ */
43
+ export const EVM_CROSS_CHAIN_SOURCE_GAS_LIMIT = 1_220_000n;
44
+
45
+ /**
46
+ * SOURCE-leg gas units when no source swap is needed (token_in already whitelisted /
47
+ * the stablecoin): start_order_execution_without_swap_cost (400_000) + claim (220_000).
48
+ */
49
+ export const EVM_CROSS_CHAIN_SOURCE_NO_SWAP_GAS_LIMIT = 620_000n;
50
+
51
+ /**
52
+ * Safety margin applied to the execution-price component of gas, on top of the
53
+ * solver-mirror math: the live EVM gas price (the solver evaluates with an eth_gasPrice
54
+ * cached for up to 10 minutes — observed +33% above our live reading on Base) and the
55
+ * Solana transaction/tip costs. Solana rent-exempt account creation (ATA, escrow PDA)
56
+ * is runtime-fixed and NOT padded. Over-provisioning keeps the signed amountOutMin
57
+ * below the solver's max when its gas figure drifts above ours.
58
+ */
59
+ export const GAS_PRICE_SAFETY_MARGIN_BPS = 2_000n; // +20%
60
+
61
+ /**
62
+ * Fixed native-token gas cost for non-EVM chains without a per-flow formula, in the
63
+ * chain's smallest unit. Sui mirrors the solver's single-chain flat 20_000_000 MIST
64
+ * (solver/src/constants/chains.rs); its cross-chain formula is smaller, so this
65
+ * overstates conservatively. Solana is computed per flow in gas.ts instead.
29
66
  */
30
67
  export const NON_EVM_FIXED_NATIVE_GAS_COST: Partial<Record<ChainID, bigint>> = {
31
68
  [ChainID.Sui]: 20_000_000n,
32
- [ChainID.Solana]: 200_000n,
33
69
  };
70
+
71
+ /** Solana cost components (lamports) — mirror solver chains.rs and constants/mod.rs (gas_price = 1). */
72
+ export const SOLANA_CREATE_ATA_COST = 2_050_000n;
73
+ export const SOLANA_CREATE_EMPTY_ACC_COST = 890_880n;
74
+ export const SOLANA_JITO_TIP = 500_000n; // solver DEFAULT_JITO_TIP
75
+ export const SOLANA_BASE_TX_COST = 200_000n;
76
+ export const SOLANA_CLAIM_TOKENS_COST = 300_000n;
77
+ export const SOLANA_START_WITHOUT_SWAP_COST = 300_000n; // start_order_execution_without_swap_cost