@kaspacom/swap-sdk 1.0.9 → 1.0.11

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 (54) hide show
  1. package/dist/index.cjs +982 -0
  2. package/dist/index.d.cts +239 -0
  3. package/dist/index.d.ts +239 -0
  4. package/dist/index.js +949 -0
  5. package/dist/types/config/networks.d.ts +2 -0
  6. package/dist/{cjs → types}/controllers/swap-sdk.controller.d.ts +2 -4
  7. package/dist/{cjs → types}/index.d.ts +1 -2
  8. package/dist/{esm → types}/services/swap.service.d.ts +1 -3
  9. package/dist/{cjs → types}/services/wallet.service.d.ts +1 -2
  10. package/dist/{esm → types}/types/index.d.ts +12 -2
  11. package/package.json +12 -30
  12. package/dist/cjs/controllers/swap-sdk.controller.cjs +0 -181
  13. package/dist/cjs/controllers/swap-sdk.controller.d.ts.map +0 -1
  14. package/dist/cjs/controllers/swap-sdk.controller.js.map +0 -1
  15. package/dist/cjs/index.cjs +0 -53
  16. package/dist/cjs/index.d.ts.map +0 -1
  17. package/dist/cjs/index.js.map +0 -1
  18. package/dist/cjs/services/swap.service.cjs +0 -568
  19. package/dist/cjs/services/swap.service.d.ts +0 -109
  20. package/dist/cjs/services/swap.service.d.ts.map +0 -1
  21. package/dist/cjs/services/swap.service.js.map +0 -1
  22. package/dist/cjs/services/wallet.service.cjs +0 -144
  23. package/dist/cjs/services/wallet.service.d.ts.map +0 -1
  24. package/dist/cjs/services/wallet.service.js.map +0 -1
  25. package/dist/cjs/types/index.cjs +0 -10
  26. package/dist/cjs/types/index.d.ts +0 -71
  27. package/dist/cjs/types/index.d.ts.map +0 -1
  28. package/dist/cjs/types/index.js.map +0 -1
  29. package/dist/cjs/types/networks.cjs +0 -23
  30. package/dist/cjs/types/networks.d.ts +0 -14
  31. package/dist/cjs/types/networks.d.ts.map +0 -1
  32. package/dist/cjs/types/networks.js.map +0 -1
  33. package/dist/esm/controllers/swap-sdk.controller.d.ts +0 -27
  34. package/dist/esm/controllers/swap-sdk.controller.d.ts.map +0 -1
  35. package/dist/esm/controllers/swap-sdk.controller.js +0 -177
  36. package/dist/esm/controllers/swap-sdk.controller.js.map +0 -1
  37. package/dist/esm/index.d.ts +0 -15
  38. package/dist/esm/index.d.ts.map +0 -1
  39. package/dist/esm/index.js +0 -30
  40. package/dist/esm/index.js.map +0 -1
  41. package/dist/esm/services/swap.service.d.ts.map +0 -1
  42. package/dist/esm/services/swap.service.js +0 -564
  43. package/dist/esm/services/swap.service.js.map +0 -1
  44. package/dist/esm/services/wallet.service.d.ts +0 -21
  45. package/dist/esm/services/wallet.service.d.ts.map +0 -1
  46. package/dist/esm/services/wallet.service.js +0 -140
  47. package/dist/esm/services/wallet.service.js.map +0 -1
  48. package/dist/esm/types/index.d.ts.map +0 -1
  49. package/dist/esm/types/index.js +0 -7
  50. package/dist/esm/types/index.js.map +0 -1
  51. package/dist/esm/types/networks.d.ts +0 -14
  52. package/dist/esm/types/networks.d.ts.map +0 -1
  53. package/dist/esm/types/networks.js +0 -20
  54. package/dist/esm/types/networks.js.map +0 -1
package/dist/index.cjs ADDED
@@ -0,0 +1,982 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DEFAULT_SWAP_SETTINGS: () => DEFAULT_SETTINGS,
24
+ LoaderStatuses: () => LoaderStatuses,
25
+ NETWORKS: () => NETWORKS,
26
+ SwapSdkController: () => SwapSdkController,
27
+ SwapService: () => SwapService,
28
+ WalletService: () => WalletService,
29
+ createKaspaComSwapController: () => createKaspaComSwapController
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/types/index.ts
34
+ var LoaderStatuses = /* @__PURE__ */ ((LoaderStatuses2) => {
35
+ LoaderStatuses2[LoaderStatuses2["CALCULATING_QUOTE"] = 1] = "CALCULATING_QUOTE";
36
+ LoaderStatuses2[LoaderStatuses2["APPROVING"] = 2] = "APPROVING";
37
+ LoaderStatuses2[LoaderStatuses2["SWAPPING"] = 3] = "SWAPPING";
38
+ return LoaderStatuses2;
39
+ })(LoaderStatuses || {});
40
+
41
+ // src/services/wallet.service.ts
42
+ var import_ethers = require("ethers");
43
+ var WalletService = class {
44
+ // injected provider (window.ethereum or similar)
45
+ constructor(config, injectedProvider) {
46
+ this.signer = null;
47
+ this.address = null;
48
+ this.config = config;
49
+ this.networkProvider = new import_ethers.JsonRpcProvider(config.rpcUrl, {
50
+ name: config.name,
51
+ chainId: config.chainId
52
+ }, config.additionalJsonRpcApiProviderOptionsOptions);
53
+ if (injectedProvider) {
54
+ this.connect(this.injectedProvider);
55
+ }
56
+ }
57
+ async connect(injectedProvider) {
58
+ if (injectedProvider) {
59
+ this.injectedProvider = injectedProvider;
60
+ this.walletProvider = new import_ethers.BrowserProvider(this.injectedProvider);
61
+ }
62
+ if (!this.injectedProvider || !this.walletProvider) {
63
+ throw new Error("Please connect wallet.");
64
+ }
65
+ try {
66
+ const accounts = await this.injectedProvider.request({
67
+ method: "eth_requestAccounts"
68
+ });
69
+ if (accounts.length === 0) {
70
+ throw new Error("No accounts found");
71
+ }
72
+ this.address = accounts[0];
73
+ const chainId = await this.injectedProvider.request({
74
+ method: "eth_chainId"
75
+ });
76
+ const currentChainId = parseInt(chainId, 16);
77
+ if (currentChainId !== this.config.chainId) {
78
+ try {
79
+ await this.injectedProvider.request({
80
+ method: "wallet_switchEthereumChain",
81
+ params: [{ chainId: `0x${this.config.chainId.toString(16)}` }]
82
+ });
83
+ } catch (switchError) {
84
+ if (switchError.code === 4902) {
85
+ await this.injectedProvider.request({
86
+ method: "wallet_addEthereumChain",
87
+ params: [{
88
+ chainId: `0x${this.config.chainId.toString(16)}`,
89
+ chainName: this.config.name,
90
+ nativeCurrency: {
91
+ name: "ETH",
92
+ symbol: "ETH",
93
+ decimals: 18
94
+ },
95
+ rpcUrls: [this.config.rpcUrl],
96
+ blockExplorerUrls: this.config.blockExplorerUrl ? [this.config.blockExplorerUrl] : []
97
+ }]
98
+ });
99
+ await this.injectedProvider.request({
100
+ method: "wallet_switchEthereumChain",
101
+ params: [{ chainId: `0x${this.config.chainId.toString(16)}` }]
102
+ });
103
+ } else {
104
+ throw new Error(`Please switch to ${this.config.name} network`);
105
+ }
106
+ }
107
+ }
108
+ this.signer = await this.walletProvider.getSigner();
109
+ return this.address;
110
+ } catch (error) {
111
+ if (error.code === 4001) {
112
+ throw new Error("User rejected wallet connection");
113
+ }
114
+ throw new Error(`Failed to connect wallet: ${error.message}`);
115
+ }
116
+ }
117
+ disconnect() {
118
+ this.address = null;
119
+ this.signer = null;
120
+ }
121
+ isConnected() {
122
+ return !!this.address && !!this.signer;
123
+ }
124
+ getAddress() {
125
+ return this.address;
126
+ }
127
+ getProvider() {
128
+ return this.walletProvider || this.networkProvider;
129
+ }
130
+ getSigner() {
131
+ return this.signer;
132
+ }
133
+ // Helper method to get current chain ID
134
+ async getCurrentChainId() {
135
+ if (!this.injectedProvider) {
136
+ throw new Error("No Ethereum wallet detected");
137
+ }
138
+ const chainId = await this.injectedProvider.request({
139
+ method: "eth_chainId"
140
+ });
141
+ return parseInt(chainId, 16);
142
+ }
143
+ // Connect wallet and emit event with address
144
+ async connectWallet() {
145
+ if (this.injectedProvider) {
146
+ try {
147
+ const accounts = await this.injectedProvider.request({
148
+ method: "eth_requestAccounts"
149
+ });
150
+ if (accounts.length > 0 && typeof accounts[0] === "string") {
151
+ const address = accounts[0];
152
+ this.address = address;
153
+ return address;
154
+ } else {
155
+ throw new Error("No accounts found");
156
+ }
157
+ } catch (error) {
158
+ console.error("Failed to connect wallet:", error);
159
+ throw error;
160
+ }
161
+ } else {
162
+ throw new Error("No Ethereum wallet detected. Please connect a wallet provider.");
163
+ }
164
+ }
165
+ // Disconnect wallet and emit event
166
+ disconnectWallet() {
167
+ this.address = null;
168
+ this.signer = null;
169
+ }
170
+ };
171
+
172
+ // src/services/swap.service.ts
173
+ var import_ethers2 = require("ethers");
174
+ var import_sdk_core = require("@uniswap/sdk-core");
175
+ var import_v2_sdk = require("@uniswap/v2-sdk");
176
+ var PARTNER_FEE_BPS_DIVISOR = 10000n;
177
+ var SwapService = class {
178
+ constructor(provider, config, swapOptions) {
179
+ this.config = config;
180
+ this.swapOptions = swapOptions;
181
+ this.signer = null;
182
+ this.pairs = [];
183
+ this.resolvePairsLoaded = null;
184
+ this.resolvePartnerFeeLoaded = null;
185
+ this.partnerFee = 0n;
186
+ this.isFeeActive = false;
187
+ this.provider = provider;
188
+ this.wethAddress = config.wethAddress;
189
+ this.chainId = config.chainId;
190
+ const routerAbi = [
191
+ // Swaps (ERC20 <-> ERC20)
192
+ "function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)",
193
+ "function swapTokensForExactTokens(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)",
194
+ // Swaps (ETH <-> ERC20)
195
+ "function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts)",
196
+ "function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts)",
197
+ "function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)",
198
+ "function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)",
199
+ // Get Amounts
200
+ "function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)",
201
+ "function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut)",
202
+ "function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn)",
203
+ "function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts)",
204
+ // Get WETH
205
+ "function WETH() external pure returns (address)"
206
+ ];
207
+ const factoryAbi = [
208
+ "function getPair(address tokenA, address tokenB) external view returns (address pair)",
209
+ "function allPairs(uint) external view returns (address pair)",
210
+ "function allPairsLength() external view returns (uint)"
211
+ ];
212
+ const proxyAbi = [
213
+ ...routerAbi,
214
+ "function partnerFee(bytes32) external view returns (address feeRecipient, uint16 feeBps)",
215
+ "function feeEnabled() external view returns (bool)"
216
+ ];
217
+ this.routerContract = new import_ethers2.Contract(config.routerAddress, routerAbi, provider);
218
+ this.factoryContract = new import_ethers2.Contract(config.factoryAddress, factoryAbi, provider);
219
+ if (config.proxyAddress) {
220
+ this.proxyContract = new import_ethers2.Contract(config.proxyAddress, proxyAbi, provider);
221
+ }
222
+ this.pairsLoadedPromise = new Promise((resolve) => {
223
+ this.resolvePairsLoaded = resolve;
224
+ });
225
+ this.partnerFeeLoadedPromise = new Promise((resolve) => {
226
+ this.resolvePartnerFeeLoaded = resolve;
227
+ });
228
+ this.loadAllPairsFromGraph();
229
+ this.loadPartnerFee();
230
+ }
231
+ // parnter fee is BPS_DIVISOR = 10_000n;
232
+ async loadPartnerFee() {
233
+ if (!this.resolvePairsLoaded) {
234
+ return this.partnerFee;
235
+ }
236
+ if (this.swapOptions.partnerKey) {
237
+ const [, fee] = await this.proxyContract?.partnerFee(this.swapOptions.partnerKey);
238
+ this.partnerFee = fee;
239
+ }
240
+ this.partnerFee = 100n;
241
+ const isFeeActive = await this.proxyContract?.feeEnabled();
242
+ this.isFeeActive = isFeeActive;
243
+ if (this.resolvePartnerFeeLoaded) {
244
+ this.resolvePartnerFeeLoaded();
245
+ this.resolvePartnerFeeLoaded = null;
246
+ }
247
+ return this.partnerFee;
248
+ }
249
+ setSigner(signer) {
250
+ this.signer = signer;
251
+ this.routerContract = this.routerContract.connect(signer);
252
+ if (this.proxyContract) {
253
+ this.proxyContract = this.proxyContract.connect(signer);
254
+ }
255
+ }
256
+ /**
257
+ * Rounds a number string to the specified number of decimal places
258
+ * to avoid parseUnits errors with too many decimals
259
+ */
260
+ roundToDecimals(value, decimals) {
261
+ const num = parseFloat(value);
262
+ if (isNaN(num)) {
263
+ return "0";
264
+ }
265
+ return num.toFixed(decimals);
266
+ }
267
+ /**
268
+ * Loads all pairs from The Graph and caches them as Uniswap SDK Pair instances.
269
+ * @param graphEndpoint The GraphQL endpoint URL
270
+ */
271
+ async loadAllPairsFromGraph() {
272
+ const query = `{
273
+ pairs(first: 1000) {
274
+ id
275
+ reserve0
276
+ reserve1
277
+ token0 { id symbol name decimals }
278
+ token1 { id symbol name decimals }
279
+ }
280
+ }`;
281
+ try {
282
+ const response = await fetch(this.config.graphEndpoint, {
283
+ method: "POST",
284
+ headers: { "Content-Type": "application/json" },
285
+ body: JSON.stringify({ query })
286
+ });
287
+ if (!response.ok) throw new Error(`Network error: ${response.status}`);
288
+ const { data } = await response.json();
289
+ if (!data || !data.pairs) return;
290
+ const pairs = [];
291
+ for (const pair of data.pairs) {
292
+ pairs.push(
293
+ this.createSDKPair(pair)
294
+ );
295
+ }
296
+ this.pairs = pairs;
297
+ const wethPair = data.pairs.find(
298
+ (pair) => pair.token0.id.toLowerCase() === this.wethAddress.toLowerCase() || pair.token1.id.toLowerCase() === this.wethAddress.toLowerCase()
299
+ );
300
+ if (wethPair) {
301
+ const tokenData = wethPair.token0.id.toLowerCase() === this.wethAddress.toLowerCase() ? wethPair.token0 : wethPair.token1;
302
+ this.wethToken = {
303
+ address: tokenData.id,
304
+ symbol: tokenData.symbol,
305
+ name: tokenData.name,
306
+ decimals: Number(tokenData.decimals)
307
+ };
308
+ } else {
309
+ throw new Error("No weth token found");
310
+ }
311
+ if (this.resolvePairsLoaded) {
312
+ this.resolvePairsLoaded();
313
+ this.resolvePairsLoaded = null;
314
+ }
315
+ } catch (error) {
316
+ console.error("Error loading pairs from graph:", error);
317
+ if (this.resolvePairsLoaded) {
318
+ this.resolvePairsLoaded();
319
+ this.resolvePairsLoaded = null;
320
+ }
321
+ }
322
+ }
323
+ async waitForPairsLoaded() {
324
+ return await this.pairsLoadedPromise;
325
+ }
326
+ async waitForPartnerFeeLoaded() {
327
+ return await this.partnerFeeLoadedPromise;
328
+ }
329
+ createSDKPair(pair) {
330
+ const { token0, token1, id, reserve0, reserve1 } = pair;
331
+ const sdkToken0 = new import_sdk_core.Token(
332
+ this.chainId,
333
+ token0.id,
334
+ Number(token0.decimals),
335
+ token0.symbol,
336
+ token0.name
337
+ );
338
+ const sdkToken1 = new import_sdk_core.Token(
339
+ this.chainId,
340
+ token1.id,
341
+ Number(token1.decimals),
342
+ token1.symbol,
343
+ token1.name
344
+ );
345
+ let reserve0BN;
346
+ let reserve1BN;
347
+ if (reserve0 && reserve1) {
348
+ reserve0BN = (0, import_ethers2.parseUnits)(reserve0, Number(token0.decimals));
349
+ reserve1BN = (0, import_ethers2.parseUnits)(reserve1, Number(token1.decimals));
350
+ } else {
351
+ throw new Error("No reserves data");
352
+ }
353
+ const amount0 = import_sdk_core.CurrencyAmount.fromRawAmount(
354
+ sdkToken0,
355
+ reserve0BN.toString()
356
+ );
357
+ const amount1 = import_sdk_core.CurrencyAmount.fromRawAmount(
358
+ sdkToken1,
359
+ reserve1BN.toString()
360
+ );
361
+ const sdkPair = new import_v2_sdk.Pair(amount0, amount1);
362
+ return sdkPair;
363
+ }
364
+ /**
365
+ * Returns the cached pairs for use in routing.
366
+ */
367
+ getPairs() {
368
+ return this.pairs;
369
+ }
370
+ /**
371
+ * Finds the best trade path using Uniswap SDK for a given input amount.
372
+ * Returns the best path as an array of addresses, or null if no trade found.
373
+ */
374
+ async getBestTrade(fromToken, toToken, amountInWei, isOutputAmount) {
375
+ const sdkFromToken = new import_sdk_core.Token(
376
+ this.chainId,
377
+ fromToken.address,
378
+ fromToken.decimals,
379
+ fromToken.symbol,
380
+ fromToken.name
381
+ );
382
+ const sdkToToken = new import_sdk_core.Token(
383
+ this.chainId,
384
+ toToken.address,
385
+ toToken.decimals,
386
+ toToken.symbol,
387
+ toToken.name
388
+ );
389
+ const currencyAmount = import_sdk_core.CurrencyAmount.fromRawAmount(
390
+ isOutputAmount ? sdkToToken : sdkFromToken,
391
+ amountInWei
392
+ );
393
+ await this.waitForPairsLoaded();
394
+ await this.waitForPartnerFeeLoaded();
395
+ const pairs = this.getPairs();
396
+ if (!pairs || pairs.length === 0) {
397
+ throw new Error("Pairs not loaded yet. Please wait for initialization.");
398
+ }
399
+ const tradeConfig = {
400
+ maxHops: 3,
401
+ maxNumResults: 1
402
+ };
403
+ const trades = isOutputAmount ? import_v2_sdk.Trade.bestTradeExactOut(pairs, sdkFromToken, currencyAmount, tradeConfig) : import_v2_sdk.Trade.bestTradeExactIn(
404
+ pairs,
405
+ currencyAmount,
406
+ sdkToToken,
407
+ tradeConfig
408
+ );
409
+ if (trades.length > 0) {
410
+ return trades[0];
411
+ } else {
412
+ return null;
413
+ }
414
+ }
415
+ trimTrailingZeros(value) {
416
+ if (!value.includes(".")) return value;
417
+ value = value.replace(/\.?0+$/, "");
418
+ return value;
419
+ }
420
+ /**
421
+ *
422
+ * @param sellToken
423
+ * @param buyToken
424
+ * @param targetAmount
425
+ * @param isOutputAmount true if user input output (How much tokens to receive) and not input (how much tokens to sell)
426
+ * @param slippage
427
+ * @returns
428
+ */
429
+ async calculateTrade(sellToken, buyToken, targetAmount, isOutputAmount, slippage) {
430
+ try {
431
+ const roundedAmountIn = this.roundToDecimals(targetAmount, isOutputAmount ? buyToken.decimals : sellToken.decimals);
432
+ let sellAmountWei = (0, import_ethers2.parseUnits)(
433
+ roundedAmountIn,
434
+ isOutputAmount ? buyToken.decimals : sellToken.decimals
435
+ );
436
+ if (isOutputAmount && this.partnerFee && this.partnerFee > 0n) {
437
+ const numerator = sellAmountWei * PARTNER_FEE_BPS_DIVISOR;
438
+ const denominator = PARTNER_FEE_BPS_DIVISOR - this.partnerFee;
439
+ sellAmountWei = (numerator + denominator - 1n) / denominator;
440
+ }
441
+ const trade = await this.getBestTrade(
442
+ sellToken.address == import_ethers2.ethers.ZeroAddress ? this.wethToken : sellToken,
443
+ buyToken.address == import_ethers2.ethers.ZeroAddress ? this.wethToken : buyToken,
444
+ sellAmountWei.toString(),
445
+ isOutputAmount
446
+ );
447
+ if (!trade) {
448
+ throw new Error("No trade path found for the given tokens and amount.");
449
+ }
450
+ const amountIn = trade.inputAmount.quotient.toString();
451
+ const amountOut = trade.outputAmount.quotient.toString();
452
+ let amounts = {
453
+ amountIn: (0, import_ethers2.formatUnits)(amountIn, sellToken.decimals),
454
+ amountOut: isOutputAmount ? this.trimTrailingZeros(roundedAmountIn) : (0, import_ethers2.formatUnits)(amountOut, buyToken.decimals),
455
+ amountInRaw: amountIn,
456
+ amountOutRaw: amountOut
457
+ };
458
+ const slippagePercent = new import_sdk_core.Percent(Math.round(parseFloat(slippage) * 100), 1e4);
459
+ let maxAmountIn, minAmountOut;
460
+ if (isOutputAmount) {
461
+ maxAmountIn = trade.maximumAmountIn(slippagePercent).quotient.toString();
462
+ amounts.maxAmountInRaw = maxAmountIn;
463
+ amounts.maxAmountIn = (0, import_ethers2.formatUnits)(maxAmountIn, sellToken.decimals);
464
+ } else {
465
+ minAmountOut = trade.minimumAmountOut(slippagePercent).quotient.toString();
466
+ amounts.minAmountOutRaw = minAmountOut;
467
+ amounts.minAmountOut = (0, import_ethers2.formatUnits)(minAmountOut, buyToken.decimals);
468
+ }
469
+ if (this.partnerFee && this.partnerFee > 0n) {
470
+ if (!isOutputAmount) {
471
+ const amountOut2 = BigInt(trade.outputAmount.quotient.toString());
472
+ const amountOutMinusFee = amountOut2 * (PARTNER_FEE_BPS_DIVISOR - this.partnerFee) / PARTNER_FEE_BPS_DIVISOR;
473
+ amounts.amountOutRaw = amountOutMinusFee.toString();
474
+ amounts.amountOut = (0, import_ethers2.formatUnits)(amountOutMinusFee, buyToken.decimals);
475
+ if (minAmountOut) {
476
+ const minOut = BigInt(minAmountOut.toString());
477
+ const minOutMinusFee = minOut * (PARTNER_FEE_BPS_DIVISOR - this.partnerFee) / PARTNER_FEE_BPS_DIVISOR;
478
+ amounts.minAmountOutRaw = minOutMinusFee.toString();
479
+ amounts.minAmountOut = (0, import_ethers2.formatUnits)(minOutMinusFee, buyToken.decimals);
480
+ }
481
+ }
482
+ }
483
+ return {
484
+ trade,
485
+ computed: amounts
486
+ };
487
+ } catch (error) {
488
+ console.error("Error calculating expected output:", error);
489
+ throw error;
490
+ }
491
+ }
492
+ async checkApproval(tokenAddress, amount, spenderAddress) {
493
+ try {
494
+ const tokenContract = new import_ethers2.Contract(
495
+ tokenAddress,
496
+ ["function allowance(address,address) view returns (uint256)"],
497
+ this.provider
498
+ );
499
+ const signerAddress = await this.signer?.getAddress();
500
+ if (!signerAddress) {
501
+ throw new Error("Please connect wallet first");
502
+ }
503
+ const allowance = await tokenContract.allowance(signerAddress, spenderAddress);
504
+ const amountWei = (0, import_ethers2.parseUnits)(amount, 18);
505
+ return allowance >= amountWei;
506
+ } catch (error) {
507
+ console.error("Error checking approval:", error);
508
+ return false;
509
+ }
510
+ }
511
+ async isApprovalNeeded(fromToken, amountInWei) {
512
+ if (!this.signer) {
513
+ throw new Error("Please connect wallet first");
514
+ }
515
+ if (fromToken.address !== import_ethers2.ethers.ZeroAddress) {
516
+ const tokenContract = new import_ethers2.Contract(
517
+ fromToken.address,
518
+ ["function allowance(address,address) view returns (uint256)", "function approve(address,uint256) returns (bool)"],
519
+ this.signer
520
+ );
521
+ const signerAddress = await this.signer.getAddress();
522
+ const allowanceTo = this.config.proxyAddress || this.config.routerAddress;
523
+ const allowance = await tokenContract.allowance(signerAddress, allowanceTo);
524
+ if (allowance < amountInWei) {
525
+ return {
526
+ isApprovalNeeded: true,
527
+ tokenContract,
528
+ signerAddress,
529
+ allowanceTo
530
+ };
531
+ }
532
+ }
533
+ return {
534
+ isApprovalNeeded: false
535
+ };
536
+ }
537
+ async approveIfNeedApproval(fromToken, amountInWei) {
538
+ const isApprovalNeededInfo = await this.isApprovalNeeded(fromToken, amountInWei);
539
+ if (!isApprovalNeededInfo.isApprovalNeeded) {
540
+ return null;
541
+ }
542
+ return await isApprovalNeededInfo.tokenContract.approve(isApprovalNeededInfo.allowanceTo, import_ethers2.ethers.MaxUint256);
543
+ }
544
+ async swapTokens(fromToken, toToken, amountInWei, amountOutWei, path, isOutputAmount, deadline) {
545
+ if (!this.signer) {
546
+ throw new Error("Please connect wallet first");
547
+ }
548
+ try {
549
+ const deadlineTimestamp = Math.floor(Date.now() / 1e3) + deadline * 60;
550
+ let tx;
551
+ const signerAddress = await this.signer.getAddress();
552
+ const iface = this.proxyContract ? this.proxyContract.interface : this.routerContract.interface;
553
+ let swapData;
554
+ const to = this.isFeeActive ? this.config.proxyAddress : signerAddress;
555
+ if (isOutputAmount) {
556
+ if (fromToken.address === import_ethers2.ethers.ZeroAddress) {
557
+ swapData = iface.encodeFunctionData("swapETHForExactTokens", [
558
+ amountOutWei,
559
+ path,
560
+ to,
561
+ deadlineTimestamp
562
+ ]);
563
+ } else if (toToken.address === import_ethers2.ethers.ZeroAddress) {
564
+ swapData = iface.encodeFunctionData("swapTokensForExactETH", [
565
+ amountOutWei,
566
+ amountInWei,
567
+ path,
568
+ to,
569
+ deadlineTimestamp
570
+ ]);
571
+ } else {
572
+ swapData = iface.encodeFunctionData("swapTokensForExactTokens", [
573
+ amountOutWei,
574
+ amountInWei,
575
+ path,
576
+ to,
577
+ deadlineTimestamp
578
+ ]);
579
+ }
580
+ } else {
581
+ if (fromToken.address === import_ethers2.ethers.ZeroAddress) {
582
+ swapData = iface.encodeFunctionData("swapExactETHForTokens", [
583
+ amountOutWei,
584
+ path,
585
+ to,
586
+ deadlineTimestamp
587
+ ]);
588
+ } else if (toToken.address === import_ethers2.ethers.ZeroAddress) {
589
+ swapData = iface.encodeFunctionData("swapExactTokensForETH", [
590
+ amountInWei,
591
+ amountOutWei,
592
+ path,
593
+ to,
594
+ deadlineTimestamp
595
+ ]);
596
+ } else {
597
+ swapData = iface.encodeFunctionData("swapExactTokensForTokens", [
598
+ amountInWei,
599
+ amountOutWei,
600
+ path,
601
+ to,
602
+ deadlineTimestamp
603
+ ]);
604
+ }
605
+ }
606
+ if (this.proxyContract) {
607
+ swapData = (0, import_ethers2.hexlify)(this.concatSelectorAndParams(import_ethers2.ethers.getBytes(swapData), [], "permit", this.swapOptions.partnerKey));
608
+ }
609
+ tx = await this.signer.sendTransaction({
610
+ to: this.config.proxyAddress || this.config.routerAddress,
611
+ from: signerAddress,
612
+ data: swapData,
613
+ value: fromToken.address === import_ethers2.ethers.ZeroAddress ? amountInWei : 0n
614
+ });
615
+ return tx;
616
+ } catch (error) {
617
+ console.error("Error swapping tokens:", error);
618
+ throw error;
619
+ }
620
+ }
621
+ async getPairAddress(tokenA, tokenB) {
622
+ try {
623
+ return await this.factoryContract.getPair(tokenA, tokenB);
624
+ } catch (error) {
625
+ console.error("Error getting pair address:", error);
626
+ throw error;
627
+ }
628
+ }
629
+ async checkLiquidityExists(tokenA, tokenB) {
630
+ try {
631
+ const pairAddress = await this.getPairAddress(tokenA, tokenB);
632
+ return pairAddress !== import_ethers2.ZeroAddress;
633
+ } catch (error) {
634
+ console.error("Error checking liquidity:", error);
635
+ return false;
636
+ }
637
+ }
638
+ // Uniswap SDK methods for advanced trading
639
+ async createTrade(fromToken, toToken, amountIn, slippageTolerance = 0.5) {
640
+ try {
641
+ const fromTokenInstance = new import_sdk_core.Token(
642
+ this.chainId,
643
+ fromToken.address,
644
+ fromToken.decimals,
645
+ fromToken.symbol,
646
+ fromToken.name
647
+ );
648
+ const toTokenInstance = new import_sdk_core.Token(
649
+ this.chainId,
650
+ toToken.address,
651
+ toToken.decimals,
652
+ toToken.symbol,
653
+ toToken.name
654
+ );
655
+ const currencyAmount = import_sdk_core.CurrencyAmount.fromRawAmount(
656
+ fromTokenInstance,
657
+ (0, import_ethers2.parseUnits)(amountIn, fromToken.decimals).toString()
658
+ );
659
+ const pairAddress = await this.getPairAddress(fromToken.address, toToken.address);
660
+ if (pairAddress === import_ethers2.ZeroAddress) {
661
+ throw new Error("No liquidity pair found");
662
+ }
663
+ const pair = new import_v2_sdk.Pair(
664
+ import_sdk_core.CurrencyAmount.fromRawAmount(fromTokenInstance, "0"),
665
+ import_sdk_core.CurrencyAmount.fromRawAmount(toTokenInstance, "0")
666
+ );
667
+ const route = new import_v2_sdk.Route([pair], fromTokenInstance, toTokenInstance);
668
+ const trade = new import_v2_sdk.Trade(
669
+ route,
670
+ currencyAmount,
671
+ import_sdk_core.TradeType.EXACT_INPUT
672
+ );
673
+ return trade;
674
+ } catch (error) {
675
+ console.error("Error creating trade:", error);
676
+ throw error;
677
+ }
678
+ }
679
+ /**
680
+ * Fetch tokens from the graph endpoint (subgraph)
681
+ * @param graphEndpoint The GraphQL endpoint URL
682
+ * @param search Optional search string for symbol or name
683
+ */
684
+ async getTokensFromGraph(limit = 100, search) {
685
+ const finalLimit = Math.min(limit, 1e3);
686
+ const query = `{
687
+ tokens(first: ${finalLimit}, where: {
688
+ ${search ? `or: [
689
+ { symbol_contains_nocase: "${search}" }
690
+ { name_contains_nocase: "${search}" }
691
+ ]` : ""}
692
+ }) {
693
+ id
694
+ symbol
695
+ name
696
+ decimals
697
+ }
698
+ }`;
699
+ try {
700
+ const response = await fetch(this.config.graphEndpoint, {
701
+ method: "POST",
702
+ headers: { "Content-Type": "application/json" },
703
+ body: JSON.stringify({ query })
704
+ });
705
+ if (!response.ok) throw new Error(`Network error: ${response.status}`);
706
+ const { data } = await response.json();
707
+ if (!data || !data.tokens) return [];
708
+ return data.tokens.map((token) => ({
709
+ address: token.id,
710
+ symbol: token.symbol,
711
+ name: token.name,
712
+ decimals: Number(token.decimals)
713
+ }));
714
+ } catch (error) {
715
+ console.error("Error fetching tokens from graph:", error);
716
+ return [];
717
+ }
718
+ }
719
+ /**
720
+ * Concatenates bytes: selector, array of bytes (each element is Uint8Array), array length (uint8, 1 byte), marker (bytes16(keccak256(markerString)))
721
+ * @param selectorBytes Uint8Array — function selector (usually 4 bytes)
722
+ * @param arrayOfBytes Uint8Array[] — array of bytes (each element is Uint8Array)
723
+ * @param markerString string — string from which bytes16(keccak256(...)) will be derived
724
+ * @returns Uint8Array — concatenated result
725
+ */
726
+ concatSelectorAndParams(selectorBytes, arrayOfBytes, markerString, partnerKey) {
727
+ const paramsBytes = arrayOfBytes.length === 0 ? new Uint8Array(0) : arrayOfBytes.reduce((acc, arr) => {
728
+ const res = new Uint8Array(acc.length + arr.length);
729
+ res.set(acc, 0);
730
+ res.set(arr, acc.length);
731
+ return res;
732
+ });
733
+ const arrayLengthByte = new Uint8Array([arrayOfBytes.length & 255]);
734
+ const markerHash = import_ethers2.ethers.keccak256(import_ethers2.ethers.toUtf8Bytes(markerString));
735
+ const markerBytes = import_ethers2.ethers.getBytes(markerHash).slice(0, 16);
736
+ const parts = [
737
+ selectorBytes,
738
+ paramsBytes,
739
+ arrayLengthByte,
740
+ markerBytes
741
+ ];
742
+ if (partnerKey) {
743
+ const partnerKeyBytes = import_ethers2.ethers.getBytes(partnerKey);
744
+ const partnerFlagHash = import_ethers2.ethers.keccak256(import_ethers2.ethers.toUtf8Bytes("is_partner_fee"));
745
+ const partnerFlagBytes = import_ethers2.ethers.getBytes(partnerFlagHash).slice(0, 16);
746
+ parts.push(partnerKeyBytes, partnerFlagBytes);
747
+ }
748
+ const totalLen = parts.reduce((sum, p) => sum + p.length, 0);
749
+ const out = new Uint8Array(totalLen);
750
+ let offset = 0;
751
+ for (const p of parts) {
752
+ out.set(p, offset);
753
+ offset += p.length;
754
+ }
755
+ return out;
756
+ }
757
+ };
758
+
759
+ // src/controllers/swap-sdk.controller.ts
760
+ var DEFAULT_SETTINGS = {
761
+ maxSlippage: "0.5",
762
+ swapDeadline: 20
763
+ };
764
+ var SwapSdkController = class {
765
+ constructor(options) {
766
+ this.state = {
767
+ loader: null
768
+ };
769
+ this.input = {
770
+ fromToken: null,
771
+ toToken: null,
772
+ amount: void 0,
773
+ isOutputAmount: false,
774
+ settings: DEFAULT_SETTINGS
775
+ };
776
+ this.options = options;
777
+ this.initServices();
778
+ }
779
+ initServices() {
780
+ this.walletService = new WalletService(
781
+ this.options.networkConfig,
782
+ this.options.walletProvider
783
+ );
784
+ this.swapService = new SwapService(
785
+ this.walletService.getProvider(),
786
+ this.options.networkConfig,
787
+ this.options
788
+ );
789
+ }
790
+ async setChange(patch) {
791
+ const next = {
792
+ ...this.state,
793
+ ...patch
794
+ };
795
+ this.state = next;
796
+ if (typeof this.options.onChange === "function") {
797
+ await this.options.onChange(next, patch);
798
+ }
799
+ }
800
+ async connectWallet(injectedProvider) {
801
+ const address = await this.walletService.connect(injectedProvider);
802
+ const signer = this.walletService.getSigner();
803
+ if (signer) this.swapService.setSigner(signer);
804
+ return address;
805
+ }
806
+ disconnectWallet() {
807
+ this.walletService.disconnect();
808
+ this.swapService.setSigner(null);
809
+ }
810
+ getState() {
811
+ return this.state;
812
+ }
813
+ get settings() {
814
+ return {
815
+ ...DEFAULT_SETTINGS,
816
+ ...this.input.settings || {}
817
+ };
818
+ }
819
+ async calculateQuoteIfNeeded() {
820
+ const { fromToken, toToken, amount, isOutputAmount } = this.input;
821
+ if (!fromToken || !toToken || !amount || amount <= 0) {
822
+ return;
823
+ }
824
+ await this.setChange({ loader: 1 /* CALCULATING_QUOTE */, error: void 0 });
825
+ try {
826
+ const tradeResult = await this.swapService.calculateTrade(
827
+ fromToken,
828
+ toToken,
829
+ String(amount),
830
+ isOutputAmount == true,
831
+ // isOutputAmount: true for input amount, false for output amount
832
+ this.settings.maxSlippage
833
+ );
834
+ await this.setChange({
835
+ computed: tradeResult.computed,
836
+ tradeInfo: tradeResult.trade,
837
+ loader: null
838
+ });
839
+ } catch (error) {
840
+ await this.setChange({ error: error?.message || String(error), loader: null });
841
+ }
842
+ }
843
+ async setData(input) {
844
+ this.input = {
845
+ ...this.input,
846
+ ...input
847
+ };
848
+ await this.calculateQuoteIfNeeded();
849
+ return this.getState();
850
+ }
851
+ async isNeedApproval() {
852
+ if (!this.input || !this.walletService.isConnected() || !this.swapService) throw new Error("Wallet not connected or input missing");
853
+ const { fromToken, amount } = this.input;
854
+ if (!fromToken || amount === void 0 || !this.state.computed?.amountInRaw) throw new Error("fromToken or amount missing");
855
+ return (await this.swapService.isApprovalNeeded(fromToken, BigInt(this.state.computed.amountInRaw))).isApprovalNeeded;
856
+ }
857
+ async approveIfNeeded() {
858
+ if (!this.input || !this.walletService.isConnected()) throw new Error("Wallet not connected or input missing");
859
+ const { fromToken, amount } = this.input;
860
+ if (!fromToken || amount === void 0 || !this.state.computed?.amountInRaw) throw new Error("fromToken or amount missing");
861
+ await this.setChange({ loader: 2 /* APPROVING */ });
862
+ try {
863
+ const tx = await this.swapService?.approveIfNeedApproval(
864
+ fromToken,
865
+ BigInt(this.state.computed.amountInRaw)
866
+ );
867
+ let receipt;
868
+ if (tx) {
869
+ await this.setChange({ approveTxHash: tx.hash });
870
+ receipt = await tx.wait();
871
+ if (!receipt) {
872
+ throw new Error("Receipt not found, Please try again");
873
+ }
874
+ if (receipt.status != 1) {
875
+ throw new Error("Transaction Rejected");
876
+ }
877
+ }
878
+ return receipt?.hash;
879
+ } catch (error) {
880
+ await this.setChange({ error: error?.message || String(error), loader: null });
881
+ throw error;
882
+ }
883
+ }
884
+ async swap() {
885
+ try {
886
+ await this.setChange({
887
+ txHash: void 0,
888
+ approveTxHash: void 0
889
+ });
890
+ const { fromToken, toToken, amount } = this.input;
891
+ if (!fromToken || !toToken || amount === void 0) throw new Error("Tokens or amount not set");
892
+ await this.approveIfNeeded();
893
+ await this.setChange({ loader: 3 /* SWAPPING */ });
894
+ const trade = this.state.tradeInfo;
895
+ if (!trade) throw new Error("Trade info missing - calculate quote first");
896
+ const path = trade.route.path.map((token) => token.address);
897
+ if (path.length === 0) throw new Error("Trade path missing");
898
+ const computed = this.state.computed;
899
+ if (!computed) throw new Error("Computed amounts missing");
900
+ const transaction = await this.swapService.swapTokens(
901
+ fromToken,
902
+ toToken,
903
+ computed.maxAmountInRaw || computed.amountInRaw,
904
+ computed.minAmountOutRaw || computed.amountOutRaw,
905
+ path,
906
+ this.input.isOutputAmount == true,
907
+ this.settings.swapDeadline
908
+ );
909
+ await this.setChange({ txHash: transaction.hash });
910
+ const receipt = await transaction.wait();
911
+ if (!receipt) {
912
+ throw new Error("Receipt not found, Please try again");
913
+ }
914
+ if (receipt.status != 1) {
915
+ throw new Error("Transaction Rejected");
916
+ }
917
+ await this.setChange({
918
+ loader: null
919
+ });
920
+ return receipt.hash;
921
+ } catch (error) {
922
+ await this.setChange({ error: error?.message || String(error), loader: null });
923
+ throw error;
924
+ }
925
+ }
926
+ async getPartnerFee() {
927
+ return Number(await this.swapService.loadPartnerFee()) / Number(PARTNER_FEE_BPS_DIVISOR);
928
+ }
929
+ async getTokensFromGraph(limit = 100, search) {
930
+ if (!this.swapService) {
931
+ throw new Error("Swap Service not exists");
932
+ }
933
+ return await this.swapService.getTokensFromGraph(limit, search);
934
+ }
935
+ get currentNetworkConfig() {
936
+ return this.options.networkConfig;
937
+ }
938
+ };
939
+
940
+ // src/config/networks.ts
941
+ var NETWORKS = {
942
+ "kasplex-testnet": {
943
+ name: "Kasplex Test",
944
+ chainId: 167012,
945
+ rpcUrl: "https://rpc.kasplextest.xyz",
946
+ routerAddress: "0x5A410f79f58a11344E3523d99820Cf231bc888bd",
947
+ factoryAddress: "0x772B3321B37C1a9aeF0Da1B5A6453E1C2A264beF",
948
+ proxyAddress: "0xbE448f863d2bB7bCcD9185A854DF2D8d63498dB0",
949
+ wethAddress: "0x654A3287c317D4Fc6e8482FeF523Dc4572b563AA",
950
+ graphEndpoint: "https://dev-graph-kasplex.kaspa.com/subgraphs/name/uniswap-v2",
951
+ blockExplorerUrl: "https://explorer.testnet.kasplextest.xyz",
952
+ additionalJsonRpcApiProviderOptionsOptions: {
953
+ batchMaxCount: 1,
954
+ batchMaxSize: 1,
955
+ batchStallTime: 0
956
+ }
957
+ }
958
+ // Add more networks as needed
959
+ };
960
+
961
+ // src/index.ts
962
+ function createKaspaComSwapController(options) {
963
+ let resolvedOptions;
964
+ if ("networkConfig" in options && typeof options.networkConfig === "string") {
965
+ const networkConfig = NETWORKS[options.networkConfig];
966
+ if (!networkConfig) throw new Error(`Unknown network key: ${options.networkConfig}`);
967
+ resolvedOptions = { ...options, networkConfig };
968
+ } else {
969
+ resolvedOptions = options;
970
+ }
971
+ return new SwapSdkController(resolvedOptions);
972
+ }
973
+ // Annotate the CommonJS export names for ESM import in node:
974
+ 0 && (module.exports = {
975
+ DEFAULT_SWAP_SETTINGS,
976
+ LoaderStatuses,
977
+ NETWORKS,
978
+ SwapSdkController,
979
+ SwapService,
980
+ WalletService,
981
+ createKaspaComSwapController
982
+ });