@7kprotocol/sdk-ts 3.4.1 → 3.4.2-beta.1

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 (91) hide show
  1. package/lib/cjs/features/limitDca/placeLimitOrder.js +8 -3
  2. package/lib/cjs/features/swap/buildTx.js +101 -66
  3. package/lib/cjs/features/swap/buildTxV2.js +225 -0
  4. package/lib/cjs/features/swap/config.js +13 -7
  5. package/lib/cjs/features/swap/getQuote.js +3 -2
  6. package/lib/cjs/features/swap/index.js +1 -0
  7. package/lib/cjs/index.js +6 -2
  8. package/lib/cjs/libs/protocols/cetus_dlmm.js +48 -0
  9. package/lib/cjs/libs/protocols/index.js +2 -0
  10. package/lib/cjs/types/features/limitDca/placeLimitOrder.d.ts +2 -1
  11. package/lib/cjs/types/features/limitDca/placeLimitOrder.d.ts.map +1 -1
  12. package/lib/cjs/types/features/swap/buildTx.d.ts +14 -1
  13. package/lib/cjs/types/features/swap/buildTx.d.ts.map +1 -1
  14. package/lib/cjs/types/features/swap/buildTxV2.d.ts +20 -0
  15. package/lib/cjs/types/features/swap/buildTxV2.d.ts.map +1 -0
  16. package/lib/cjs/types/features/swap/config.d.ts.map +1 -1
  17. package/lib/cjs/types/features/swap/getQuote.d.ts +3 -1
  18. package/lib/cjs/types/features/swap/getQuote.d.ts.map +1 -1
  19. package/lib/cjs/types/features/swap/index.d.ts +1 -0
  20. package/lib/cjs/types/features/swap/index.d.ts.map +1 -1
  21. package/lib/cjs/types/index.d.ts +4 -2
  22. package/lib/cjs/types/index.d.ts.map +1 -1
  23. package/lib/cjs/types/libs/protocols/base.d.ts +4 -5
  24. package/lib/cjs/types/libs/protocols/base.d.ts.map +1 -1
  25. package/lib/cjs/types/libs/protocols/cetus_dlmm.d.ts +9 -0
  26. package/lib/cjs/types/libs/protocols/cetus_dlmm.d.ts.map +1 -0
  27. package/lib/cjs/types/libs/protocols/index.d.ts +2 -0
  28. package/lib/cjs/types/libs/protocols/index.d.ts.map +1 -1
  29. package/lib/cjs/types/libs/protocols/steamm/index.d.ts +3 -12
  30. package/lib/cjs/types/libs/protocols/steamm/index.d.ts.map +1 -1
  31. package/lib/cjs/types/libs/swapWithRoute.d.ts +3 -4
  32. package/lib/cjs/types/libs/swapWithRoute.d.ts.map +1 -1
  33. package/lib/cjs/types/types/aggregator.d.ts +5 -1
  34. package/lib/cjs/types/types/aggregator.d.ts.map +1 -1
  35. package/lib/cjs/types/types/tx.d.ts +13 -0
  36. package/lib/cjs/types/types/tx.d.ts.map +1 -1
  37. package/lib/cjs/types/utils/sui.d.ts +1 -0
  38. package/lib/cjs/types/utils/sui.d.ts.map +1 -1
  39. package/lib/cjs/utils/sui.js +7 -0
  40. package/lib/esm/features/limitDca/placeLimitOrder.js +8 -3
  41. package/lib/esm/features/swap/buildTx.js +95 -66
  42. package/lib/esm/features/swap/buildTxV2.js +219 -0
  43. package/lib/esm/features/swap/config.js +13 -7
  44. package/lib/esm/features/swap/getQuote.js +3 -2
  45. package/lib/esm/features/swap/index.js +1 -0
  46. package/lib/esm/index.mjs +12 -10
  47. package/lib/esm/libs/protocols/cetus_dlmm.js +44 -0
  48. package/lib/esm/libs/protocols/index.js +2 -0
  49. package/lib/esm/types/features/limitDca/placeLimitOrder.d.ts +2 -1
  50. package/lib/esm/types/features/limitDca/placeLimitOrder.d.ts.map +1 -1
  51. package/lib/esm/types/features/swap/buildTx.d.ts +14 -1
  52. package/lib/esm/types/features/swap/buildTx.d.ts.map +1 -1
  53. package/lib/esm/types/features/swap/buildTxV2.d.ts +20 -0
  54. package/lib/esm/types/features/swap/buildTxV2.d.ts.map +1 -0
  55. package/lib/esm/types/features/swap/config.d.ts.map +1 -1
  56. package/lib/esm/types/features/swap/getQuote.d.ts +3 -1
  57. package/lib/esm/types/features/swap/getQuote.d.ts.map +1 -1
  58. package/lib/esm/types/features/swap/index.d.ts +1 -0
  59. package/lib/esm/types/features/swap/index.d.ts.map +1 -1
  60. package/lib/esm/types/index.d.ts +4 -2
  61. package/lib/esm/types/index.d.ts.map +1 -1
  62. package/lib/esm/types/libs/protocols/base.d.ts +4 -5
  63. package/lib/esm/types/libs/protocols/base.d.ts.map +1 -1
  64. package/lib/esm/types/libs/protocols/cetus_dlmm.d.ts +9 -0
  65. package/lib/esm/types/libs/protocols/cetus_dlmm.d.ts.map +1 -0
  66. package/lib/esm/types/libs/protocols/index.d.ts +2 -0
  67. package/lib/esm/types/libs/protocols/index.d.ts.map +1 -1
  68. package/lib/esm/types/libs/protocols/steamm/index.d.ts +3 -12
  69. package/lib/esm/types/libs/protocols/steamm/index.d.ts.map +1 -1
  70. package/lib/esm/types/libs/swapWithRoute.d.ts +3 -4
  71. package/lib/esm/types/libs/swapWithRoute.d.ts.map +1 -1
  72. package/lib/esm/types/types/aggregator.d.ts +5 -1
  73. package/lib/esm/types/types/aggregator.d.ts.map +1 -1
  74. package/lib/esm/types/types/tx.d.ts +13 -0
  75. package/lib/esm/types/types/tx.d.ts.map +1 -1
  76. package/lib/esm/types/utils/sui.d.ts +1 -0
  77. package/lib/esm/types/utils/sui.d.ts.map +1 -1
  78. package/lib/esm/utils/sui.js +7 -0
  79. package/package.json +3 -3
  80. package/lib/cjs/libs/getCoinOjectIdsByAmount.js +0 -65
  81. package/lib/cjs/libs/getSplitCoinForTx.js +0 -33
  82. package/lib/cjs/types/libs/getCoinOjectIdsByAmount.d.ts +0 -7
  83. package/lib/cjs/types/libs/getCoinOjectIdsByAmount.d.ts.map +0 -1
  84. package/lib/cjs/types/libs/getSplitCoinForTx.d.ts +0 -6
  85. package/lib/cjs/types/libs/getSplitCoinForTx.d.ts.map +0 -1
  86. package/lib/esm/libs/getCoinOjectIdsByAmount.js +0 -61
  87. package/lib/esm/libs/getSplitCoinForTx.js +0 -29
  88. package/lib/esm/types/libs/getCoinOjectIdsByAmount.d.ts +0 -7
  89. package/lib/esm/types/libs/getCoinOjectIdsByAmount.d.ts.map +0 -1
  90. package/lib/esm/types/libs/getSplitCoinForTx.d.ts +0 -6
  91. package/lib/esm/types/libs/getSplitCoinForTx.d.ts.map +0 -1
@@ -1,15 +1,13 @@
1
- import { Transaction, } from "@mysten/sui/transactions";
1
+ import { coinWithBalance, Transaction, } from "@mysten/sui/transactions";
2
2
  import { isValidSuiAddress, toBase64, toHex } from "@mysten/sui/utils";
3
3
  import { Config } from "../../config";
4
4
  import { _7K_CONFIG, _7K_PACKAGE_ID, _7K_VAULT } from "../../constants/_7k";
5
- import { getSplitCoinForTx } from "../../libs/getSplitCoinForTx";
6
5
  import { groupSwapRoutes } from "../../libs/groupSwapRoutes";
7
6
  import { sponsorBluefinX } from "../../libs/protocols/bluefinx/client";
8
7
  import { BluefinXTx } from "../../libs/protocols/bluefinx/types";
9
8
  import { swapWithRoute } from "../../libs/swapWithRoute";
10
9
  import { isBluefinXRouting, } from "../../types/aggregator";
11
10
  import { SuiUtils } from "../../utils/sui";
12
- import { denormalizeTokenType } from "../../utils/token";
13
11
  import { getConfig } from "./config";
14
12
  import { ORACLE_BASED_SOURCES } from "./getQuote";
15
13
  export const buildTx = async ({ quoteResponse, accountAddress, slippage, commission: __commission, devInspect, extendTx, isSponsored, }) => {
@@ -19,7 +17,7 @@ export const buildTx = async ({ quoteResponse, accountAddress, slippage, commiss
19
17
  // commission is ignored for bluefinx
20
18
  commissionBps: isBluefinX ? 0 : __commission.commissionBps,
21
19
  };
22
- const { tx: _tx, coinIn } = extendTx || {};
20
+ const { tx: _tx, coinIn: _coinIn } = extendTx || {};
23
21
  let coinOut;
24
22
  if (isBluefinX && devInspect) {
25
23
  throw new Error("BluefinX tx is sponsored, skip devInspect");
@@ -37,16 +35,15 @@ export const buildTx = async ({ quoteResponse, accountAddress, slippage, commiss
37
35
  const routes = groupSwapRoutes(quoteResponse);
38
36
  validateRoutes(routes, isSponsored);
39
37
  const splits = routes.map((group) => group[0]?.amount ?? "0");
40
- let coinData;
41
- if (coinIn) {
42
- coinData = tx.splitCoins(coinIn, splits);
43
- SuiUtils.transferOrDestroyZeroCoin(tx, quoteResponse.tokenIn, coinIn, accountAddress);
44
- }
45
- else {
46
- const { coinData: _data } = await getSplitCoinForTx(accountAddress, quoteResponse.swapAmountWithDecimal, splits, denormalizeTokenType(quoteResponse.tokenIn), tx, devInspect, isSponsored || isBluefinX);
47
- coinData = _data;
48
- }
49
- const pythMap = await updatePythPriceFeedsIfAny(tx, quoteResponse);
38
+ const coinIn = _coinIn ||
39
+ tx.add(coinWithBalance({
40
+ type: quoteResponse.tokenIn,
41
+ balance: BigInt(quoteResponse.swapAmountWithDecimal),
42
+ useGasCoin: !isSponsored && !isBluefinX,
43
+ }));
44
+ const coinData = tx.splitCoins(coinIn, splits);
45
+ SuiUtils.transferOrDestroyZeroCoin(tx, quoteResponse.tokenIn, coinIn, accountAddress);
46
+ const pythMap = await updatePythPriceFeedsIfAny(tx, [quoteResponse]);
50
47
  const coinObjects = [];
51
48
  const config = await getConfig();
52
49
  await Promise.all(routes.map(async (route, index) => {
@@ -64,75 +61,38 @@ export const buildTx = async ({ quoteResponse, accountAddress, slippage, commiss
64
61
  }
65
62
  }));
66
63
  if (coinObjects.length > 0) {
67
- const mergeCoin = coinObjects.length > 1
68
- ? SuiUtils.mergeCoins(coinObjects, tx)
69
- : coinObjects[0];
70
- const returnAmountAfterCommission = (BigInt(10000 - _commission.commissionBps) *
71
- BigInt(quoteResponse.returnAmountWithDecimal)) /
72
- BigInt(10000);
73
- const minReceived = (BigInt(1e9 - +slippage * 1e9) * BigInt(returnAmountAfterCommission)) /
74
- BigInt(1e9);
75
- tx.moveCall({
76
- target: `${_7K_PACKAGE_ID}::settle::settle`,
77
- typeArguments: [quoteResponse.tokenIn, quoteResponse.tokenOut],
78
- arguments: [
79
- tx.object(_7K_CONFIG),
80
- tx.object(_7K_VAULT),
81
- tx.pure.u64(quoteResponse.swapAmountWithDecimal),
82
- mergeCoin,
83
- tx.pure.u64(minReceived), // minimum received
84
- tx.pure.u64(returnAmountAfterCommission), // expected amount out
85
- tx.pure.option("address", isValidSuiAddress(_commission.partner) ? _commission.partner : null),
86
- tx.pure.u64(_commission.commissionBps),
87
- tx.pure.u64(0),
88
- ],
89
- });
64
+ const mergedCoin = tx.add(settle(coinObjects, quoteResponse, Math.floor(+slippage * 10000), _commission));
90
65
  if (!extendTx) {
91
- tx.transferObjects([mergeCoin], tx.pure.address(accountAddress));
66
+ tx.transferObjects([mergedCoin], tx.pure.address(accountAddress));
92
67
  }
93
68
  else {
94
- coinOut = mergeCoin;
69
+ coinOut = mergedCoin;
95
70
  }
96
71
  }
97
72
  if (isBluefinX) {
98
- const extra = quoteResponse.swaps[0].extra;
99
- if (extra.quoteExpiresAtUtcMillis < Date.now()) {
100
- throw new Error("Quote expired");
101
- }
102
- tx.setSenderIfNotSet(accountAddress);
103
- const bytes = await tx.build({
104
- client: Config.getSuiClient(),
105
- onlyTransactionKind: true,
106
- });
107
- const res = await sponsorBluefinX({
108
- quoteId: extra.quoteId,
109
- txBytes: toBase64(bytes),
110
- sender: accountAddress,
111
- });
112
- if (!res.success) {
113
- throw new Error("Sponsor failed");
114
- }
115
73
  return {
116
- tx: new BluefinXTx(res.quoteId, res.data.txBytes),
74
+ tx: await buildBluefinXTx(tx, accountAddress, quoteResponse),
117
75
  coinOut,
118
76
  };
119
77
  }
78
+ tx.setSenderIfNotSet(accountAddress);
120
79
  return { tx, coinOut };
121
80
  };
122
- const getPythPriceFeeds = (res) => {
81
+ export const getPythPriceFeeds = (responses) => {
123
82
  const ids = new Set();
124
- for (const s of res.swaps) {
125
- for (const o of (s.extra?.oracles || [])) {
126
- // FIXME: deprecation price_identifier in the next version
127
- const bytes = o.Pyth?.bytes || o.Pyth?.price_identifier?.bytes;
128
- if (bytes) {
129
- ids.add("0x" + toHex(Uint8Array.from(bytes)));
83
+ for (const res of responses) {
84
+ for (const s of res.swaps) {
85
+ for (const o of s.extra?.oracles || []) {
86
+ const bytes = o.Pyth?.price_identifier?.bytes || o.Pyth?.bytes;
87
+ if (bytes) {
88
+ ids.add("0x" + toHex(Uint8Array.from(bytes)));
89
+ }
130
90
  }
131
91
  }
132
92
  }
133
93
  return Array.from(ids);
134
94
  };
135
- const updatePythPriceFeedsIfAny = async (tx, quoteResponse) => {
95
+ export const updatePythPriceFeedsIfAny = async (tx, quoteResponse) => {
136
96
  // update oracles price if any
137
97
  const pythMap = {};
138
98
  const pythIds = getPythPriceFeeds(quoteResponse);
@@ -145,7 +105,7 @@ const updatePythPriceFeedsIfAny = async (tx, quoteResponse) => {
145
105
  }
146
106
  return pythMap;
147
107
  };
148
- const validateRoutes = (routes, isSponsored) => {
108
+ export const validateRoutes = (routes, isSponsored) => {
149
109
  if (!isSponsored) {
150
110
  return;
151
111
  }
@@ -154,3 +114,72 @@ const validateRoutes = (routes, isSponsored) => {
154
114
  throw new Error("Oracle based sources are not supported for sponsored tx");
155
115
  }
156
116
  };
117
+ export const getExpectedReturn = (returnAmount, slippageBps, commissionBps, tipBps = 0) => {
118
+ if (slippageBps > 10000) {
119
+ throw new Error("Slippage must be less than 100%");
120
+ }
121
+ if (commissionBps > 10000) {
122
+ throw new Error("Commission must be less than 100%");
123
+ }
124
+ if (tipBps > 10000) {
125
+ throw new Error("Tip must be less than 100%");
126
+ }
127
+ const returnAmountWithDecimal = BigInt(returnAmount);
128
+ const tipAmountWithDecimal = (returnAmountWithDecimal * BigInt(tipBps || 0)) / 10000n;
129
+ const commissionAmountWithDecimal = ((returnAmountWithDecimal - tipAmountWithDecimal) * BigInt(commissionBps)) /
130
+ 10000n;
131
+ const expectedReturnWithDecimal = returnAmountWithDecimal -
132
+ tipAmountWithDecimal -
133
+ commissionAmountWithDecimal;
134
+ const minAmountWithDecimal = (expectedReturnWithDecimal * BigInt(1e4 - slippageBps)) / 10000n;
135
+ return {
136
+ tipAmount: tipAmountWithDecimal,
137
+ minAmount: minAmountWithDecimal,
138
+ commissionAmount: commissionAmountWithDecimal,
139
+ expectedAmount: expectedReturnWithDecimal.toString(10),
140
+ };
141
+ };
142
+ export const settle = (coinObjects, quoteResponse, slippageBps, _commission) => {
143
+ return (tx) => {
144
+ const mergeCoin = coinObjects.length > 1
145
+ ? (tx.mergeCoins(coinObjects[0], coinObjects.slice(1)), coinObjects[0])
146
+ : coinObjects[0];
147
+ const { minAmount, expectedAmount } = getExpectedReturn(quoteResponse.returnAmountWithDecimal, slippageBps, _commission.commissionBps);
148
+ tx.moveCall({
149
+ target: `${_7K_PACKAGE_ID}::settle::settle`,
150
+ typeArguments: [quoteResponse.tokenIn, quoteResponse.tokenOut],
151
+ arguments: [
152
+ tx.object(_7K_CONFIG),
153
+ tx.object(_7K_VAULT),
154
+ tx.pure.u64(quoteResponse.swapAmountWithDecimal),
155
+ mergeCoin,
156
+ tx.pure.u64(minAmount), // minimum received
157
+ tx.pure.u64(expectedAmount), // expected amount out
158
+ tx.pure.option("address", isValidSuiAddress(_commission.partner) ? _commission.partner : null),
159
+ tx.pure.u64(_commission.commissionBps),
160
+ tx.pure.u64(0),
161
+ ],
162
+ });
163
+ return mergeCoin;
164
+ };
165
+ };
166
+ export const buildBluefinXTx = async (tx, accountAddress, quoteResponse) => {
167
+ const extra = quoteResponse.swaps[0].extra;
168
+ if (extra.quoteExpiresAtUtcMillis < Date.now()) {
169
+ throw new Error("Quote expired");
170
+ }
171
+ tx.setSenderIfNotSet(accountAddress);
172
+ const bytes = await tx.build({
173
+ client: Config.getSuiClient(),
174
+ onlyTransactionKind: true,
175
+ });
176
+ const res = await sponsorBluefinX({
177
+ quoteId: extra.quoteId,
178
+ txBytes: toBase64(bytes),
179
+ sender: accountAddress,
180
+ });
181
+ if (!res.success) {
182
+ throw new Error("Sponsor failed");
183
+ }
184
+ return new BluefinXTx(res.quoteId, res.data.txBytes);
185
+ };
@@ -0,0 +1,219 @@
1
+ import { coinWithBalance, Transaction, } from "@mysten/sui/transactions";
2
+ import { isValidSuiAddress, normalizeStructTag } from "@mysten/sui/utils";
3
+ import { groupSwapRoutes } from "../../libs/groupSwapRoutes";
4
+ import { swapWithRoute } from "../../libs/swapWithRoute";
5
+ import { isBluefinXRouting, } from "../../types/aggregator";
6
+ import { SuiUtils } from "../../utils/sui";
7
+ import { buildBluefinXTx, settle, updatePythPriceFeedsIfAny, validateRoutes, } from "./buildTx";
8
+ import { getConfig } from "./config";
9
+ /**
10
+ * Wave-based transaction builder that optimizes swap execution by:
11
+ * 1. Grouping swaps into execution waves based on readiness
12
+ * 2. Merging redundant swaps to the same pool within each wave
13
+ * 3. Processing waves sequentially, passing intermediate tokens between waves
14
+ */
15
+ export const buildTxV2 = async ({ quoteResponse, accountAddress, slippage, commission: __commission, devInspect, extendTx, isSponsored, }) => {
16
+ const isBluefinX = isBluefinXRouting(quoteResponse);
17
+ const _commission = {
18
+ ...__commission,
19
+ commissionBps: isBluefinX ? 0 : __commission.commissionBps,
20
+ };
21
+ const { tx: _tx, coinIn: _coinIn } = extendTx || {};
22
+ let coinOut;
23
+ if (isBluefinX && devInspect) {
24
+ throw new Error("BluefinX tx is sponsored, skip devInspect");
25
+ }
26
+ if (!accountAddress) {
27
+ throw new Error("Sender address is required");
28
+ }
29
+ if (!quoteResponse.routes) {
30
+ throw new Error("Invalid quote response: 'routes' are required");
31
+ }
32
+ if (!isValidSuiAddress(_commission.partner)) {
33
+ throw new Error("Invalid commission partner address");
34
+ }
35
+ const tx = _tx || new Transaction();
36
+ const routes = groupSwapRoutes(quoteResponse);
37
+ validateRoutes(routes, isSponsored);
38
+ const splits = routes.map((group) => group[0]?.amount ?? "0");
39
+ const coinIn = _coinIn ||
40
+ tx.add(coinWithBalance({
41
+ type: quoteResponse.tokenIn,
42
+ balance: BigInt(quoteResponse.swapAmountWithDecimal),
43
+ useGasCoin: !isSponsored && !isBluefinX,
44
+ }));
45
+ const coinData = tx.splitCoins(coinIn, splits);
46
+ SuiUtils.transferOrDestroyZeroCoin(tx, quoteResponse.tokenIn, coinIn, accountAddress);
47
+ const pythMap = await updatePythPriceFeedsIfAny(tx, [quoteResponse]);
48
+ const config = await getConfig();
49
+ const finalCoins = await optimize(pythMap, config, routes, coinData, tx, accountAddress);
50
+ // Merge all final coins
51
+ if (finalCoins.length > 0) {
52
+ const mergeCoin = tx.add(settle(finalCoins, quoteResponse, Math.floor(+slippage * 10000), _commission));
53
+ if (!extendTx) {
54
+ tx.transferObjects([mergeCoin], tx.pure.address(accountAddress));
55
+ }
56
+ else {
57
+ coinOut = mergeCoin;
58
+ }
59
+ }
60
+ if (isBluefinX) {
61
+ return {
62
+ tx: await buildBluefinXTx(tx, accountAddress, quoteResponse),
63
+ coinOut,
64
+ };
65
+ }
66
+ tx.setSenderIfNotSet(accountAddress);
67
+ return { tx, coinOut };
68
+ };
69
+ export const optimize = async (pythMap, config, routes, coinData, tx, accountAddress) => {
70
+ // Initialize route states with split coins
71
+ const routeStates = routes.map((route, index) => ({
72
+ routeIndex: index,
73
+ currentHopIndex: 0,
74
+ currentCoin: coinData[index],
75
+ swaps: route,
76
+ completed: false,
77
+ }));
78
+ const finalCoins = [];
79
+ const coinTypeOut = routes[0][routes[0].length - 1].assetOut;
80
+ // Execute swaps in waves
81
+ while (true) {
82
+ const readyHops = [];
83
+ routeStates.forEach((state) => {
84
+ if (state.completed)
85
+ return;
86
+ if (state.currentHopIndex >= state.swaps.length) {
87
+ state.completed = true;
88
+ finalCoins.push(state.currentCoin);
89
+ return;
90
+ }
91
+ const swap = state.swaps[state.currentHopIndex];
92
+ readyHops.push({
93
+ routeIndex: state.routeIndex,
94
+ hopIndex: state.currentHopIndex,
95
+ swap,
96
+ inputCoin: state.currentCoin,
97
+ });
98
+ });
99
+ if (readyHops.length === 0)
100
+ break;
101
+ // Group hops by pool for merging opportunities
102
+ const poolGroups = new Map();
103
+ readyHops.forEach((hop) => {
104
+ const poolKey = `${hop.swap.poolId}|${hop.swap.assetIn}|${hop.swap.assetOut}`;
105
+ if (!poolGroups.has(poolKey)) {
106
+ poolGroups.set(poolKey, {
107
+ swap: hop.swap,
108
+ hops: [],
109
+ });
110
+ }
111
+ poolGroups.get(poolKey).hops.push(hop);
112
+ });
113
+ // Execute each pool group in this wave
114
+ const wavePromises = [];
115
+ for (const group of poolGroups.values()) {
116
+ const { swap, hops } = group;
117
+ // For merged swaps, we need to combine input coins first
118
+ const wavePromise = (async () => {
119
+ let combinedCoin;
120
+ if (hops.length > 1) {
121
+ // Merge all input coins for this pool
122
+ const inputCoins = hops.map((h) => h.inputCoin);
123
+ tx.mergeCoins(inputCoins[0], inputCoins.slice(1));
124
+ combinedCoin = inputCoins[0];
125
+ }
126
+ else {
127
+ combinedCoin = hops[0].inputCoin;
128
+ }
129
+ // Execute the swap with the combined coin
130
+ const resultCoin = await swapWithRoute({
131
+ route: [swap],
132
+ inputCoinObject: combinedCoin,
133
+ currentAccount: accountAddress,
134
+ tx,
135
+ config,
136
+ pythMap,
137
+ });
138
+ if (!resultCoin) {
139
+ throw new Error(`Swap failed for pool ${swap.poolId}`);
140
+ }
141
+ if (hops[0].swap.assetOut === coinTypeOut) {
142
+ finalCoins.push(resultCoin);
143
+ hops.forEach((hop) => {
144
+ const state = routeStates[hop.routeIndex];
145
+ state.completed = true;
146
+ state.currentHopIndex++;
147
+ });
148
+ return;
149
+ }
150
+ // For merged swaps, we need to split the output proportionally
151
+ if (hops.length > 1) {
152
+ // Split output proportionally (except the last one which gets the remainder)
153
+ const splitAmounts = hops
154
+ .slice(1)
155
+ .map((hop) => hop.swap.returnAmount);
156
+ const splitCoins = splitAmounts.length > 0
157
+ ? [resultCoin, ...tx.splitCoins(resultCoin, splitAmounts)]
158
+ : [resultCoin];
159
+ // Assign split coins back to routes
160
+ hops.forEach((hop, index) => {
161
+ const state = routeStates[hop.routeIndex];
162
+ state.currentCoin = splitCoins[index];
163
+ state.currentHopIndex++;
164
+ });
165
+ }
166
+ else {
167
+ // Single swap - simply update the route state
168
+ const state = routeStates[hops[0].routeIndex];
169
+ state.currentCoin = resultCoin;
170
+ state.currentHopIndex++;
171
+ }
172
+ })();
173
+ wavePromises.push(wavePromise);
174
+ }
175
+ // Wait for all swaps in this wave to complete
176
+ await Promise.all(wavePromises);
177
+ }
178
+ return finalCoins;
179
+ };
180
+ /**
181
+ * execute multiple swap in single transaction
182
+ *
183
+ * User must handle the coins from return
184
+ * @param param - MultiSwapParams
185
+ * @returns a map of coinType to coinObject
186
+ */
187
+ export const multiSwap = async ({ sender, slippageBps, swaps, tx, commission, }) => {
188
+ if (swaps.some((s) => isBluefinXRouting(s.quote))) {
189
+ throw Error("BluefinX routing not supported yet");
190
+ }
191
+ // update oracles price if any
192
+ const pythMap = await updatePythPriceFeedsIfAny(tx, swaps.map((s) => s.quote));
193
+ const map = {};
194
+ const config = await getConfig();
195
+ for (const { quote: sorResponse, coinIn } of swaps) {
196
+ const routes = groupSwapRoutes(sorResponse);
197
+ const splits = routes.map((group) => group[0]?.amount ?? "0");
198
+ const coinData = splits.length === 1
199
+ ? [coinIn]
200
+ : [coinIn, ...tx.splitCoins(coinIn, splits.slice(1))];
201
+ const coinObjects = await optimize(pythMap, config, routes, coinData, tx, sender);
202
+ if (coinObjects.length > 0) {
203
+ const mergeCoin = tx.add(settle(coinObjects, sorResponse, slippageBps, commission));
204
+ if (!map[normalizeStructTag(sorResponse.tokenOut)]) {
205
+ map[normalizeStructTag(sorResponse.tokenOut)] = [];
206
+ }
207
+ map[normalizeStructTag(sorResponse.tokenOut)].push(mergeCoin);
208
+ }
209
+ }
210
+ const result = {};
211
+ for (const [tokenOut, coins] of Object.entries(map)) {
212
+ if (coins.length > 1) {
213
+ tx.mergeCoins(coins[0], coins.slice(1));
214
+ }
215
+ result[tokenOut] = coins[0];
216
+ }
217
+ tx.setSenderIfNotSet(sender);
218
+ return result;
219
+ };
@@ -22,7 +22,7 @@ export const DEFAULT_CONFIG = {
22
22
  },
23
23
  cetus: {
24
24
  name: "Cetus",
25
- package: "0xb2db7142fa83210a7d78d9c12ac49c043b3cbbd482224fea6e3da00aa5a5ae2d",
25
+ package: "0xfbb32ac0fa89a3cb0c56c745b688c6d2a53ac8e43447119ad822763997ffb9c3",
26
26
  globalConfig: "0xdaa46292632c3c4d8f31f23ea0f9b36a28ff3677e9684980e4438403a67a3d8f",
27
27
  },
28
28
  deepbook_v3: {
@@ -75,8 +75,8 @@ export const DEFAULT_CONFIG = {
75
75
  },
76
76
  steamm: {
77
77
  name: "Steamm",
78
- package: "0x4454d95507deb17d5017db11105bd95027d434776af1d0049ce27a3510a9a1ba",
79
- script: "0xbef015f8fe24f324cc4a7939a88c164e78d2d859aa925a75bd8f8472b6ae7d0e",
78
+ package: "0x5ef2a1bca239764c8381ba26b758833060eadb8903682e4fb15e58c6406e2488",
79
+ script: "0x0755429cba577decc090009348987a89f4fb8397da27a3eaafc366794078af7d",
80
80
  oracle: "0xe84b649199654d18c38e727212f5d8dacfc3cf78d60d0a7fc85fd589f280eb2b",
81
81
  },
82
82
  magma: {
@@ -86,16 +86,16 @@ export const DEFAULT_CONFIG = {
86
86
  },
87
87
  haedal_pmm: {
88
88
  name: "Haedal PMM",
89
- package: "0xa0e3b011012b80af4957afa30e556486eb3da0a7d96eeb733cf16ccd3aec32e0",
89
+ package: "0x486622af8a7250a192e6ee97eed4f54e30343b764d9148bf1535b55f85155204",
90
90
  },
91
91
  momentum: {
92
92
  name: "Momentum",
93
- package: "0xc84b1ef2ac2ba5c3018e2b8c956ba5d0391e0e46d1daa1926d5a99a6a42526b4",
93
+ package: "0xcf60a40f45d46fc1e828871a647c1e25a0915dec860d2662eb10fdb382c3c1d1",
94
94
  version: "0x2375a0b1ec12010aaea3b2545acfa2ad34cfbba03ce4b59f4c39e1e25eed1b2a",
95
95
  },
96
96
  bluefinx: {
97
97
  name: "BluefinX",
98
- package: "0xf8870f988ab09be7c5820a856bd5e9da84fc7192e095a7a8829919293b00a36c",
98
+ package: "0x9633d611ea4b3a30751135cede2c7871980955473c1c7c883d43567e7e9b164e",
99
99
  globalConfig: "0xc6b29a60c3924776bedc78df72c127ea52b86aeb655432979a38f13d742dedaa",
100
100
  },
101
101
  sevenk_v1: {
@@ -105,12 +105,18 @@ export const DEFAULT_CONFIG = {
105
105
  },
106
106
  fullsail: {
107
107
  name: "Fullsail",
108
- package: "0xe1b7d5fd116fea5a8f8e85c13754248d56626a8d0a614b7d916c2348d8323149",
108
+ package: "0xb3b98d4fda36acc2c2e66dba61f9149b341c38e97a532af802ebbb0c037b9d1f",
109
109
  globalConfig: "0xe93baa80cb570b3a494cbf0621b2ba96bc993926d34dc92508c9446f9a05d615",
110
110
  rewarderGlobalVault: "0xfb971d3a2fb98bde74e1c30ba15a3d8bef60a02789e59ae0b91660aeed3e64e1",
111
111
  priceProvider: "0x854b2d2c0381bb656ec962f8b443eb082654384cf97885359d1956c7d76e33c9",
112
112
  stats: "0x6822a33d1d971e040c32f7cc74507010d1fe786f7d06ab89135083ddb07d2dc2",
113
113
  },
114
+ cetus_dlmm: {
115
+ name: "Cetus DLMM",
116
+ package: "0xa4c6f46bd6b456e6477bcddf0652e0d2d8fb4767e306533e6e885302ee28cfab",
117
+ globalConfig: "0xf31b605d117f959b9730e8c07b08b856cb05143c5e81d5751c90d2979e82f599",
118
+ version: "0x05370b2d656612dd5759cbe80463de301e3b94a921dfc72dd9daa2ecdeb2d0a8",
119
+ },
114
120
  };
115
121
  let config = DEFAULT_CONFIG;
116
122
  let configTs = 0;
@@ -25,6 +25,7 @@ export const DEFAULT_SOURCES = [
25
25
  "momentum",
26
26
  "sevenk_v1",
27
27
  "fullsail",
28
+ "cetus_dlmm",
28
29
  ];
29
30
  export const ORACLE_BASED_SOURCES = new Set([
30
31
  "obric",
@@ -33,7 +34,7 @@ export const ORACLE_BASED_SOURCES = new Set([
33
34
  "steamm_oracle_quoter",
34
35
  "steamm_oracle_quoter_v2",
35
36
  ]);
36
- export async function getQuote({ tokenIn, tokenOut, amountIn, sources: _sources = DEFAULT_SOURCES, commissionBps, targetPools, excludedPools, taker, isSponsored, }) {
37
+ export async function getQuote({ tokenIn, tokenOut, amountIn, sources: _sources = DEFAULT_SOURCES, commissionBps, targetPools, excludedPools, taker, isSponsored, api, }) {
37
38
  let sources = _sources;
38
39
  if (isSponsored) {
39
40
  sources = _sources.filter((s) => !ORACLE_BASED_SOURCES.has(s));
@@ -53,7 +54,7 @@ export async function getQuote({ tokenIn, tokenOut, amountIn, sources: _sources
53
54
  if (taker) {
54
55
  params.append("taker", taker);
55
56
  }
56
- const response = await fetchClient(`${API_ENDPOINTS.MAIN}/quote?${params}`);
57
+ const response = await fetchClient(`${api || API_ENDPOINTS.MAIN}/quote?${params}`);
57
58
  if (!response.ok) {
58
59
  let responseText;
59
60
  try {
@@ -1,4 +1,5 @@
1
1
  export * from "./buildTx";
2
+ export * from "./buildTxV2";
2
3
  export * from "./estimateGasFee";
3
4
  export * from "./executeTx";
4
5
  export * from "./getQuote";
package/lib/esm/index.mjs CHANGED
@@ -2,24 +2,24 @@ export * from "./types/aggregator";
2
2
  import { Config } from "./config";
3
3
  import { getSuiPrice, getTokenPrice, getTokenPrices } from "./features/prices";
4
4
  import { executeBluefinTx } from "./libs/protocols/bluefinx/client";
5
- import { buildTx, estimateGasFee, executeTx, getQuote, getSwapHistory, DEFAULT_SOURCES, } from "./features/swap";
5
+ import { buildTx, buildTxV2, DEFAULT_SOURCES, estimateGasFee, executeTx, getQuote, getSwapHistory, multiSwap, } from "./features/swap";
6
6
  import { cancelDcaOrder, cancelLimitOrder, claimExpiredLimitOrder, getClosedDcaOrders, getClosedLimitOrders, getDcaOrderExecutions, getOpenDcaOrders, getOpenLimitOrders, placeDcaOrder, placeLimitOrder, } from "./features/limitDca";
7
7
  // avoid breaking changes
8
8
  const getSuiClient = Config.getSuiClient;
9
9
  const setSuiClient = Config.setSuiClient;
10
- export {
10
+ export { buildTx, buildTxV2, cancelDcaOrder, cancelLimitOrder, claimExpiredLimitOrder,
11
11
  // config
12
- Config,
12
+ Config, DEFAULT_SOURCES, estimateGasFee, executeBluefinTx, executeTx, getClosedDcaOrders, getClosedLimitOrders, getDcaOrderExecutions, getOpenDcaOrders, getOpenLimitOrders,
13
+ // swap
14
+ getQuote,
13
15
  // sui client
14
- getSuiClient, setSuiClient,
16
+ getSuiClient, getSuiPrice, getSwapHistory,
15
17
  // prices
16
- getTokenPrice, getTokenPrices, getSuiPrice,
17
- // swap
18
- getQuote, estimateGasFee, buildTx, getSwapHistory, executeTx, executeBluefinTx, DEFAULT_SOURCES,
19
- // limit order
20
- placeLimitOrder, getOpenLimitOrders, cancelLimitOrder, claimExpiredLimitOrder, getClosedLimitOrders,
18
+ getTokenPrice, getTokenPrices, multiSwap,
21
19
  // dca
22
- placeDcaOrder, getOpenDcaOrders, cancelDcaOrder, getClosedDcaOrders, getDcaOrderExecutions, };
20
+ placeDcaOrder,
21
+ // limit order
22
+ placeLimitOrder, setSuiClient, };
23
23
  export default {
24
24
  // config
25
25
  Config,
@@ -34,6 +34,8 @@ export default {
34
34
  getQuote,
35
35
  estimateGasFee,
36
36
  buildTx,
37
+ buildTxV2,
38
+ multiSwap,
37
39
  getSwapHistory,
38
40
  executeTx,
39
41
  executeBluefinTx,
@@ -0,0 +1,44 @@
1
+ import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui/utils";
2
+ import { SuiUtils } from "../../utils/sui";
3
+ import { BaseContract } from "./base";
4
+ export class CetusDLMMContract extends BaseContract {
5
+ async swap(tx) {
6
+ const config = this.config.cetus_dlmm;
7
+ const [x, y, receipt] = tx.moveCall({
8
+ target: `${config.package}::pool::flash_swap`,
9
+ typeArguments: [this.swapInfo.coinX.type, this.swapInfo.coinY.type],
10
+ arguments: [
11
+ tx.object(this.swapInfo.poolId),
12
+ tx.pure.bool(this.swapInfo.swapXtoY),
13
+ tx.pure.bool(true), // exact in
14
+ this.getInputCoinValue(tx),
15
+ tx.object(config.globalConfig),
16
+ tx.object(config.version),
17
+ tx.object(SUI_CLOCK_OBJECT_ID),
18
+ ],
19
+ });
20
+ const debtX = this.swapInfo.swapXtoY
21
+ ? SuiUtils.coinIntoBalance(tx, this.swapInfo.coinX.type, this.inputCoinObject)
22
+ : SuiUtils.zeroBalance(tx, this.swapInfo.coinX.type);
23
+ const debtY = this.swapInfo.swapXtoY
24
+ ? SuiUtils.zeroBalance(tx, this.swapInfo.coinY.type)
25
+ : SuiUtils.coinIntoBalance(tx, this.swapInfo.coinY.type, this.inputCoinObject);
26
+ tx.moveCall({
27
+ target: `${config.package}::pool::repay_flash_swap`,
28
+ typeArguments: [this.swapInfo.coinX.type, this.swapInfo.coinY.type],
29
+ arguments: [
30
+ tx.object(this.swapInfo.poolId),
31
+ debtX,
32
+ debtY,
33
+ receipt,
34
+ tx.object(config.version),
35
+ ],
36
+ });
37
+ const [destroyType, destroyCoin, outType, outBalance] = this.swapInfo
38
+ .swapXtoY
39
+ ? [this.swapInfo.coinX.type, x, this.swapInfo.coinY.type, y]
40
+ : [this.swapInfo.coinY.type, y, this.swapInfo.coinX.type, x];
41
+ SuiUtils.balanceDestroyZero(tx, destroyType, destroyCoin);
42
+ return SuiUtils.coinFromBalance(tx, outType, outBalance);
43
+ }
44
+ }
@@ -3,6 +3,7 @@ import { BluefinContract } from "./bluefin";
3
3
  import { BluefinXContract } from "./bluefinx";
4
4
  import { BluemoveContract } from "./bluemove";
5
5
  import { CetusContract } from "./cetus";
6
+ import { CetusDLMMContract } from "./cetus_dlmm";
6
7
  import { SponsoredDeepBookV3Contract } from "./deepbookV3/sponsored";
7
8
  import { FlowXContract } from "./flowx";
8
9
  import { FlowxV3Contract } from "./flowxV3";
@@ -43,4 +44,5 @@ export const ProtocolContract = {
43
44
  bluefinx: BluefinXContract,
44
45
  sevenk_v1: SevenKV1,
45
46
  fullsail: FullsailContract,
47
+ cetus_dlmm: CetusDLMMContract,
46
48
  };
@@ -1,3 +1,4 @@
1
+ import { Transaction } from "@mysten/sui/transactions";
1
2
  export interface PlaceLimitOrderParams {
2
3
  accountAddress: string;
3
4
  payCoinType: string;
@@ -8,5 +9,5 @@ export interface PlaceLimitOrderParams {
8
9
  expireTs: bigint;
9
10
  devInspect?: boolean;
10
11
  }
11
- export declare function placeLimitOrder({ accountAddress, payCoinType, targetCoinType, payCoinAmount, rate, slippage, expireTs, devInspect, }: PlaceLimitOrderParams): Promise<import("@mysten/sui/dist/cjs/transactions").Transaction>;
12
+ export declare function placeLimitOrder({ accountAddress, payCoinType, targetCoinType, payCoinAmount, rate, slippage, expireTs, devInspect, }: PlaceLimitOrderParams): Promise<Transaction>;
12
13
  //# sourceMappingURL=placeLimitOrder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"placeLimitOrder.d.ts","sourceRoot":"","sources":["../../../../../src/features/limitDca/placeLimitOrder.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAsB,eAAe,CAAC,EACpC,cAAc,EACd,WAAW,EACX,cAAc,EACd,aAAa,EACb,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,UAAU,GACX,EAAE,qBAAqB,oEAwBvB"}
1
+ {"version":3,"file":"placeLimitOrder.d.ts","sourceRoot":"","sources":["../../../../../src/features/limitDca/placeLimitOrder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGxE,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAsB,eAAe,CAAC,EACpC,cAAc,EACd,WAAW,EACX,cAAc,EACd,aAAa,EACb,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,UAAU,GACX,EAAE,qBAAqB,wBAyBvB"}