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

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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shogun-sdk/intents-sdk",
3
- "version": "1.2.6-test.15",
3
+ "version": "1.2.6-test.17",
4
4
  "type": "module",
5
5
  "description": "Shogun Network Intent-based cross-chain swaps SDK",
6
6
  "author": "Shogun network",
@@ -47,8 +47,9 @@
47
47
  "@types/bn.js": "5.2.0",
48
48
  "@types/node": "22.13.10",
49
49
  "codama": "1.3.4",
50
- "tsup": "^8.5.1",
51
- "typescript": "5.8.2"
50
+ "tsup": "^8.5.0",
51
+ "typescript": "5.8.2",
52
+ "vitest": "^2.1.9"
52
53
  },
53
54
  "dependencies": {
54
55
  "@codama/visitors-core": "^1.3.7",
@@ -57,7 +58,7 @@
57
58
  "@mysten/bcs": "1.8.0",
58
59
  "@mysten/sui": "1.29.1",
59
60
  "@paraswap/sdk": "7.3.1",
60
- "@raydium-io/raydium-sdk-v2": "0.2.32-alpha",
61
+ "@raydium-io/raydium-sdk-v2": "0.2.30-alpha",
61
62
  "@solana-program/system": "0.8.0",
62
63
  "@solana-program/token": "0.6.0",
63
64
  "@solana/accounts": "3.0.3",
@@ -72,9 +73,9 @@
72
73
  "@solana/transaction-confirmation": "3.0.2",
73
74
  "@solana/transactions": "3.0.2",
74
75
  "@solana/web3.js": "1.98.4",
75
- "@uniswap/permit2-sdk": "1.4.0",
76
+ "@uniswap/permit2-sdk": "1.3.0",
76
77
  "aftermath-ts-sdk": "1.3.14",
77
- "bn.js": "5.2.3",
78
+ "bn.js": "5.2.2",
78
79
  "dayjs": "1.11.13",
79
80
  "decimal.js": "^10.4.3",
80
81
  "ethers": "6.13.5",
@@ -89,6 +90,8 @@
89
90
  "lint": "npx eslint --fix --ext .ts ./src",
90
91
  "format": "prettier --write ./src",
91
92
  "pre-publish": "pnpm node ./scripts/prePublish.js",
92
- "generate-solana-idl": "pnpm node ./scripts/codama.js"
93
+ "generate-solana-idl": "pnpm node ./scripts/codama.js",
94
+ "test": "vitest run",
95
+ "test:watch": "vitest"
93
96
  }
94
97
  }
package/src/auth/siwe.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { ChainID } from '../chains.js';
2
2
  import { AUCTIONEER_URL } from '../constants.js';
3
3
  import type { ApiResponse } from '../types/api.js';
4
- import { withAuctioneerHeaders } from '../utils/auctioneer-headers.js';
5
4
 
6
5
  export type FetchSiweMessageParams = {
7
6
  wallet: string;
@@ -15,9 +14,7 @@ export type FetchJWTParams = {
15
14
 
16
15
  export async function fetchSiweMessage(params: FetchSiweMessageParams) {
17
16
  const url = `${AUCTIONEER_URL}/siwe?wallet=${params.wallet}&chainId=${params.chainId}`;
18
- const response = await fetch(url, {
19
- headers: withAuctioneerHeaders(),
20
- });
17
+ const response = await fetch(url);
21
18
  if (!response.ok) {
22
19
  throw new Error(`Failed to fetch SIWE message: ${response.status} ${response.statusText}`);
23
20
  }
@@ -27,12 +24,17 @@ export async function fetchSiweMessage(params: FetchSiweMessageParams) {
27
24
 
28
25
  export async function fetchJWTToken(params: FetchJWTParams, token?: string) {
29
26
  const url = `${AUCTIONEER_URL}/siwe`;
27
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' };
28
+ // Only forward an existing token when present; the server merges the newly verified
29
+ // wallet into it. Sending `Bearer undefined` would be a no-op the server discards.
30
+ if (token) {
31
+ headers.Authorization = `Bearer ${token}`;
32
+ }
33
+
30
34
  const response = await fetch(url, {
31
35
  method: 'POST',
32
36
  body: JSON.stringify(params),
33
- headers: withAuctioneerHeaders({
34
- Authorization: `Bearer ${token}`,
35
- }),
37
+ headers,
36
38
  });
37
39
 
38
40
  if (!response.ok) {
package/src/constants.ts CHANGED
@@ -79,7 +79,9 @@ export const PROD_DCA_SINGLE_CHAIN_GUARD_ADDRESSES: Record<SupportedEvmChain, Ev
79
79
  [ChainID.Arbitrum]: '0x0000000000000000000000000000000000000000' as EvmAddress,
80
80
  [ChainID.Optimism]: '0x1b754840f058b78e412156d2674b5954728cb86e' as EvmAddress,
81
81
  [ChainID.Base]: '0x2df3ab1bb8b9d48769718003de2d8ca8e9d3c465' as EvmAddress,
82
- [ChainID.Hyperliquid]: '0x618ee9b6829261108d811b91581e00da192b0b75' as EvmAddress,
82
+ // HyperEVM SingleChainGuardDca per the on-chain deployment + auctioneer config.
83
+ // (0x618ee9… is the SingleChainGuardLimit, which previously sat here by mistake.)
84
+ [ChainID.Hyperliquid]: '0xdcbb658910b5192a43b79fd8244bdf27940d3b6b' as EvmAddress,
83
85
  [ChainID.BSC]: '0x11ef880e2e8d17c114b9eb87369ce3d1311c62e1' as EvmAddress,
84
86
  [ChainID.MONAD]: '0x418302C3bef14D405A9B30381a9cb9d4d82EB2d4' as EvmAddress,
85
87
  };
@@ -116,23 +118,14 @@ export const DCA_SINGLE_CHAIN_GUARD_ADDRESSES = useProdConfig
116
118
  ? PROD_DCA_SINGLE_CHAIN_GUARD_ADDRESSES
117
119
  : TEST_DCA_SINGLE_CHAIN_GUARD_ADDRESSES;
118
120
 
121
+ // No separate test deployment is configured for cross-chain DCA guards yet; prod values
122
+ // match the auctioneer's `cross_chain.guard_dca` config and are used in both modes.
123
+ export const DCA_CROSS_CHAIN_GUARD_ADDRESSES = PROD_DCA_CROSS_CHAIN_GUARD_ADDRESSES;
124
+
119
125
  export const NATIVE_SOLANA_TOKEN_ADDRESS =
120
126
  'So11111111111111111111111111111111111111111' as SolanaAddress<'So11111111111111111111111111111111111111111'>;
121
127
  export const WRAPPED_SOL_MINT_ADDRESS =
122
128
  'So11111111111111111111111111111111111111112' as SolanaAddress<'So11111111111111111111111111111111111111112'>;
123
- export const SOLANA_TIP_ACCOUNTS = [
124
- '4ACfpUFoaSD9bfPdeu6DBt89gB6ENTeHBXCAi87NhDEE',
125
- 'D2L6yPZ2FmmmTKPgzaMKdhu6EWZcTpLy1Vhx8uvZe7NZ',
126
- '9bnz4RShgq1hAnLnZbP8kbgBg1kEmcJBYQq3gQbmnSta',
127
- '5VY91ws6B2hMmBFRsXkoAAdsPHBJwRfBht4DXox3xkwn',
128
- '2nyhqdwKcJZR2vcqCyrYsaPVdAnFoJjiksCXJ7hfEYgD',
129
- '2q5pghRs6arqVjRvT5gfgWfWcHWmw1ZuCzphgd5KfWGJ',
130
- 'wyvPkWjVZz1M8fHQnMMCDTQDbkManefNNhweYk5WkcF',
131
- '3KCKozbAaF75qEU33jtzozcJ29yJuaLJTy2jFdzUY8bT',
132
- '4vieeGHPYPG2MmyPRcYjdiDmmhN3ww7hsFNap8pVN3Ey',
133
- '4TQLFNWK8AovT1gFvda5jfw2oJeRMKEmw7aH6MGBJ3or',
134
- ] as const;
135
- export const SOLANA_MIN_TIP_LAMPORTS = 200_000n;
136
129
 
137
130
  type SolanaMint = {
138
131
  name: string;
@@ -158,9 +151,15 @@ export const SUI_GUARD_COLLATERAL_TYPE =
158
151
  export const SUI_GUARD_STABLECOIN_TYPE =
159
152
  '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';
160
153
 
161
- export const AUCTIONEER_URL = useProdConfig
162
- ? 'https://auctioneer-825534211396.us-central1.run.app'
163
- : 'http://127.0.0.1:8080';
154
+ // `INTENTS_AUCTIONEER_URL` overrides the default endpoint (e.g. to target a locally
155
+ // running auctioneer) without touching the committed `useProdConfig` flag. Guarded so it
156
+ // is a no-op in browser bundles where `process` is undefined.
157
+ const auctioneerUrlOverride =
158
+ typeof process !== 'undefined' ? process.env?.INTENTS_AUCTIONEER_URL : undefined;
159
+
160
+ export const AUCTIONEER_URL =
161
+ auctioneerUrlOverride ||
162
+ (useProdConfig ? 'https://auctioneer-825534211396.us-central1.run.app' : 'http://127.0.0.1:8080');
164
163
 
165
164
  export const MAX_UINT_32 = 2n ** 32n - 1n;
166
165
  export const MAX_UINT_64 = 2n ** 64n - 1n;
@@ -189,4 +188,20 @@ export function isNativeEvmToken(tokenAddress: string): boolean {
189
188
  return NATIVE_EVM_ETH_ADDRESSES.some((addr) => addr.toLowerCase() === normalizedAddress);
190
189
  }
191
190
 
191
+ export const EVM_ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as EvmAddress;
192
+
193
+ /** Placeholder signature (32 zero bytes) for unsigned/validate-only intent requests. */
194
+ export const EVM_ZERO_SIGNATURE = '0x0000000000000000000000000000000000000000000000000000000000000000';
195
+
196
+ /**
197
+ * Output-token addresses must be normalized to the zero address before signing the EIP-712
198
+ * witness: the auctioneer collapses any native sentinel (`0xeee…`/`0x0…`) to `Address::default()`
199
+ * when it reconstructs `requestedOutput`/`extraTransfers` to recover the signer. Signing the raw
200
+ * sentinel instead would yield a different hash → signature recovery fails. (`tokenIn` is NOT
201
+ * normalized on either side, so leave it untouched.)
202
+ */
203
+ export function normalizeNativeEvmTokenForSigning(tokenAddress: string): string {
204
+ return isNativeEvmToken(tokenAddress) ? EVM_ZERO_ADDRESS : tokenAddress;
205
+ }
206
+
192
207
  export const TOKEN_SEARCH_API_BASE_URL = 'https://shogun-token-search-api-825534211396.europe-west4.run.app';
@@ -1,8 +1,10 @@
1
+ import type { Address, Hex, TypedDataDefinition } from 'viem';
1
2
  import type { SupportedEvmChain } from '../../chains.js';
2
3
  import type {
3
4
  SingleChainUserIntentRequest,
4
5
  CrossChainUserIntentRequest,
5
6
  DcaSingleChainUserIntentRequest,
7
+ DcaCrossChainUserIntentRequest,
6
8
  } from '../../types/intent.js';
7
9
  import type { SingleChainOrder } from '../orders/single-chain.js';
8
10
  import type { CrossChainOrder } from '../orders/cross-chain.js';
@@ -13,6 +15,13 @@ import {
13
15
  getEVMSingleChainOrderTypedData,
14
16
  getEVMDcaSingleChainOrderTypedData,
15
17
  } from './order-signature.js';
18
+ import {
19
+ CROSS_CHAIN_DCA_PERMIT2_TYPES,
20
+ PERMIT2_DOMAIN,
21
+ type CrossChainDcaPermitTransferFrom,
22
+ } from './permit2.js';
23
+ import { EVMIntentProvider } from './intent-provider.js';
24
+ import { DCA_CROSS_CHAIN_GUARD_ADDRESSES, EVM_ZERO_SIGNATURE } from '../../constants.js';
16
25
  import { generateExecutionDetailsHash } from '../../utils/generate-execution-details-hash.js';
17
26
  import { Parsers } from '../../utils/parsers.js';
18
27
 
@@ -97,7 +106,7 @@ export function createEvmSingleChainLimitOrderIntentRequest(
97
106
  chainSpecificData: {
98
107
  EVM: {
99
108
  nonce: params.nonce || String(Math.floor(Math.random() * 10000000)),
100
- signature: params.signature || '0x0000000000000000000000000000000000000000000000000000000000000000',
109
+ signature: params.signature || EVM_ZERO_SIGNATURE,
101
110
  },
102
111
  },
103
112
  };
@@ -131,7 +140,7 @@ export function createEvmSingleChainDcaOrderIntentRequest(
131
140
  chainSpecificData: {
132
141
  EVM: {
133
142
  nonce: params.nonce || String(Math.floor(Math.random() * 10000000)),
134
- signature: params.signature || '0x0000000000000000000000000000000000000000000000000000000000000000',
143
+ signature: params.signature || EVM_ZERO_SIGNATURE,
135
144
  },
136
145
  },
137
146
  };
@@ -157,8 +166,8 @@ export function createEvmCrossChainOrderIntentRequest(
157
166
  };
158
167
  const executionDetailsString = JSON.stringify(executionDetails, Parsers.bigIntReplacer);
159
168
 
160
- // Calculate execution details hash
161
- const executionDetailsHash = generateExecutionDetailsHash(executionDetails);
169
+ // Hash the exact string we send; the auctioneer recomputes sha256 over it and rejects on mismatch
170
+ const executionDetailsHash = generateExecutionDetailsHash(executionDetailsString);
162
171
 
163
172
  const genericData = {
164
173
  user: params.user,
@@ -169,9 +178,6 @@ export function createEvmCrossChainOrderIntentRequest(
169
178
  deadline: params.deadline,
170
179
  executionDetailsHash: executionDetailsHash as `0x${string}`,
171
180
  extraTransfers: params.extraTransfers,
172
- takeProfitMinOut: params.takeProfitMinOut,
173
- stopLossType: params.stopLossType,
174
- stopLossTriggerPrice: params.stopLossTriggerPrice,
175
181
  };
176
182
 
177
183
  return {
@@ -180,7 +186,7 @@ export function createEvmCrossChainOrderIntentRequest(
180
186
  chainSpecificData: {
181
187
  EVM: {
182
188
  nonce: params.nonce || String(Math.floor(Math.random() * 10000000)),
183
- signature: params.signature || '0x0000000000000000000000000000000000000000000000000000000000000000',
189
+ signature: params.signature || EVM_ZERO_SIGNATURE,
184
190
  },
185
191
  },
186
192
  };
@@ -220,3 +226,120 @@ export async function generateEvmCrossChainOrderTypedData(order: CrossChainOrder
220
226
  export function generateEvmRandomNonce(): string {
221
227
  return String(Math.floor(Math.random() * 10000000));
222
228
  }
229
+
230
+ export type CreateEvmCrossChainDcaOrderIntentParams = {
231
+ user: `0x${string}`;
232
+ sourceChainId: SupportedEvmChain;
233
+ sourceTokenAddress: string;
234
+ destinationChainId: number;
235
+ destinationTokenAddress: string;
236
+ destinationTokenMinAmount?: bigint;
237
+ destinationAddress: string;
238
+ minStablecoinAmount?: bigint;
239
+ deadline: number;
240
+ startTime: number;
241
+ amountInPerInterval: bigint;
242
+ totalIntervals: number;
243
+ intervalDuration: number;
244
+ extraTransfers?: ExtraTransfer[];
245
+ nonce?: string;
246
+ signature?: string;
247
+ };
248
+
249
+ /**
250
+ * Builds the cross-chain DCA execution-details string and its SHA-256 hash.
251
+ * Both the wire payload and the EIP-712 witness must reference the SAME string/hash, so this
252
+ * is the single source of truth — the auctioneer recomputes sha256 over the string it receives.
253
+ */
254
+ function buildCrossChainDcaExecutionDetails(params: CreateEvmCrossChainDcaOrderIntentParams): {
255
+ executionDetailsString: string;
256
+ executionDetailsHash: string;
257
+ } {
258
+ const executionDetails = {
259
+ destChainId: params.destinationChainId,
260
+ tokenOut: params.destinationTokenAddress,
261
+ amountOutMin: params.destinationTokenMinAmount?.toString() || '1',
262
+ destinationAddress: params.destinationAddress,
263
+ extraTransfers: params.extraTransfers,
264
+ };
265
+ const executionDetailsString = JSON.stringify(executionDetails, Parsers.bigIntReplacer);
266
+ return { executionDetailsString, executionDetailsHash: generateExecutionDetailsHash(executionDetailsString) };
267
+ }
268
+
269
+ /**
270
+ * Creates an EVM cross-chain DCA order intent request, ready to POST to
271
+ * `/user_intent/cross_chain/dca_order`. Pass `nonce`/`signature` from
272
+ * {@link getEVMCrossChainDcaOrderTypedData} for an executable order.
273
+ */
274
+ export function createEvmCrossChainDcaOrderIntentRequest(
275
+ params: CreateEvmCrossChainDcaOrderIntentParams,
276
+ ): DcaCrossChainUserIntentRequest {
277
+ const { executionDetailsString, executionDetailsHash } = buildCrossChainDcaExecutionDetails(params);
278
+
279
+ return {
280
+ genericData: {
281
+ user: params.user,
282
+ srcChainId: params.sourceChainId,
283
+ tokenIn: params.sourceTokenAddress,
284
+ minStablecoinsAmount: params.minStablecoinAmount || 1n,
285
+ deadline: params.deadline,
286
+ executionDetailsHash: executionDetailsHash as `0x${string}`,
287
+ startTime: params.startTime,
288
+ amountInPerInterval: params.amountInPerInterval,
289
+ totalIntervals: params.totalIntervals,
290
+ intervalDuration: params.intervalDuration,
291
+ },
292
+ executionDetails: executionDetailsString,
293
+ chainSpecificData: {
294
+ EVM: {
295
+ nonce: params.nonce || generateEvmRandomNonce(),
296
+ signature: params.signature || EVM_ZERO_SIGNATURE,
297
+ },
298
+ },
299
+ };
300
+ }
301
+
302
+ /**
303
+ * Generates the EIP-712 typed data (and nonce) for a cross-chain DCA order's Permit2 witness.
304
+ * Sign `orderTypedData`, then pass the resulting `nonce`/`signature` into
305
+ * {@link createEvmCrossChainDcaOrderIntentRequest} — both share the same execution-details hash.
306
+ */
307
+ export function getEVMCrossChainDcaOrderTypedData(
308
+ params: CreateEvmCrossChainDcaOrderIntentParams,
309
+ providedNonce?: bigint,
310
+ ): { orderTypedData: TypedDataDefinition; nonce: bigint } {
311
+ const { executionDetailsHash } = buildCrossChainDcaExecutionDetails(params);
312
+ const nonce = providedNonce ?? EVMIntentProvider.getRandomNonce();
313
+ const totalAmountIn = params.amountInPerInterval * BigInt(params.totalIntervals);
314
+ const spender = DCA_CROSS_CHAIN_GUARD_ADDRESSES[params.sourceChainId] as Address;
315
+
316
+ const message: CrossChainDcaPermitTransferFrom = {
317
+ permitted: { token: params.sourceTokenAddress as Address, amount: totalAmountIn },
318
+ spender,
319
+ nonce,
320
+ deadline: BigInt(params.deadline),
321
+ witness: {
322
+ user: params.user as Address,
323
+ tokenIn: params.sourceTokenAddress as Address,
324
+ srcChainId: params.sourceChainId,
325
+ startTime: params.startTime,
326
+ deadline: params.deadline,
327
+ totalIntervals: params.totalIntervals,
328
+ intervalDuration: params.intervalDuration,
329
+ amountInPerInterval: params.amountInPerInterval,
330
+ minStablecoinsAmountPerInterval: params.minStablecoinAmount || 1n,
331
+ executionDetailsHash: executionDetailsHash as Hex,
332
+ nonce,
333
+ },
334
+ };
335
+
336
+ return {
337
+ orderTypedData: {
338
+ domain: PERMIT2_DOMAIN(params.sourceChainId),
339
+ types: CROSS_CHAIN_DCA_PERMIT2_TYPES,
340
+ primaryType: 'PermitWitnessTransferFrom',
341
+ message,
342
+ },
343
+ nonce,
344
+ };
345
+ }
@@ -6,6 +6,7 @@ import {
6
6
  CROSS_CHAIN_GUARD_ADDRESSES,
7
7
  MAX_UINT_32,
8
8
  DCA_SINGLE_CHAIN_GUARD_ADDRESSES,
9
+ normalizeNativeEvmTokenForSigning,
9
10
  } from '../../constants.js';
10
11
  import {
11
12
  getEVMCrossChainOrderTypedData,
@@ -103,14 +104,14 @@ export class EVMIntentProvider {
103
104
  const spender = SINGLE_CHAIN_GUARD_ADDRESSES[order.chainId as SupportedEvmChain] as Address;
104
105
 
105
106
  const requestedOutput: TransferData = {
106
- token: order.tokenOut as Address,
107
+ token: normalizeNativeEvmTokenForSigning(order.tokenOut) as Address,
107
108
  receiver: order.destinationAddress as Address,
108
109
  amount: order.amountOutMin,
109
110
  };
110
111
 
111
112
  const extraTransfers: TransferData[] =
112
113
  order.extraTransfers?.map((transfer) => ({
113
- token: transfer.token as Address,
114
+ token: normalizeNativeEvmTokenForSigning(transfer.token) as Address,
114
115
  receiver: transfer.receiver as Address,
115
116
  amount: transfer.amount,
116
117
  })) || [];
@@ -166,14 +167,14 @@ export class EVMIntentProvider {
166
167
  const spender = DCA_SINGLE_CHAIN_GUARD_ADDRESSES[order.chainId as SupportedEvmChain] as Address;
167
168
 
168
169
  const requestedOutput: TransferData = {
169
- token: order.tokenOut as Address,
170
+ token: normalizeNativeEvmTokenForSigning(order.tokenOut) as Address,
170
171
  receiver: order.destinationAddress as Address,
171
172
  amount: BigInt(order.amountOutMin),
172
173
  };
173
174
 
174
175
  const extraTransfers: TransferData[] =
175
176
  order.extraTransfers?.map((transfer) => ({
176
- token: transfer.token as Address,
177
+ token: normalizeNativeEvmTokenForSigning(transfer.token) as Address,
177
178
  receiver: transfer.receiver as Address,
178
179
  amount: transfer.amount,
179
180
  })) || [];
@@ -174,3 +174,39 @@ export const DCA_SINGLE_CHAIN_PERMIT2_TYPES = {
174
174
  { name: 'witness', type: 'SingleChainDcaOrder' },
175
175
  ],
176
176
  } as const;
177
+
178
+ export type CrossChainDcaPermitTransferFrom = {
179
+ permitted: TokenPermissions;
180
+ spender: Address;
181
+ nonce: bigint;
182
+ deadline: bigint;
183
+ witness: DcaOrderInfo;
184
+ };
185
+
186
+ // Cross-chain DCA witness — mirrors the auctioneer's
187
+ // `DcaOrderInfo(address user,address tokenIn,uint32 srcChainId,uint32 startTime,uint32 deadline,
188
+ // uint24 totalIntervals,uint32 intervalDuration,uint128 amountInPerInterval,
189
+ // uint128 minStablecoinsAmountPerInterval,bytes32 executionDetailsHash,uint256 nonce)`.
190
+ export const CROSS_CHAIN_DCA_PERMIT2_TYPES = {
191
+ TokenPermissions: TOKEN_PERMISSIONS_TYPE,
192
+ DcaOrderInfo: [
193
+ { name: 'user', type: 'address' },
194
+ { name: 'tokenIn', type: 'address' },
195
+ { name: 'srcChainId', type: 'uint32' },
196
+ { name: 'startTime', type: 'uint32' },
197
+ { name: 'deadline', type: 'uint32' },
198
+ { name: 'totalIntervals', type: 'uint24' },
199
+ { name: 'intervalDuration', type: 'uint32' },
200
+ { name: 'amountInPerInterval', type: 'uint128' },
201
+ { name: 'minStablecoinsAmountPerInterval', type: 'uint128' },
202
+ { name: 'executionDetailsHash', type: 'bytes32' },
203
+ { name: 'nonce', type: 'uint256' },
204
+ ],
205
+ PermitWitnessTransferFrom: [
206
+ { name: 'permitted', type: 'TokenPermissions' },
207
+ { name: 'spender', type: 'address' },
208
+ { name: 'nonce', type: 'uint256' },
209
+ { name: 'deadline', type: 'uint256' },
210
+ { name: 'witness', type: 'DcaOrderInfo' },
211
+ ],
212
+ } as const;
@@ -1,42 +1,18 @@
1
- import { AUCTIONEER_URL } from '../../../constants.js';
2
- import type { ApiResponse, ApiUserOrders } from '../../../types/api.js';
3
- import { withAuctioneerHeaders } from '../../../utils/auctioneer-headers.js';
1
+ import { AuctioneerAPI } from './index.js';
2
+ import type { ApiUserOrders } from '../../../types/api.js';
4
3
 
5
4
  /**
6
- * Fetches user orders by any combination of wallet addresses.
7
- * Builds a query string with evmWallets, solanaWallets, and suiWallets.
5
+ * Fetches the orders owned by the wallets encoded in a SIWE-issued JWT.
6
+ *
7
+ * The auctioneer derives the queried wallets from the verified JWT claims, never from
8
+ * caller-supplied params — a caller can only read intents for wallets it proved control
9
+ * of via SIWE. Obtain the token with `EVMSDK.authenticate()` (or the `fetchSiweMessage` +
10
+ * `fetchJWTToken` pair) and pass it here.
8
11
  */
9
- export async function fetchUserOrders(
10
- evmAddress?: string | null,
11
- solAddress?: string | null,
12
- suiAddress?: string | null
13
- ): Promise<ApiUserOrders> {
14
- const params = new URLSearchParams();
15
-
16
- if (evmAddress) params.append('evmWallets', evmAddress);
17
- if (solAddress) params.append('solanaWallets', solAddress);
18
- if (suiAddress) params.append('suiWallets', suiAddress);
19
-
20
- if ([evmAddress, solAddress, suiAddress].every((a) => !a)) {
21
- throw new Error('At least one wallet address is required');
22
- }
23
-
24
- const url = `${AUCTIONEER_URL}/user_intent?${params.toString()}`;
25
-
26
- const response = await fetch(url, {
27
- method: 'GET',
28
- headers: withAuctioneerHeaders({ 'Content-Type': 'application/json' }),
29
- });
30
-
31
- if (!response.ok) {
32
- throw new Error(`Failed to fetch user orders: ${response.status} ${response.statusText}`);
33
- }
34
-
35
- const data: ApiResponse<ApiUserOrders> = await response.json();
36
-
37
- if (!data.success) {
38
- throw new Error(`Failed to fetch user orders: ${data.error}`);
12
+ export async function fetchUserOrders(aggregatedToken: string): Promise<ApiUserOrders> {
13
+ if (!aggregatedToken) {
14
+ throw new Error('A SIWE-issued JWT is required to fetch user orders');
39
15
  }
40
16
 
41
- return data.data!;
17
+ return new AuctioneerAPI(aggregatedToken).getUserOrders();
42
18
  }
@@ -1,6 +1,5 @@
1
1
  import { AUCTIONEER_URL } from '../../../constants.js';
2
2
  import type { ApiResponse, ApiUserOrders } from '../../../types/api.js';
3
- import { withAuctioneerHeaders } from '../../../utils/auctioneer-headers.js';
4
3
 
5
4
  export class AuctioneerAPI {
6
5
  public aggregatedToken: string;
@@ -17,10 +16,10 @@ export class AuctioneerAPI {
17
16
  const url = `${AUCTIONEER_URL}/user_intent`;
18
17
  const response = await fetch(url, {
19
18
  method: 'GET',
20
- headers: withAuctioneerHeaders({
19
+ headers: {
21
20
  'Content-Type': 'application/json',
22
21
  Authorization: `Bearer ${this.aggregatedToken}`,
23
- }),
22
+ },
24
23
  });
25
24
 
26
25
  const data: ApiResponse<ApiUserOrders> = await response.json();
@@ -200,6 +200,9 @@ export class CrossChainOrder {
200
200
  destinationAddress: this.destinationAddress,
201
201
  amountOutMin: this.destinationTokenMinAmount,
202
202
  extraTransfers: this.extraTransfers,
203
+ stopLossType: this.stopLossType,
204
+ stopLossTriggerPrice: this.stopLossTriggerPrice,
205
+ takeProfitMinOut: this.takeProfitMinOut,
203
206
  };
204
207
  }
205
208
 
@@ -4,6 +4,9 @@ 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';
7
10
  import { getEVMSingleChainOrderTypedData } from '../evm/order-signature.js';
8
11
  import { BaseSDK } from '../sdk.js';
9
12
  import { getSolanaSingleChainOrderInstructions } from '../solana/order-instructions.js';
@@ -116,14 +119,24 @@ export class SingleChainOrder {
116
119
 
117
120
  switch (scenario) {
118
121
  case 'QUOTE_REQUIRED': {
119
- const quote = await QuoteProvider.getSingleChainQuote({
120
- tokenIn: input.tokenIn,
121
- amount: input.amountIn,
122
- chainId: input.chainId,
123
- tokenOut: input.tokenOut,
124
- });
125
- // TODO: Change this. Maybe we can use token prices api and subtract gas fees (0.5$)
126
- return (quote.amountOut * 93n) / 100n; // Add 7% reduced fixed amount to cover fees
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);
127
140
  }
128
141
 
129
142
  case 'USE_PROVIDED_AMOUNT':
package/src/core/sdk.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { AUCTIONEER_URL } from '../constants.js';
2
2
  import { NetworkError } from '../errors/index.js';
3
3
  import type { ApiResponse } from '../types/api.js';
4
- import { withAuctioneerHeaders } from '../utils/auctioneer-headers.js';
5
4
  import type {
6
5
  CrossChainOrderPrepared,
7
6
  CrossChainUserIntentRequest,
7
+ DcaCrossChainUserIntentRequest,
8
8
  DcaSingleChainOrderPrepared,
9
9
  DcaSingleChainUserIntentRequest,
10
10
  SingleChainOrderPrepared,
@@ -29,7 +29,7 @@ export abstract class BaseSDK {
29
29
  private static async makeRequest(url: string, body: string): Promise<ApiResponse> {
30
30
  const response = await fetch(url, {
31
31
  method: 'POST',
32
- headers: withAuctioneerHeaders({ 'Content-Type': 'application/json' }),
32
+ headers: { 'Content-Type': 'application/json' },
33
33
  body,
34
34
  });
35
35
 
@@ -123,4 +123,13 @@ export abstract class BaseSDK {
123
123
  public static async validateDcaSingleChainOrder(intentRequest: DcaSingleChainUserIntentRequest): Promise<void> {
124
124
  return BaseSDK.validateOrder(intentRequest, '/validate_intent/single_chain/dca_order');
125
125
  }
126
+
127
+ public static async validateDcaCrossChainOrder(intentRequest: DcaCrossChainUserIntentRequest): Promise<void> {
128
+ return BaseSDK.validateOrder(intentRequest, '/validate_intent/cross_chain/dca_order');
129
+ }
130
+
131
+ public static async sendDcaCrossChainOrder(intentRequest: DcaCrossChainUserIntentRequest): Promise<ApiResponse> {
132
+ const body = JSON.stringify(intentRequest, Parsers.bigIntReplacer);
133
+ return BaseSDK.makeRequest(`${AUCTIONEER_URL}/user_intent/cross_chain/dca_order`, body);
134
+ }
126
135
  }