@shogun-sdk/intents-sdk 1.2.6-test.15 → 1.2.6-test.16

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.
Files changed (35) hide show
  1. package/dist/index.cjs +535 -484
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +121 -43
  4. package/dist/index.d.ts +121 -43
  5. package/dist/index.js +369 -318
  6. package/dist/index.js.map +1 -1
  7. package/package.json +10 -7
  8. package/src/auth/siwe.ts +9 -7
  9. package/src/constants.ts +32 -17
  10. package/src/core/evm/intent-helpers.ts +131 -8
  11. package/src/core/evm/intent-provider.ts +5 -4
  12. package/src/core/evm/permit2.ts +36 -0
  13. package/src/core/orders/api/fetch.ts +12 -36
  14. package/src/core/orders/api/index.ts +2 -3
  15. package/src/core/orders/cross-chain.ts +3 -0
  16. package/src/core/orders/single-chain.ts +21 -8
  17. package/src/core/sdk.ts +11 -2
  18. package/src/core/solana/dca/create-order.ts +6 -19
  19. package/src/core/solana/intent-helpers.ts +12 -20
  20. package/src/core/solana/order-instructions.ts +12 -37
  21. package/src/core/sui/intent-helpers.ts +4 -1
  22. package/src/index.ts +4 -7
  23. package/src/types/api.ts +14 -3
  24. package/src/types/intent.ts +36 -2
  25. package/src/utils/base-validator.ts +0 -4
  26. package/src/utils/generate-execution-details-hash.ts +9 -32
  27. package/src/utils/quote/address.ts +4 -0
  28. package/src/utils/quote/aggregator.ts +29 -9
  29. package/src/utils/quote/pricing/constants.ts +33 -0
  30. package/src/utils/quote/pricing/decimals.ts +11 -0
  31. package/src/utils/quote/pricing/estimate-amount-out.ts +36 -0
  32. package/src/utils/quote/pricing/expenses.ts +62 -0
  33. package/src/utils/quote/pricing/gas.ts +28 -0
  34. package/src/core/solana/transaction-options.ts +0 -83
  35. package/src/utils/auctioneer-headers.ts +0 -32
@@ -35,16 +35,14 @@ import {
35
35
  import { ChainID } from '../../../chains.js';
36
36
  import { getTransferSolInstruction } from '@solana-program/system';
37
37
  import { getCreateDcaOrderInstructionAsync } from '../generated/single-chain/index.js';
38
- import { resolveSolanaFeePayer, resolveSolanaTip, type SolanaTransactionOptions } from '../transaction-options.js';
39
38
 
40
39
  export async function getSolanaDcaSingleChainOrderInstructions(
41
40
  order: DcaSingleChainOrder,
42
- options?: SolanaTransactionOptions,
41
+ options?: { rpcUrl?: string },
43
42
  ): Promise<SolanaOrderInstructionResult & { secretNumber: string }> {
44
43
  const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC();
45
44
  const orderSigner = await generateKeyPairSigner();
46
- const userSigner = createNoopSigner(order.user as Address);
47
- const feePayerSigner = resolveSolanaFeePayer(userSigner, options?.feePayer);
45
+ const signer = createNoopSigner(order.user as Address);
48
46
 
49
47
  let tokenInMint = address(order.tokenIn);
50
48
 
@@ -76,7 +74,7 @@ export async function getSolanaDcaSingleChainOrderInstructions(
76
74
 
77
75
  if (!userTokenInAccount.exists) {
78
76
  const createAccountIx = await getCreateAssociatedTokenInstructionAsync({
79
- payer: feePayerSigner,
77
+ payer: signer,
80
78
  ata: tokenInProgramAccount,
81
79
  owner: orderUserAddress,
82
80
  mint: tokenMintProgram.address,
@@ -92,7 +90,7 @@ export async function getSolanaDcaSingleChainOrderInstructions(
92
90
  const transferIx = getTransferSolInstruction({
93
91
  amount: totalAmountIn,
94
92
  destination: userTokenInAccount.address,
95
- source: userSigner,
93
+ source: signer,
96
94
  });
97
95
 
98
96
  instructions.push(transferIx);
@@ -105,7 +103,7 @@ export async function getSolanaDcaSingleChainOrderInstructions(
105
103
  }
106
104
 
107
105
  const createDcaOrderIx = await getCreateDcaOrderInstructionAsync({
108
- user: userSigner,
106
+ user: signer,
109
107
  order: orderSigner,
110
108
  guard: guardAddress,
111
109
  tokenInMint: tokenInMint,
@@ -121,23 +119,12 @@ export async function getSolanaDcaSingleChainOrderInstructions(
121
119
  });
122
120
 
123
121
  instructions.push(createDcaOrderIx);
124
- const tip = resolveSolanaTip(options);
125
-
126
- if (tip) {
127
- instructions.push(
128
- getTransferSolInstruction({
129
- source: feePayerSigner,
130
- destination: tip.account,
131
- amount: tip.lamports,
132
- }),
133
- );
134
- }
135
122
 
136
123
  const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send();
137
124
 
138
125
  const txMessage = pipe(
139
126
  createTransactionMessage({ version: 0 }),
140
- (tx) => setTransactionMessageFeePayerSigner(feePayerSigner, tx),
127
+ (tx) => setTransactionMessageFeePayerSigner(signer, tx),
141
128
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
142
129
  (tx) => appendTransactionMessageInstructions(instructions, tx),
143
130
  (tx) => addSignersToTransactionMessage([orderSigner], tx),
@@ -10,7 +10,6 @@ import type { DcaSingleChainOrder } from '../orders/dca-single-chain.js';
10
10
  import type { ExtraTransfer, StopLossType } from '../orders/common.js';
11
11
  import { getSolanaCrossChainOrderInstructions, getSolanaSingleChainOrderInstructions } from './order-instructions.js';
12
12
  import { getSolanaDcaSingleChainOrderInstructions } from './dca/create-order.js';
13
- import type { SolanaTransactionOptions } from './transaction-options.js';
14
13
  import { prepareDcaSecretData } from './dca/single-chain-dca-order.js';
15
14
  import { prepareSecretData } from './dca/single-chain-limit-order.js';
16
15
  import { generateExecutionDetailsHash } from '../../utils/generate-execution-details-hash.js';
@@ -61,7 +60,8 @@ export type CreateSolanaCrossChainOrderIntentParams = {
61
60
  deadline: number;
62
61
  extraTransfers?: ExtraTransfer[];
63
62
  orderPubkey?: string;
64
- stopLossMaxOut?: bigint;
63
+ stopLossType?: StopLossType;
64
+ stopLossTriggerPrice?: string;
65
65
  takeProfitMinOut?: bigint;
66
66
  };
67
67
 
@@ -151,13 +151,14 @@ export function createSolanaCrossChainOrderIntentRequest(
151
151
  amountOutMin: params.destinationTokenMinAmount?.toString() || '1',
152
152
  destinationAddress: params.destinationAddress,
153
153
  extraTransfers: params.extraTransfers,
154
- stopLossMaxOut: params.stopLossMaxOut?.toString(),
154
+ stopLossType: params.stopLossType,
155
+ stopLossTriggerPrice: params.stopLossTriggerPrice,
155
156
  takeProfitMinOut: params.takeProfitMinOut?.toString(),
156
157
  };
157
158
  const executionDetailsString = JSON.stringify(executionDetails, Parsers.bigIntReplacer);
158
159
 
159
- // Calculate execution details hash
160
- const executionDetailsHash = generateExecutionDetailsHash(executionDetails);
160
+ // Hash the exact string we send; the auctioneer recomputes sha256 over it and rejects on mismatch
161
+ const executionDetailsHash = generateExecutionDetailsHash(executionDetailsString);
161
162
 
162
163
  const genericData = {
163
164
  user: params.user,
@@ -186,11 +187,8 @@ export function createSolanaCrossChainOrderIntentRequest(
186
187
  * @param order The single-chain order
187
188
  * @returns Promise resolving to instruction bytes
188
189
  */
189
- export async function generateSolanaSingleChainLimitOrderInstructions(
190
- order: SingleChainOrder,
191
- options?: SolanaTransactionOptions,
192
- ) {
193
- return getSolanaSingleChainOrderInstructions(order, options);
190
+ export async function generateSolanaSingleChainLimitOrderInstructions(order: SingleChainOrder) {
191
+ return getSolanaSingleChainOrderInstructions(order);
194
192
  }
195
193
 
196
194
  /**
@@ -198,11 +196,8 @@ export async function generateSolanaSingleChainLimitOrderInstructions(
198
196
  * @param order The DCA order
199
197
  * @returns Promise resolving to instruction bytes
200
198
  */
201
- export async function generateSolanaSingleChainDcaOrderInstructions(
202
- order: DcaSingleChainOrder,
203
- options?: SolanaTransactionOptions,
204
- ) {
205
- return getSolanaDcaSingleChainOrderInstructions(order, options);
199
+ export async function generateSolanaSingleChainDcaOrderInstructions(order: DcaSingleChainOrder) {
200
+ return getSolanaDcaSingleChainOrderInstructions(order);
206
201
  }
207
202
 
208
203
  /**
@@ -210,11 +205,8 @@ export async function generateSolanaSingleChainDcaOrderInstructions(
210
205
  * @param order The cross-chain order
211
206
  * @returns Promise resolving to instruction bytes
212
207
  */
213
- export async function generateSolanaCrossChainOrderInstructions(
214
- order: CrossChainOrder,
215
- options?: SolanaTransactionOptions,
216
- ) {
217
- return getSolanaCrossChainOrderInstructions(order, options);
208
+ export async function generateSolanaCrossChainOrderInstructions(order: CrossChainOrder) {
209
+ return getSolanaCrossChainOrderInstructions(order);
218
210
  }
219
211
 
220
212
  /**
@@ -38,7 +38,6 @@ import { getDefaultSolanaRPC } from './client.js';
38
38
  import { getCreateOrderInstruction } from './generated/cross-chain/index.js';
39
39
  import { getCreateLimitOrderInstructionAsync } from './generated/single-chain/index.js';
40
40
  import { genSecretHashAndNumber } from './utils.js';
41
- import { resolveSolanaFeePayer, resolveSolanaTip, type SolanaTransactionOptions } from './transaction-options.js';
42
41
 
43
42
  export type SolanaOrderInstructionResult = {
44
43
  // Order address
@@ -49,12 +48,11 @@ export type SolanaOrderInstructionResult = {
49
48
 
50
49
  export async function getSolanaSingleChainOrderInstructions(
51
50
  order: SingleChainOrder,
52
- options?: SolanaTransactionOptions,
51
+ options?: { rpcUrl?: string },
53
52
  ): Promise<SolanaOrderInstructionResult & { secretNumber: string }> {
54
53
  const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC();
55
54
  const orderSigner = await generateKeyPairSigner();
56
- const userSigner = createNoopSigner(order.user as Address);
57
- const feePayerSigner = resolveSolanaFeePayer(userSigner, options?.feePayer);
55
+ const signer = createNoopSigner(order.user as Address);
58
56
 
59
57
  let tokenInMint = address(order.tokenIn);
60
58
 
@@ -86,7 +84,7 @@ export async function getSolanaSingleChainOrderInstructions(
86
84
 
87
85
  if (!userTokenInAccount.exists) {
88
86
  const createAccountIx = await getCreateAssociatedTokenInstructionAsync({
89
- payer: feePayerSigner,
87
+ payer: signer,
90
88
  ata: tokenInProgramAccount,
91
89
  owner: orderUserAddress,
92
90
  mint: tokenMintProgram.address,
@@ -100,7 +98,7 @@ export async function getSolanaSingleChainOrderInstructions(
100
98
  const transferIx = getTransferSolInstruction({
101
99
  amount: order.amountIn,
102
100
  destination: userTokenInAccount.address,
103
- source: userSigner,
101
+ source: signer,
104
102
  });
105
103
 
106
104
  instructions.push(transferIx);
@@ -113,7 +111,7 @@ export async function getSolanaSingleChainOrderInstructions(
113
111
  }
114
112
 
115
113
  const createSingleChainLimitOrderIx = await getCreateLimitOrderInstructionAsync({
116
- user: userSigner,
114
+ user: signer,
117
115
  order: orderSigner,
118
116
  guard: guardAddress,
119
117
  tokenInMint: tokenInMint,
@@ -127,23 +125,12 @@ export async function getSolanaSingleChainOrderInstructions(
127
125
  });
128
126
 
129
127
  instructions.push(createSingleChainLimitOrderIx);
130
- const tip = resolveSolanaTip(options);
131
-
132
- if (tip) {
133
- instructions.push(
134
- getTransferSolInstruction({
135
- source: feePayerSigner,
136
- destination: tip.account,
137
- amount: tip.lamports,
138
- }),
139
- );
140
- }
141
128
 
142
129
  const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send();
143
130
 
144
131
  const txMessage = pipe(
145
132
  createTransactionMessage({ version: 0 }),
146
- (tx) => setTransactionMessageFeePayerSigner(feePayerSigner, tx),
133
+ (tx) => setTransactionMessageFeePayerSigner(signer, tx),
147
134
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
148
135
  (tx) => appendTransactionMessageInstructions(instructions, tx),
149
136
  (tx) => addSignersToTransactionMessage([orderSigner], tx),
@@ -162,12 +149,11 @@ export async function getSolanaSingleChainOrderInstructions(
162
149
 
163
150
  export async function getSolanaCrossChainOrderInstructions(
164
151
  order: CrossChainOrder,
165
- options?: SolanaTransactionOptions,
152
+ options?: { rpcUrl?: string },
166
153
  ): Promise<SolanaOrderInstructionResult> {
167
154
  const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC();
168
155
  const orderSigner = await generateKeyPairSigner();
169
- const userSigner = createNoopSigner(order.user as Address);
170
- const feePayerSigner = resolveSolanaFeePayer(userSigner, options?.feePayer);
156
+ const signer = createNoopSigner(order.user as Address);
171
157
 
172
158
  let tokenInMint = address(order.sourceTokenAddress);
173
159
 
@@ -214,7 +200,7 @@ export async function getSolanaCrossChainOrderInstructions(
214
200
 
215
201
  if (!userTokenInAccount.exists) {
216
202
  const createAccountIx = await getCreateAssociatedTokenInstructionAsync({
217
- payer: feePayerSigner,
203
+ payer: signer,
218
204
  ata: tokenInProgramAccount,
219
205
  owner: orderUserAddress,
220
206
  mint: tokenMintProgram.address,
@@ -225,7 +211,7 @@ export async function getSolanaCrossChainOrderInstructions(
225
211
  }
226
212
 
227
213
  const transferIx = getTransferSolInstruction({
228
- source: userSigner,
214
+ source: signer,
229
215
  destination: userTokenInAccount.address,
230
216
  amount: order.sourceTokenAmount,
231
217
  });
@@ -241,7 +227,7 @@ export async function getSolanaCrossChainOrderInstructions(
241
227
  const executionHashUint8Array = order.executionDetailsHashToBytes();
242
228
 
243
229
  const createOrderIx = getCreateOrderInstruction({
244
- user: userSigner,
230
+ user: signer,
245
231
  order: orderSigner,
246
232
  guard: address(CROSS_CHAIN_GUARD_ADDRESSES[ChainID.Solana]),
247
233
  systemProgram: SYSTEM_PROGRAM_ADDRESS,
@@ -257,23 +243,12 @@ export async function getSolanaCrossChainOrderInstructions(
257
243
  });
258
244
 
259
245
  instructions.push(createOrderIx);
260
- const tip = resolveSolanaTip(options);
261
-
262
- if (tip) {
263
- instructions.push(
264
- getTransferSolInstruction({
265
- source: feePayerSigner,
266
- destination: tip.account,
267
- amount: tip.lamports,
268
- }),
269
- );
270
- }
271
246
 
272
247
  const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send();
273
248
 
274
249
  const txMessage = pipe(
275
250
  createTransactionMessage({ version: 0 }),
276
- (tx) => setTransactionMessageFeePayerSigner(feePayerSigner, tx),
251
+ (tx) => setTransactionMessageFeePayerSigner(signer, tx),
277
252
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
278
253
  (tx) => appendTransactionMessageInstructions(instructions, tx),
279
254
  (tx) => addSignersToTransactionMessage([orderSigner], tx),
@@ -84,6 +84,8 @@ export function createSuiSingleChainLimitOrderIntentRequest(
84
84
  deadline: params.deadline,
85
85
 
86
86
  takeProfitMinOut: params.takeProfitMinOut,
87
+ stopLossType: params.stopLossType,
88
+ stopLossTriggerPrice: params.stopLossTriggerPrice,
87
89
  } as SingleChainOrder;
88
90
 
89
91
  return {
@@ -153,7 +155,8 @@ export function createSuiCrossChainOrderIntentRequest(
153
155
  };
154
156
  const executionDetailsString = JSON.stringify(executionDetails, Parsers.bigIntReplacer);
155
157
 
156
- const executionDetailsHash = generateExecutionDetailsHash(executionDetails);
158
+ // Hash the exact string we send; the auctioneer recomputes sha256 over it and rejects on mismatch
159
+ const executionDetailsHash = generateExecutionDetailsHash(executionDetailsString);
157
160
 
158
161
  const genericData = {
159
162
  user: params.user,
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ export { CrossChainOrder } from './core/orders/cross-chain.js';
11
11
  export type { CreateCrossChainOrderParams } from './core/orders/cross-chain.js';
12
12
  export { SingleChainOrder } from './core/orders/single-chain.js';
13
13
  export type { CreateSingleChainOrderParams } from './core/orders/single-chain.js';
14
+ export type { StopLossType } from './core/orders/common.js';
14
15
 
15
16
  export { fetchUserOrders } from './core/orders/api/fetch.js';
16
17
  export { AuctioneerAPI } from './core/orders/api/index.js';
@@ -28,12 +29,6 @@ export {
28
29
  } from './types/api.js';
29
30
  export { calculateAmounts, getCoinFromResponse, getTokensData } from './utils/defillama.js';
30
31
  export type { DefiLlamaCoinData, DefiLlamaTokensResponse } from './utils/defillama.js';
31
- export {
32
- DEV_ACCESS_HEADER,
33
- DEV_ACCESS_STORAGE_KEY,
34
- getDevAccessHeaderValue,
35
- withAuctioneerHeaders,
36
- } from './utils/auctioneer-headers.js';
37
32
  export {
38
33
  getCancelCrossChainOrderRawData,
39
34
  getCancelSingleChainOrderRawData,
@@ -51,7 +46,6 @@ export {
51
46
  getSolanaSingleChainOrderInstructions,
52
47
  type SolanaOrderInstructionResult,
53
48
  } from './core/solana/order-instructions.js';
54
- export { type SolanaTransactionOptions } from './core/solana/transaction-options.js';
55
49
  export {
56
50
  cancelCrossChainOrderInstructionsAsBytes,
57
51
  cancelSingleChainOrderInstructionsAsBytes,
@@ -99,13 +93,16 @@ export {
99
93
  createEvmSingleChainLimitOrderIntentRequest,
100
94
  createEvmSingleChainDcaOrderIntentRequest,
101
95
  createEvmCrossChainOrderIntentRequest,
96
+ createEvmCrossChainDcaOrderIntentRequest,
102
97
  generateEvmSingleChainLimitOrderTypedData,
103
98
  generateEvmSingleChainDcaOrderTypedData,
104
99
  generateEvmCrossChainOrderTypedData,
100
+ getEVMCrossChainDcaOrderTypedData,
105
101
  generateEvmRandomNonce,
106
102
  type CreateEvmSingleChainLimitOrderIntentParams,
107
103
  type CreateEvmSingleChainDcaOrderIntentParams,
108
104
  type CreateEvmCrossChainOrderIntentParams,
105
+ type CreateEvmCrossChainDcaOrderIntentParams,
109
106
  } from './core/evm/intent-helpers.js';
110
107
 
111
108
  // Solana Intent Helpers
package/src/types/api.ts CHANGED
@@ -1,6 +1,13 @@
1
1
  import type { ExtraTransfer } from '../core/orders/common.js';
2
2
 
3
- export type ChainOrderStatus = 'Auction' | 'NoBids' | 'Executing' | 'Fulfilled' | 'Cancelled' | 'Outdated';
3
+ export type ChainOrderStatus =
4
+ | 'Auction'
5
+ | 'NoBids'
6
+ | 'Executing'
7
+ | 'Fulfilled'
8
+ | 'Cancelled'
9
+ | 'Outdated'
10
+ | 'DcaIntervalFulfilled';
4
11
 
5
12
  export type ApiCrossChainOrder = {
6
13
  orderId: string;
@@ -61,8 +68,12 @@ export interface ApiIntentResponse {
61
68
  /** Unique identifier for the created intent */
62
69
  intentId: string;
63
70
 
64
- /** JWT token used for authorization or subsequent requests */
65
- jwt: string;
71
+ /**
72
+ * JWT token used for authorization or subsequent requests.
73
+ * Optional: order-creation responses only return `intentId`; obtain a JWT via the
74
+ * SIWE flow (`EVMSDK.authenticate()` / `fetchJWTToken`) instead.
75
+ */
76
+ jwt?: string;
66
77
  }
67
78
  export type ValidResponseData = ApiUserOrders | string | ApiIntentResponse | null;
68
79
 
@@ -1,6 +1,6 @@
1
1
  import type { SupportedChain } from '../chains.js';
2
2
  import type { CrossChainOrder } from '../core/orders/cross-chain.js';
3
- import type { ExtraTransfer } from '../core/orders/common.js';
3
+ import type { ExtraTransfer, StopLossType } from '../core/orders/common.js';
4
4
  import type { SingleChainOrder } from '../core/orders/single-chain.js';
5
5
  import type { DcaSingleChainOrder } from '../core/orders/dca-single-chain.js';
6
6
 
@@ -20,7 +20,11 @@ export type ExecutionDetails = {
20
20
  /** Extra transfers to be made */
21
21
  extraTransfers?: ExtraTransfer[];
22
22
 
23
- stopLossMaxOut?: bigint;
23
+ /** Stop loss type, must match the auctioneer's `stopLossType` field */
24
+ stopLossType?: StopLossType;
25
+ /** Initial trigger price (tokenIn/tokenOut) that arms the stop loss */
26
+ stopLossTriggerPrice?: string;
27
+ /** Take profit minimum amount out */
24
28
  takeProfitMinOut?: bigint;
25
29
  };
26
30
 
@@ -96,6 +100,36 @@ export type SingleChainUserIntentRequest = {
96
100
  };
97
101
  };
98
102
 
103
+ /**
104
+ * Source chain data for a cross-chain DCA order. Mirrors the auctioneer's
105
+ * `CrossChainDcaOrderGenericRequestData` (common cross-chain fields + flattened
106
+ * `CommonDcaOrderData`). The destination fields live in `executionDetails`.
107
+ */
108
+ export type DcaCrossChainSourceChainData = {
109
+ user: string;
110
+ srcChainId: SupportedChain;
111
+ tokenIn: string;
112
+ minStablecoinsAmount: bigint;
113
+ deadline: number;
114
+ executionDetailsHash: ExecutionDetailsHash;
115
+ /** Flattened CommonDcaOrderData */
116
+ startTime: number;
117
+ amountInPerInterval: bigint;
118
+ totalIntervals: number;
119
+ intervalDuration: number;
120
+ };
121
+
122
+ export type DcaCrossChainUserIntentRequest = {
123
+ genericData: DcaCrossChainSourceChainData;
124
+ /** JSON-stringified execution details for the destination chain */
125
+ executionDetails: string;
126
+ chainSpecificData: {
127
+ EVM?: DcaSingleChainEVMPreparedData;
128
+ Solana?: DcaSingleChainSolanaPreparedData;
129
+ Sui?: DcaSingleChainSuiPreparedData;
130
+ };
131
+ };
132
+
99
133
  export type ChainPreparedData = EvmPreparedData | SuiPreparedData | SolanaPreparedData;
100
134
 
101
135
  export type CrossChainOrderPrepared = {
@@ -32,10 +32,6 @@ export abstract class BaseValidator implements IChainValidator {
32
32
  this.validateAmount(order.amountIn);
33
33
  this.validateAmount(order.amountOutMin ?? 1n);
34
34
 
35
- if (order.stopLossMaxOut) {
36
- this.validateAmount(order.stopLossMaxOut);
37
- }
38
-
39
35
  if (order.takeProfitMinOut) {
40
36
  this.validateAmount(order.takeProfitMinOut);
41
37
  }
@@ -1,36 +1,13 @@
1
1
  import { createHash } from 'crypto';
2
- import type { ExtraTransfer } from '../core/orders/common.js';
3
- import { Parsers } from './parsers.js';
4
-
5
- export function generateExecutionDetailsHash({
6
- destChainId,
7
- destinationAddress,
8
- tokenOut,
9
- amountOutMin,
10
- stopLossMaxOut,
11
- takeProfitMinOut,
12
- extraTransfers,
13
- }: {
14
- destChainId: number;
15
- destinationAddress: string;
16
- tokenOut: string;
17
- amountOutMin: string;
18
- stopLossMaxOut?: string;
19
- takeProfitMinOut?: string;
20
- extraTransfers?: ExtraTransfer[];
21
- }): string {
22
- const executionDetails = JSON.stringify(
23
- {
24
- destChainId,
25
- destinationAddress,
26
- tokenOut,
27
- amountOutMin,
28
- stopLossMaxOut,
29
- takeProfitMinOut,
30
- extraTransfers,
31
- },
32
- Parsers.bigIntReplacer,
33
- );
34
2
 
3
+ /**
4
+ * Computes the SHA-256 hash the auctioneer expects for cross-chain execution details.
5
+ *
6
+ * The auctioneer recomputes sha256 over the exact `executionDetails` JSON string it
7
+ * receives and rejects the order when it differs (hash mismatch). Callers therefore MUST
8
+ * hash the same serialized string they send on the wire — pass that string here directly
9
+ * instead of re-serializing a parallel object, which is how key-order/field drift creeps in.
10
+ */
11
+ export function generateExecutionDetailsHash(executionDetails: string): string {
35
12
  return '0x' + createHash('sha256').update(executionDetails).digest('hex');
36
13
  }
@@ -0,0 +1,4 @@
1
+ /** Case-insensitive address equality; false if either address is missing. */
2
+ export function compareAddresses(firstAddress?: string, secondAddress?: string): boolean {
3
+ return !!firstAddress && !!secondAddress && firstAddress.toLowerCase() === secondAddress.toLowerCase();
4
+ }
@@ -9,10 +9,11 @@ import { LiquidSwapQuoteProvider, type LiquidSwapQuoteResponse } from './liquids
9
9
  import { RaydiumQuoteProvider, type RaydiumPumpQuoteParams, type RaydiumQuoteResponse } from './raydium.js';
10
10
  import { PumpFunQuoteProvider } from './pumpfun/index.js';
11
11
  import { RelayQuoteProvider } from './relay.js';
12
-
13
- const compareAddresses = (firstAddress?: string, secondAddress?: string): boolean => {
14
- return !!firstAddress && !!secondAddress && firstAddress.toLowerCase() === secondAddress.toLowerCase();
15
- };
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';
16
+ import { compareAddresses } from './address.js';
16
17
 
17
18
  type SingleChainQuoteParams = {
18
19
  chainId: ChainID;
@@ -95,6 +96,7 @@ export class QuoteProvider {
95
96
  },
96
97
  };
97
98
  } catch (e) {
99
+ if (e instanceof IntentNotViableError) throw e;
98
100
  console.error('Error getting quote from routers:', e);
99
101
  throw new Error('Failed to get quote from routers');
100
102
  }
@@ -136,12 +138,22 @@ export class QuoteProvider {
136
138
  tokenOut: sourceStable.address,
137
139
  };
138
140
 
139
- const sourceQuote = await this.getSingleChainQuote(sourceSingleChainQuoteParams);
141
+ const [sourceQuote, expenses] = await Promise.all([
142
+ this.getSingleChainQuote(sourceSingleChainQuoteParams),
143
+ estimateExpensesInTokenOut({
144
+ chainId: params.destChainId,
145
+ tokenOut: params.tokenOut,
146
+ gasLimit: EVM_CROSS_CHAIN_GAS_LIMIT,
147
+ getQuote: (p) => this.getSingleChainQuote(p),
148
+ }),
149
+ ]);
140
150
 
141
151
  // Step 2: Normalize stable amount between source and destination ---
142
152
  // Adjust for decimal difference between stables (e.g. 6 → 18)
143
- const normalizedBridgeAmount = BigInt(
144
- Math.floor((Number(sourceQuote.amountOut) / 10 ** sourceStable.decimals) * 10 ** destStable.decimals),
153
+ const normalizedBridgeAmount = convertDecimals(
154
+ sourceQuote.amountOut,
155
+ sourceStable.decimals,
156
+ destStable.decimals,
145
157
  );
146
158
 
147
159
  // Step 3: Get quote from dest bridge stable → target token ---
@@ -154,17 +166,25 @@ export class QuoteProvider {
154
166
 
155
167
  const destQuote = await this.getSingleChainQuote(destSingleChainQuoteParams);
156
168
 
157
- // -Step 4: Compute stable min-amount with correct decimals ---
169
+ // Step 4: Compute stable min-amount with correct decimals ---
158
170
  const estimatedAmountInAsMinStablecoinAmount = BigInt(
159
171
  Math.floor(sourceQuote.amountInUsd * 10 ** sourceStable.decimals),
160
172
  );
161
173
 
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
+ );
181
+
162
182
  return {
163
183
  estimatedAmountOut: destQuote.amountOut,
164
184
  estimatedAmountOutUsd: destQuote.amountOutUsd,
165
185
  amountInUsd: sourceQuote.amountInUsd,
166
186
  estimatedAmountInAsMinStablecoinAmount,
167
- estimatedAmountOutReduced: destQuote.amountOutMin || destQuote.amountOut,
187
+ estimatedAmountOutReduced,
168
188
  estimatedAmountOutUsdReduced: destQuote.amountOutUsd,
169
189
  };
170
190
  }
@@ -0,0 +1,33 @@
1
+ import { ChainID } from '../../../chains.js';
2
+
3
+ /**
4
+ * Solver commission in basis points. MUST equal the solver's COMMISSION env value
5
+ * (solver/src/config.rs). Update both together to avoid quote divergence.
6
+ */
7
+ export const DEFAULT_COMMISSION_BPS = 50n; // 0.5% — placeholder, confirm against solver COMMISSION
8
+
9
+ /** Single-chain divisor — mirrors evaluate.rs:130-131 (`commission / 10_000`). */
10
+ export const SINGLE_CHAIN_COMMISSION_DENOMINATOR = 10_000n;
11
+
12
+ /**
13
+ * Cross-chain divisor — mirrors cross_chain_estimation.rs:213-214 (`commission / 100_000`).
14
+ * The asymmetry with the single-chain divisor is intentional (verbatim solver mirror);
15
+ * confirm with the solver team whether this is a unit choice or a bug before relying on it.
16
+ */
17
+ export const CROSS_CHAIN_COMMISSION_DENOMINATOR = 100_000n;
18
+
19
+ /** EVM fulfillment gas units — mirrors solver/src/constants/chains.rs (EVM single-chain). */
20
+ export const EVM_SINGLE_CHAIN_GAS_LIMIT = 3_000_000n;
21
+
22
+ /** EVM cross-chain fulfillment gas units — mirrors solver/src/constants/chains.rs (EVM cross-chain). */
23
+ export const EVM_CROSS_CHAIN_GAS_LIMIT = 2_000_000n;
24
+
25
+ /**
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).
29
+ */
30
+ export const NON_EVM_FIXED_NATIVE_GAS_COST: Partial<Record<ChainID, bigint>> = {
31
+ [ChainID.Sui]: 20_000_000n,
32
+ [ChainID.Solana]: 200_000n,
33
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Integer decimal conversion between two tokens, mirroring solver cross_chain_estimation.rs:205-211.
3
+ * Uses BigInt powers of ten to avoid the float precision loss of `Math.floor(Number(...))`.
4
+ */
5
+ export function convertDecimals(amount: bigint, fromDecimals: number, toDecimals: number): bigint {
6
+ const diff = toDecimals - fromDecimals;
7
+ if (diff >= 0) {
8
+ return amount * 10n ** BigInt(diff);
9
+ }
10
+ return amount / 10n ** BigInt(-diff);
11
+ }
@@ -0,0 +1,36 @@
1
+ /** Expense components, all expressed in the output token's smallest units. */
2
+ export type Expenses = {
3
+ gasInTokenOut: bigint;
4
+ protocolFeeInTokenOut: bigint;
5
+ extraTransfersInTokenOut: bigint;
6
+ };
7
+
8
+ /** Thrown when total expenses meet or exceed the swap output, mirroring the solver's rejection. */
9
+ export class IntentNotViableError extends Error {
10
+ constructor(message: string) {
11
+ super(message);
12
+ this.name = 'IntentNotViableError';
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Ports solver_rust evaluate.rs: final amount = swapOutput − expenses − commission.
18
+ * `commissionBps` is basis points; `commissionDenominator` selects single-chain (10_000n)
19
+ * vs cross-chain (100_000n) semantics. Throws IntentNotViableError when nothing is left.
20
+ */
21
+ export function estimateAmountOut(
22
+ swapOutput: bigint,
23
+ commissionBps: bigint,
24
+ expenses: Expenses,
25
+ commissionDenominator = 10_000n,
26
+ ): bigint {
27
+ const commissionFee = (swapOutput * commissionBps) / commissionDenominator;
28
+ const total =
29
+ expenses.gasInTokenOut + expenses.protocolFeeInTokenOut + expenses.extraTransfersInTokenOut + commissionFee;
30
+
31
+ if (total >= swapOutput) {
32
+ throw new IntentNotViableError(`Total expenses (${total}) meet or exceed swap output (${swapOutput})`);
33
+ }
34
+
35
+ return swapOutput - total;
36
+ }