@shogun-sdk/swap 0.0.2-test

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/dist/core.js ADDED
@@ -0,0 +1,590 @@
1
+ // src/core/token-list.ts
2
+ import { getTokenList as intentsGetTokenList } from "@shogun-sdk/intents-sdk";
3
+ async function getTokenList(params) {
4
+ return intentsGetTokenList(params);
5
+ }
6
+
7
+ // src/core/getQuote.ts
8
+ import { QuoteProvider } from "@shogun-sdk/intents-sdk";
9
+ import { parseUnits } from "viem";
10
+
11
+ // src/core/executeOrder/normalizeNative.ts
12
+ import { isEvmChain } from "@shogun-sdk/intents-sdk";
13
+
14
+ // src/utils/address.ts
15
+ var NATIVE_TOKEN = {
16
+ ETH: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
17
+ SOL: "So11111111111111111111111111111111111111111",
18
+ SUI: "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI"
19
+ };
20
+ var isNativeAddress = (tokenAddress) => {
21
+ const NATIVE_ADDRESSES = Object.values(SupportedChains).map((chain) => chain.tokenAddress.toLowerCase());
22
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
23
+ return !!tokenAddress && NATIVE_ADDRESSES.includes(normalizedTokenAddress);
24
+ };
25
+
26
+ // src/utils/chain.ts
27
+ import { ChainID } from "@shogun-sdk/intents-sdk";
28
+ var SOLANA_CHAIN_ID = ChainID.Solana;
29
+ var SupportedChains = [
30
+ {
31
+ id: ChainID.Arbitrum,
32
+ name: "Arbitrum",
33
+ isEVM: true,
34
+ wrapped: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
35
+ symbol: "ETH",
36
+ decimals: 18,
37
+ tokenAddress: NATIVE_TOKEN.ETH
38
+ },
39
+ {
40
+ id: ChainID.Optimism,
41
+ name: "Optimism",
42
+ isEVM: true,
43
+ wrapped: "0x4200000000000000000000000000000000000006",
44
+ symbol: "ETH",
45
+ decimals: 18,
46
+ tokenAddress: NATIVE_TOKEN.ETH
47
+ },
48
+ {
49
+ id: ChainID.Base,
50
+ name: "Base",
51
+ isEVM: true,
52
+ wrapped: "0x4200000000000000000000000000000000000006",
53
+ symbol: "ETH",
54
+ decimals: 18,
55
+ tokenAddress: NATIVE_TOKEN.ETH
56
+ },
57
+ {
58
+ id: ChainID.Hyperliquid,
59
+ name: "Hyperliquid",
60
+ isEVM: true,
61
+ wrapped: "0x5555555555555555555555555555555555555555",
62
+ symbol: "ETH",
63
+ decimals: 18,
64
+ tokenAddress: NATIVE_TOKEN.ETH
65
+ },
66
+ {
67
+ id: ChainID.BSC,
68
+ name: "BSC",
69
+ isEVM: true,
70
+ wrapped: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
71
+ symbol: "BNB",
72
+ decimals: 18,
73
+ tokenAddress: NATIVE_TOKEN.ETH
74
+ },
75
+ {
76
+ id: SOLANA_CHAIN_ID,
77
+ name: "Solana",
78
+ isEVM: false,
79
+ wrapped: "So11111111111111111111111111111111111111112",
80
+ symbol: "SOL",
81
+ decimals: 9,
82
+ tokenAddress: NATIVE_TOKEN.SOL
83
+ }
84
+ ];
85
+
86
+ // src/utils/viem.ts
87
+ function isViemWalletClient(wallet) {
88
+ return wallet.extend !== void 0 && wallet.getPermissions !== void 0;
89
+ }
90
+
91
+ // src/utils/number.ts
92
+ function serializeBigIntsToStrings(obj) {
93
+ if (typeof obj === "bigint") {
94
+ return obj.toString();
95
+ }
96
+ if (typeof obj === "string" && /^\d+n$/.test(obj)) {
97
+ return obj.slice(0, -1);
98
+ }
99
+ if (Array.isArray(obj)) {
100
+ return obj.map((item) => serializeBigIntsToStrings(item));
101
+ }
102
+ if (obj && typeof obj === "object") {
103
+ const result = {};
104
+ for (const [key, value] of Object.entries(obj)) {
105
+ result[key] = serializeBigIntsToStrings(value);
106
+ }
107
+ return result;
108
+ }
109
+ return obj;
110
+ }
111
+
112
+ // src/core/executeOrder/normalizeNative.ts
113
+ function normalizeNative(chainId, address) {
114
+ if (isEvmChain(chainId) && isNativeAddress(address)) {
115
+ const chain = SupportedChains.find((c) => c.id === chainId);
116
+ if (!chain?.wrapped)
117
+ throw new Error(`Wrapped token not found for chainId ${chainId}`);
118
+ return chain.wrapped;
119
+ }
120
+ return address;
121
+ }
122
+
123
+ // src/core/getQuote.ts
124
+ async function getQuote(params) {
125
+ if (!params.tokenIn?.address || !params.tokenOut?.address) {
126
+ throw new Error("Both tokenIn and tokenOut must include an address.");
127
+ }
128
+ if (!params.sourceChainId || !params.destChainId) {
129
+ throw new Error("Both sourceChainId and destChainId are required.");
130
+ }
131
+ if (params.amount <= 0n) {
132
+ throw new Error("Amount must be greater than 0.");
133
+ }
134
+ const normalizedTokenIn = normalizeNative(params.sourceChainId, params.tokenIn.address);
135
+ const data = await QuoteProvider.getQuote({
136
+ sourceChainId: params.sourceChainId,
137
+ destChainId: params.destChainId,
138
+ tokenIn: normalizedTokenIn,
139
+ tokenOut: params.tokenOut.address,
140
+ amount: params.amount
141
+ });
142
+ const inputSlippage = params.slippage ?? 5;
143
+ const slippageDecimal = inputSlippage / 100;
144
+ const slippage = Math.min(Math.max(slippageDecimal, 0), 0.5);
145
+ let warning;
146
+ if (slippage > 0.1) {
147
+ warning = `\u26A0\uFE0F High slippage tolerance (${(slippage * 100).toFixed(2)}%) \u2014 price may vary significantly.`;
148
+ }
149
+ const estimatedAmountOut = BigInt(data.estimatedAmountOutReduced);
150
+ const slippageBps = BigInt(Math.round(slippage * 1e4));
151
+ const estimatedAmountOutAfterSlippage = estimatedAmountOut * (10000n - slippageBps) / 10000n;
152
+ const pricePerInputToken = estimatedAmountOut * 10n ** BigInt(params.tokenIn.decimals ?? 18) / BigInt(params.amount);
153
+ return {
154
+ amountOut: estimatedAmountOut,
155
+ amountOutUsd: data.estimatedAmountOutUsd,
156
+ amountInUsd: data.amountInUsd,
157
+ minStablecoinsAmount: data.estimatedAmountInAsMinStablecoinAmount,
158
+ tokenIn: {
159
+ address: params.tokenIn.address,
160
+ decimals: params.tokenIn.decimals ?? 18,
161
+ chainId: params.sourceChainId
162
+ },
163
+ tokenOut: {
164
+ address: params.tokenOut.address,
165
+ decimals: params.tokenOut.decimals ?? 18,
166
+ chainId: params.destChainId
167
+ },
168
+ amountIn: params.amount,
169
+ pricePerInputToken,
170
+ slippage,
171
+ internal: {
172
+ ...data,
173
+ estimatedAmountOutReduced: estimatedAmountOutAfterSlippage
174
+ },
175
+ warning
176
+ };
177
+ }
178
+ function buildQuoteParams({
179
+ tokenIn,
180
+ tokenOut,
181
+ sourceChainId,
182
+ destChainId,
183
+ amount,
184
+ slippage
185
+ }) {
186
+ return {
187
+ tokenIn,
188
+ tokenOut,
189
+ sourceChainId,
190
+ destChainId,
191
+ amount: parseUnits(amount.toString(), tokenIn.decimals ?? 18),
192
+ slippage
193
+ };
194
+ }
195
+
196
+ // src/core/getBalances.ts
197
+ import { TOKEN_SEARCH_API_BASE_URL } from "@shogun-sdk/intents-sdk";
198
+ async function getBalances(params, options) {
199
+ const { addresses, cursorEvm, cursorSvm } = params;
200
+ const { signal } = options ?? {};
201
+ if (!addresses?.evm && !addresses?.svm) {
202
+ throw new Error("At least one address (EVM or SVM) must be provided.");
203
+ }
204
+ const payload = JSON.stringify({
205
+ addresses,
206
+ cursorEvm,
207
+ cursorSvm
208
+ });
209
+ const start = performance.now();
210
+ const response = await fetch(`${TOKEN_SEARCH_API_BASE_URL}/tokens/balances`, {
211
+ method: "POST",
212
+ headers: {
213
+ accept: "application/json",
214
+ "Content-Type": "application/json"
215
+ },
216
+ body: payload,
217
+ signal
218
+ }).catch((err) => {
219
+ if (err.name === "AbortError") {
220
+ throw new Error("Balance request was cancelled.");
221
+ }
222
+ throw err;
223
+ });
224
+ if (!response.ok) {
225
+ const text = await response.text().catch(() => "");
226
+ throw new Error(`Failed to fetch balances: ${response.status} ${text}`);
227
+ }
228
+ const data = await response.json().catch(() => {
229
+ throw new Error("Invalid JSON response from balances API.");
230
+ });
231
+ const duration = (performance.now() - start).toFixed(1);
232
+ if (process.env.NODE_ENV !== "production") {
233
+ console.debug(`[Shogun SDK] Fetched balances in ${duration}ms`);
234
+ }
235
+ const evmItems = data.evm?.items ?? [];
236
+ const svmItems = data.svm?.items ?? [];
237
+ const combined = [...evmItems, ...svmItems];
238
+ return {
239
+ results: combined,
240
+ nextCursorEvm: data.evm?.cursor ?? null,
241
+ nextCursorSvm: data.svm?.cursor ?? null
242
+ };
243
+ }
244
+
245
+ // src/core/executeOrder/execute.ts
246
+ import { ChainID as ChainID4, isEvmChain as isEvmChain2 } from "@shogun-sdk/intents-sdk";
247
+ import { BaseError } from "viem";
248
+
249
+ // src/wallet-adapter/evm-wallet-adapter/adapter.ts
250
+ import "ethers/lib/ethers.js";
251
+ import { hexValue } from "ethers/lib/utils.js";
252
+ import {
253
+ custom
254
+ } from "viem";
255
+ function isEVMTransaction(tx) {
256
+ return typeof tx.from === "string";
257
+ }
258
+ var adaptViemWallet = (wallet) => {
259
+ const signTypedData = async (signData) => {
260
+ return await wallet.signTypedData({
261
+ account: wallet.account,
262
+ domain: signData.domain,
263
+ types: signData.types,
264
+ primaryType: signData.primaryType ?? "Order",
265
+ message: signData.value
266
+ });
267
+ };
268
+ const sendTransaction = async (transaction) => {
269
+ if (!isEVMTransaction(transaction)) {
270
+ throw new Error("Expected EVMTransaction but got SolanaTransaction");
271
+ }
272
+ const tx = await wallet.sendTransaction({
273
+ from: transaction.from,
274
+ to: transaction.to,
275
+ data: transaction.data,
276
+ value: transaction.value,
277
+ account: wallet.account?.address,
278
+ chain: wallet.chain
279
+ });
280
+ return tx;
281
+ };
282
+ const switchChain = async (chainId) => {
283
+ try {
284
+ await wallet.switchChain({ id: chainId });
285
+ } catch (e) {
286
+ const msg = e?.message || "";
287
+ if (msg.includes("does not support the requested chain")) {
288
+ throw new Error("Wallet does not support this chain");
289
+ }
290
+ if (msg.includes("rejected")) throw e;
291
+ if (msg.includes("already pending")) return;
292
+ }
293
+ };
294
+ const address = async () => {
295
+ let addr = wallet.account?.address;
296
+ if (!addr) {
297
+ const addresses = await wallet.getAddresses();
298
+ addr = addresses[0];
299
+ }
300
+ if (!addr) throw new Error("No address found");
301
+ return addr;
302
+ };
303
+ return {
304
+ vmType: "EVM" /* EVM */,
305
+ transport: custom(wallet.transport),
306
+ getChainId: async () => wallet.getChainId(),
307
+ address,
308
+ sendTransaction,
309
+ signTypedData,
310
+ switchChain
311
+ };
312
+ };
313
+
314
+ // src/core/executeOrder/handleEvmExecution.ts
315
+ import {
316
+ getEVMSingleChainOrderTypedData,
317
+ getEVMCrossChainOrderTypedData,
318
+ PERMIT2_ADDRESS
319
+ } from "@shogun-sdk/intents-sdk";
320
+ import { encodeFunctionData, erc20Abi } from "viem";
321
+
322
+ // src/core/executeOrder/stageMessages.ts
323
+ var DEFAULT_STAGE_MESSAGES = {
324
+ processing: "Preparing transaction for execution",
325
+ approving: "Approving token allowance",
326
+ approved: "Token approved successfully",
327
+ signing: "Signing order for submission",
328
+ submitting: "Submitting order to Auctioneer",
329
+ success: "Order executed successfully",
330
+ error: "Order execution failed"
331
+ };
332
+
333
+ // src/core/executeOrder/buildOrder.ts
334
+ import { CrossChainOrder, SingleChainOrder } from "@shogun-sdk/intents-sdk";
335
+ async function buildOrder({
336
+ quote,
337
+ accountAddress,
338
+ destination,
339
+ deadline,
340
+ isSingleChain
341
+ }) {
342
+ const { tokenIn, tokenOut } = quote;
343
+ if (isSingleChain) {
344
+ return await SingleChainOrder.create({
345
+ user: accountAddress,
346
+ chainId: tokenIn.chainId,
347
+ tokenIn: tokenIn.address,
348
+ tokenOut: tokenOut.address,
349
+ amountIn: quote.amountIn,
350
+ amountOutMin: quote.internal.estimatedAmountOutReduced,
351
+ deadline,
352
+ destinationAddress: destination
353
+ });
354
+ }
355
+ return await CrossChainOrder.create({
356
+ user: accountAddress,
357
+ sourceChainId: tokenIn.chainId,
358
+ sourceTokenAddress: tokenIn.address,
359
+ sourceTokenAmount: quote.amountIn,
360
+ destinationChainId: tokenOut.chainId,
361
+ destinationTokenAddress: tokenOut.address,
362
+ destinationAddress: destination,
363
+ deadline,
364
+ destinationTokenMinAmount: quote.internal.estimatedAmountOutReduced,
365
+ minStablecoinAmount: quote.minStablecoinsAmount
366
+ });
367
+ }
368
+
369
+ // src/core/executeOrder/handleEvmExecution.ts
370
+ async function handleEvmExecution({
371
+ recipientAddress,
372
+ quote,
373
+ chainId,
374
+ accountAddress,
375
+ wallet,
376
+ isSingleChain,
377
+ deadline,
378
+ update
379
+ }) {
380
+ const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage];
381
+ await wallet.switchChain(chainId);
382
+ const tokenIn = normalizeNative(chainId, quote.tokenIn.address);
383
+ const shouldWrapNative = isNativeAddress(quote.tokenIn.address);
384
+ update("processing", shouldWrapNative ? `${messageFor("processing")} (wrapping native token)` : messageFor("processing"));
385
+ if (shouldWrapNative) {
386
+ await wallet.sendTransaction({
387
+ to: tokenIn,
388
+ data: encodeFunctionData({
389
+ abi: [{ type: "function", name: "deposit", stateMutability: "payable", inputs: [], outputs: [] }],
390
+ functionName: "deposit",
391
+ args: []
392
+ }),
393
+ value: BigInt(quote.amountIn),
394
+ from: accountAddress
395
+ });
396
+ }
397
+ update("approving", messageFor("approving"));
398
+ await wallet.sendTransaction({
399
+ to: tokenIn,
400
+ data: encodeFunctionData({
401
+ abi: erc20Abi,
402
+ functionName: "approve",
403
+ args: [PERMIT2_ADDRESS[chainId], BigInt(quote.amountIn)]
404
+ }),
405
+ value: 0n,
406
+ from: accountAddress
407
+ });
408
+ update("approved", messageFor("approved"));
409
+ const destination = recipientAddress ?? accountAddress;
410
+ const order = await buildOrder({
411
+ quote,
412
+ accountAddress,
413
+ destination,
414
+ deadline,
415
+ isSingleChain
416
+ });
417
+ update("signing", messageFor("signing"));
418
+ const { orderTypedData, nonce } = isSingleChain ? await getEVMSingleChainOrderTypedData(order) : await getEVMCrossChainOrderTypedData(order);
419
+ if (!wallet.signTypedData) {
420
+ throw new Error("Wallet does not support EIP-712 signing");
421
+ }
422
+ const signature = await wallet.signTypedData(serializeBigIntsToStrings(orderTypedData));
423
+ update("submitting", messageFor("submitting"));
424
+ const res = await order.sendToAuctioneer({ signature, nonce: nonce.toString() });
425
+ if (!res.success) {
426
+ throw new Error("Auctioneer submission failed");
427
+ }
428
+ update("success", messageFor("success"));
429
+ return { status: true, txHash: res.data, chainId, stage: "success" };
430
+ }
431
+
432
+ // src/core/executeOrder/handleSolanaExecution.ts
433
+ import {
434
+ ChainID as ChainID3,
435
+ getSolanaSingleChainOrderInstructions,
436
+ getSolanaCrossChainOrderInstructions
437
+ } from "@shogun-sdk/intents-sdk";
438
+ import { VersionedTransaction } from "@solana/web3.js";
439
+ async function handleSolanaExecution({
440
+ recipientAddress,
441
+ quote,
442
+ wallet,
443
+ isSingleChain,
444
+ update,
445
+ accountAddress,
446
+ deadline
447
+ }) {
448
+ if (!wallet.rpcUrl) {
449
+ throw new Error("Solana wallet is missing rpcUrl");
450
+ }
451
+ const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage];
452
+ update("processing", messageFor("processing"));
453
+ const destination = recipientAddress ?? accountAddress;
454
+ const order = await buildOrder({
455
+ quote,
456
+ accountAddress,
457
+ destination,
458
+ deadline,
459
+ isSingleChain
460
+ });
461
+ const txData = await getSolanaOrderInstructions({
462
+ order,
463
+ isSingleChain,
464
+ rpcUrl: wallet.rpcUrl
465
+ });
466
+ const transaction = VersionedTransaction.deserialize(Uint8Array.from(txData.txBytes));
467
+ update("signing", messageFor("signing"));
468
+ console.log({ order });
469
+ const txSignature = await wallet.sendTransaction(transaction);
470
+ update("submitting", messageFor("submitting"));
471
+ const response = await submitToAuctioneer({
472
+ order,
473
+ isSingleChain,
474
+ txData
475
+ });
476
+ if (!response.success) {
477
+ throw new Error("Auctioneer submission failed");
478
+ }
479
+ update("success", messageFor("success"));
480
+ return {
481
+ status: true,
482
+ txHash: txSignature,
483
+ chainId: ChainID3.Solana,
484
+ stage: "success"
485
+ };
486
+ }
487
+ async function getSolanaOrderInstructions({
488
+ order,
489
+ isSingleChain,
490
+ rpcUrl
491
+ }) {
492
+ if (isSingleChain) {
493
+ return await getSolanaSingleChainOrderInstructions(order, {
494
+ rpcUrl
495
+ });
496
+ }
497
+ return await getSolanaCrossChainOrderInstructions(order, {
498
+ rpcUrl
499
+ });
500
+ }
501
+ async function submitToAuctioneer({
502
+ order,
503
+ isSingleChain,
504
+ txData
505
+ }) {
506
+ if (isSingleChain) {
507
+ const { orderAddress, secretNumber } = txData;
508
+ return await order.sendToAuctioneer({
509
+ orderPubkey: orderAddress,
510
+ secretNumber
511
+ });
512
+ }
513
+ return await order.sendToAuctioneer({
514
+ orderPubkey: txData.orderAddress
515
+ });
516
+ }
517
+
518
+ // src/core/executeOrder/execute.ts
519
+ async function executeOrder({
520
+ quote,
521
+ accountAddress,
522
+ recipientAddress,
523
+ wallet,
524
+ onStatus,
525
+ options = {}
526
+ }) {
527
+ const deadline = options.deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
528
+ const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage];
529
+ const update = (stage, message) => onStatus?.(stage, message ?? messageFor(stage));
530
+ try {
531
+ const adapter = normalizeWallet(wallet);
532
+ if (!adapter) throw new Error("No wallet provided");
533
+ const { tokenIn, tokenOut } = quote;
534
+ const isSingleChain = tokenIn.chainId === tokenOut.chainId;
535
+ const chainId = Number(tokenIn.chainId);
536
+ update("processing", messageFor("processing"));
537
+ if (isEvmChain2(chainId)) {
538
+ return await handleEvmExecution({
539
+ recipientAddress,
540
+ quote,
541
+ chainId,
542
+ accountAddress,
543
+ wallet: adapter,
544
+ isSingleChain,
545
+ deadline,
546
+ update
547
+ });
548
+ }
549
+ if (chainId === ChainID4.Solana) {
550
+ return await handleSolanaExecution({
551
+ recipientAddress,
552
+ quote,
553
+ accountAddress,
554
+ wallet: adapter,
555
+ isSingleChain,
556
+ deadline,
557
+ update
558
+ });
559
+ }
560
+ update("error", "Unsupported chain");
561
+ return { status: false, message: "Unsupported chain", stage: "error" };
562
+ } catch (error) {
563
+ const message = error instanceof BaseError ? error.shortMessage : error instanceof Error ? error.message : String(error);
564
+ update("error", message);
565
+ return { status: false, message, stage: "error" };
566
+ }
567
+ }
568
+ function normalizeWallet(wallet) {
569
+ if (!wallet) return void 0;
570
+ if (isViemWalletClient(wallet)) return adaptViemWallet(wallet);
571
+ return wallet;
572
+ }
573
+
574
+ // src/core/index.ts
575
+ import { ChainID as ChainID5, isEvmChain as isEvmChain3 } from "@shogun-sdk/intents-sdk";
576
+ export {
577
+ ChainID5 as ChainID,
578
+ NATIVE_TOKEN,
579
+ SOLANA_CHAIN_ID,
580
+ SupportedChains,
581
+ buildQuoteParams,
582
+ executeOrder,
583
+ getBalances,
584
+ getQuote,
585
+ getTokenList,
586
+ isEvmChain3 as isEvmChain,
587
+ isNativeAddress,
588
+ isViemWalletClient,
589
+ serializeBigIntsToStrings
590
+ };