@shogun-sdk/intents-sdk 1.2.6-test.14 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shogun-sdk/intents-sdk",
3
- "version": "1.2.6-test.14",
3
+ "version": "1.2.6-test.16",
4
4
  "type": "module",
5
5
  "description": "Shogun Network Intent-based cross-chain swaps SDK",
6
6
  "author": "Shogun network",
@@ -48,7 +48,8 @@
48
48
  "@types/node": "22.13.10",
49
49
  "codama": "1.3.4",
50
50
  "tsup": "^8.5.0",
51
- "typescript": "5.8.2"
51
+ "typescript": "5.8.2",
52
+ "vitest": "^2.1.9"
52
53
  },
53
54
  "dependencies": {
54
55
  "@codama/visitors-core": "^1.3.7",
@@ -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,5 +1,5 @@
1
1
  import type { ChainID } from '../chains.js';
2
- import { AUCTIONEER_URL, DEV_ACCESS_KEY } from '../constants.js';
2
+ import { AUCTIONEER_URL } from '../constants.js';
3
3
  import type { ApiResponse } from '../types/api.js';
4
4
 
5
5
  export type FetchSiweMessageParams = {
@@ -14,13 +14,7 @@ export type FetchJWTParams = {
14
14
 
15
15
  export async function fetchSiweMessage(params: FetchSiweMessageParams) {
16
16
  const url = `${AUCTIONEER_URL}/siwe?wallet=${params.wallet}&chainId=${params.chainId}`;
17
- const response = await fetch(url, {
18
- headers: {
19
- ...(DEV_ACCESS_KEY
20
- ? { "x-dev-access": DEV_ACCESS_KEY }
21
- : {})
22
- }
23
- });
17
+ const response = await fetch(url);
24
18
  if (!response.ok) {
25
19
  throw new Error(`Failed to fetch SIWE message: ${response.status} ${response.statusText}`);
26
20
  }
@@ -30,12 +24,17 @@ export async function fetchSiweMessage(params: FetchSiweMessageParams) {
30
24
 
31
25
  export async function fetchJWTToken(params: FetchJWTParams, token?: string) {
32
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
+
33
34
  const response = await fetch(url, {
34
35
  method: 'POST',
35
36
  body: JSON.stringify(params),
36
- headers: {
37
- Authorization: `Bearer ${token}`,
38
- },
37
+ headers,
39
38
  });
40
39
 
41
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,6 +118,10 @@ 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 =
@@ -145,9 +151,15 @@ export const SUI_GUARD_COLLATERAL_TYPE =
145
151
  export const SUI_GUARD_STABLECOIN_TYPE =
146
152
  '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';
147
153
 
148
- export const AUCTIONEER_URL = useProdConfig
149
- ? 'https://auctioneer-825534211396.us-central1.run.app'
150
- : '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');
151
163
 
152
164
  export const MAX_UINT_32 = 2n ** 32n - 1n;
153
165
  export const MAX_UINT_64 = 2n ** 64n - 1n;
@@ -176,8 +188,20 @@ export function isNativeEvmToken(tokenAddress: string): boolean {
176
188
  return NATIVE_EVM_ETH_ADDRESSES.some((addr) => addr.toLowerCase() === normalizedAddress);
177
189
  }
178
190
 
179
- export const TOKEN_SEARCH_API_BASE_URL = 'https://shogun-token-search-api-825534211396.europe-west4.run.app';
191
+ export const EVM_ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as EvmAddress;
180
192
 
181
- export const DEV_ACCESS_KEY = typeof window !== "undefined"
182
- ? localStorage.getItem("x-dev-access")
183
- : null;
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
+
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,45 +1,18 @@
1
- import { AUCTIONEER_URL, DEV_ACCESS_KEY } from '../../../constants.js';
2
- import type { ApiResponse, ApiUserOrders } from '../../../types/api.js';
1
+ import { AuctioneerAPI } from './index.js';
2
+ import type { ApiUserOrders } from '../../../types/api.js';
3
3
 
4
4
  /**
5
- * Fetches user orders by any combination of wallet addresses.
6
- * 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.
7
11
  */
8
- export async function fetchUserOrders(
9
- evmAddress?: string | null,
10
- solAddress?: string | null,
11
- suiAddress?: string | null
12
- ): Promise<ApiUserOrders> {
13
- const params = new URLSearchParams();
14
-
15
- if (evmAddress) params.append('evmWallets', evmAddress);
16
- if (solAddress) params.append('solanaWallets', solAddress);
17
- if (suiAddress) params.append('suiWallets', suiAddress);
18
-
19
- if ([evmAddress, solAddress, suiAddress].every((a) => !a)) {
20
- throw new Error('At least one wallet address is required');
21
- }
22
-
23
- const url = `${AUCTIONEER_URL}/user_intent?${params.toString()}`;
24
-
25
- const response = await fetch(url, {
26
- method: 'GET',
27
- headers: {
28
- 'Content-Type': 'application/json', ...(DEV_ACCESS_KEY
29
- ? { "x-dev-access": DEV_ACCESS_KEY }
30
- : {})
31
- },
32
- });
33
-
34
- if (!response.ok) {
35
- throw new Error(`Failed to fetch user orders: ${response.status} ${response.statusText}`);
36
- }
37
-
38
- const data: ApiResponse<ApiUserOrders> = await response.json();
39
-
40
- if (!data.success) {
41
- 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');
42
15
  }
43
16
 
44
- return data.data!;
17
+ return new AuctioneerAPI(aggregatedToken).getUserOrders();
45
18
  }
@@ -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,9 +1,10 @@
1
- import { AUCTIONEER_URL, DEV_ACCESS_KEY } from '../constants.js';
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
4
  import type {
5
5
  CrossChainOrderPrepared,
6
6
  CrossChainUserIntentRequest,
7
+ DcaCrossChainUserIntentRequest,
7
8
  DcaSingleChainOrderPrepared,
8
9
  DcaSingleChainUserIntentRequest,
9
10
  SingleChainOrderPrepared,
@@ -28,11 +29,7 @@ export abstract class BaseSDK {
28
29
  private static async makeRequest(url: string, body: string): Promise<ApiResponse> {
29
30
  const response = await fetch(url, {
30
31
  method: 'POST',
31
- headers: {
32
- 'Content-Type': 'application/json', ...(DEV_ACCESS_KEY
33
- ? { "x-dev-access": DEV_ACCESS_KEY }
34
- : {}),
35
- },
32
+ headers: { 'Content-Type': 'application/json' },
36
33
  body,
37
34
  });
38
35
 
@@ -126,4 +123,13 @@ export abstract class BaseSDK {
126
123
  public static async validateDcaSingleChainOrder(intentRequest: DcaSingleChainUserIntentRequest): Promise<void> {
127
124
  return BaseSDK.validateOrder(intentRequest, '/validate_intent/single_chain/dca_order');
128
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
+ }
129
135
  }
@@ -60,7 +60,8 @@ export type CreateSolanaCrossChainOrderIntentParams = {
60
60
  deadline: number;
61
61
  extraTransfers?: ExtraTransfer[];
62
62
  orderPubkey?: string;
63
- stopLossMaxOut?: bigint;
63
+ stopLossType?: StopLossType;
64
+ stopLossTriggerPrice?: string;
64
65
  takeProfitMinOut?: bigint;
65
66
  };
66
67
 
@@ -150,13 +151,14 @@ export function createSolanaCrossChainOrderIntentRequest(
150
151
  amountOutMin: params.destinationTokenMinAmount?.toString() || '1',
151
152
  destinationAddress: params.destinationAddress,
152
153
  extraTransfers: params.extraTransfers,
153
- stopLossMaxOut: params.stopLossMaxOut?.toString(),
154
+ stopLossType: params.stopLossType,
155
+ stopLossTriggerPrice: params.stopLossTriggerPrice,
154
156
  takeProfitMinOut: params.takeProfitMinOut?.toString(),
155
157
  };
156
158
  const executionDetailsString = JSON.stringify(executionDetails, Parsers.bigIntReplacer);
157
159
 
158
- // Calculate execution details hash
159
- 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);
160
162
 
161
163
  const genericData = {
162
164
  user: params.user,
@@ -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';
@@ -92,13 +93,16 @@ export {
92
93
  createEvmSingleChainLimitOrderIntentRequest,
93
94
  createEvmSingleChainDcaOrderIntentRequest,
94
95
  createEvmCrossChainOrderIntentRequest,
96
+ createEvmCrossChainDcaOrderIntentRequest,
95
97
  generateEvmSingleChainLimitOrderTypedData,
96
98
  generateEvmSingleChainDcaOrderTypedData,
97
99
  generateEvmCrossChainOrderTypedData,
100
+ getEVMCrossChainDcaOrderTypedData,
98
101
  generateEvmRandomNonce,
99
102
  type CreateEvmSingleChainLimitOrderIntentParams,
100
103
  type CreateEvmSingleChainDcaOrderIntentParams,
101
104
  type CreateEvmCrossChainOrderIntentParams,
105
+ type CreateEvmCrossChainDcaOrderIntentParams,
102
106
  } from './core/evm/intent-helpers.js';
103
107
 
104
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