@scallop-io/scallop-swap-sdk 1.0.0

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.
@@ -0,0 +1,33 @@
1
+ export interface Coin {
2
+ type: string;
3
+ amount: bigint;
4
+ }
5
+
6
+ interface SwapCoinInfo {
7
+ coinIn: Coin;
8
+ coinOut: Coin;
9
+ }
10
+
11
+ interface SwapPath extends SwapCoinInfo {
12
+ protocolName: string;
13
+ }
14
+
15
+ export interface SwapRoute extends SwapCoinInfo {
16
+ paths: SwapPath[];
17
+ splitPercentage?: number; // for multi route swap
18
+ }
19
+
20
+ /**
21
+ * Common interface for route result (for frontend display purposes)
22
+ */
23
+ export interface FormattedRouteResult {
24
+ routes: SwapRoute[];
25
+ coinIn: Coin; // Input coin info
26
+ coinOut: Coin; // Output coin info
27
+ name: string; // Aggregator name
28
+ }
29
+
30
+ export type FetchRouteResult<T> = {
31
+ rawRouteResult: T; // The raw route result from the aggregator
32
+ formattedResult: FormattedRouteResult; // The formatted route result for frontend display
33
+ };
package/src/sdk/7k.ts ADDED
@@ -0,0 +1,231 @@
1
+ import {
2
+ buildTx,
3
+ getQuote,
4
+ setSuiClient,
5
+ Commission,
6
+ QuoteResponse,
7
+ SorRoute,
8
+ SourceDex,
9
+ } from '@7kprotocol/sdk-ts';
10
+ import { Transaction, TransactionObjectArgument } from '@scallop-io/sui-kit';
11
+ import { SwapSdkBase } from 'src/class';
12
+ import {
13
+ FetchRouteParams,
14
+ FetchRouteResult,
15
+ FormattedRouteResult,
16
+ SwapParams,
17
+ SwapRoute,
18
+ SwapSdkConstructorParams,
19
+ } from 'src/interface';
20
+ import { BigNumber } from 'bignumber.js';
21
+ import { transformProperCase } from 'src/utils';
22
+
23
+ // From 7k SDK
24
+ interface Params {
25
+ tokenIn: string;
26
+ tokenOut: string;
27
+ amountIn: string;
28
+ /**
29
+ * @default DEFAULT_SOURCES
30
+ * @warning BluefinX must be explicitly specified if needed
31
+ * @example ```sources: [...DEFAULT_SOURCES, "bluefinx"]``` */
32
+ sources?: SourceDex[];
33
+ commissionBps?: number;
34
+ /** Limit the route to a specific set of pools */
35
+ targetPools?: string[];
36
+ /** Exclude a specific set of pools from the route */
37
+ excludedPools?: string[];
38
+ /** The taker address, required for bluefinx */
39
+ taker?: string;
40
+ /** If true, excludes all liquidity sources that depend on pyth price feeds - pyth client use tx.gas to pay the fee*/
41
+ isSponsored?: boolean;
42
+ }
43
+
44
+ export class _7kSwap extends SwapSdkBase<
45
+ SourceDex,
46
+ QuoteResponse,
47
+ Omit<Params, 'tokenIn' | 'tokenOut' | 'amountIn'>
48
+ > {
49
+ commission: Commission;
50
+ constructor(params: SwapSdkConstructorParams & { commission?: Commission }) {
51
+ super('7k', params);
52
+ this.commission = params.commission ?? {
53
+ partner: this.walletAddress,
54
+ commissionBps: 0,
55
+ };
56
+ setSuiClient(this.client);
57
+ }
58
+
59
+ setFetchRouteSettings(
60
+ settings: Omit<Params, 'tokenIn' | 'tokenOut' | 'amountIn'>
61
+ ) {
62
+ this.fetchSettings = settings;
63
+ }
64
+
65
+ private createRoute(
66
+ sorRoute: SorRoute[],
67
+ coinInDecimal: number,
68
+ coinOutDecimal: number
69
+ ): SwapRoute[] {
70
+ const routes: SwapRoute[] = [];
71
+ sorRoute.forEach((r, idx) => {
72
+ routes.push({
73
+ paths: [],
74
+ coinIn: {
75
+ type: r.tokenIn,
76
+ amount: BigInt(
77
+ BigNumber(r.tokenInAmount)
78
+ .multipliedBy(coinInDecimal)
79
+ .integerValue()
80
+ .toString()
81
+ ),
82
+ },
83
+ coinOut: {
84
+ type: r.tokenOut,
85
+ amount: BigInt(
86
+ BigNumber(r.tokenOutAmount)
87
+ .multipliedBy(coinOutDecimal)
88
+ .integerValue()
89
+ .toString()
90
+ ),
91
+ },
92
+ });
93
+
94
+ r.hops.forEach((h) => {
95
+ routes[idx].paths.push({
96
+ protocolName: transformProperCase(
97
+ h.pool.type.toLowerCase() as Lowercase<string>
98
+ ),
99
+ coinIn: {
100
+ type: h.tokenIn,
101
+ amount: BigInt(
102
+ BigNumber(h.tokenInAmount)
103
+ .multipliedBy(coinInDecimal)
104
+ .integerValue()
105
+ .toString()
106
+ ),
107
+ },
108
+ coinOut: {
109
+ type: h.tokenOut,
110
+ amount: BigInt(
111
+ BigNumber(h.tokenOutAmount)
112
+ .multipliedBy(coinOutDecimal)
113
+ .integerValue()
114
+ .toString()
115
+ ),
116
+ },
117
+ });
118
+ });
119
+ });
120
+
121
+ const totalSwapAmount = routes
122
+ .reduce((acc, r) => acc + r.coinIn.amount, BigInt(0))
123
+ .toString();
124
+ routes.forEach((r) => {
125
+ r.splitPercentage = BigNumber(r.coinIn.amount.toString())
126
+ .div(totalSwapAmount)
127
+ .times(100)
128
+ .toNumber();
129
+ });
130
+ return routes;
131
+ }
132
+
133
+ private parseRawRouteToFormattedResult(
134
+ rawRoute: QuoteResponse
135
+ ): FormattedRouteResult {
136
+ if (!rawRoute.routes) {
137
+ throw new Error(`${this.name} fetch route returned empty sorRoute`);
138
+ }
139
+ return {
140
+ routes: this.createRoute(
141
+ rawRoute.routes,
142
+ BigNumber(rawRoute.swapAmountWithDecimal)
143
+ .dividedBy(rawRoute.swapAmount)
144
+ .toNumber(),
145
+ BigNumber(rawRoute.returnAmountAfterCommissionWithDecimal)
146
+ .dividedBy(rawRoute.returnAmount)
147
+ .toNumber()
148
+ ),
149
+ coinIn: {
150
+ type: rawRoute.tokenIn,
151
+ amount: BigInt(rawRoute.swapAmountWithDecimal),
152
+ },
153
+ coinOut: {
154
+ type: rawRoute.tokenOut,
155
+ amount: BigInt(rawRoute.returnAmountAfterCommissionWithDecimal),
156
+ },
157
+ name: this.name,
158
+ };
159
+ }
160
+
161
+ async fetchRoute(
162
+ params: FetchRouteParams
163
+ ): Promise<FetchRouteResult<QuoteResponse>> {
164
+ const normalizedParams = this.normalizeFetchRouteParams(params);
165
+
166
+ const { coinInType, coinOutType, swapAmount } = normalizedParams;
167
+ const rawRouteResult = await this.withTimeout(
168
+ getQuote({
169
+ tokenIn: coinInType,
170
+ tokenOut: coinOutType,
171
+ amountIn: swapAmount.toString(),
172
+ ...this.fetchSettings,
173
+ })
174
+ );
175
+
176
+ if (!rawRouteResult) {
177
+ throw new Error(`${this.name} fetch route returned empty response`);
178
+ }
179
+
180
+ this.rawRouteResult = rawRouteResult;
181
+
182
+ return {
183
+ formattedResult: this.parseRawRouteToFormattedResult(rawRouteResult),
184
+ rawRouteResult,
185
+ };
186
+ }
187
+
188
+ async buildSwapTransaction(
189
+ params: SwapParams
190
+ ): Promise<{ tx: Transaction; coinOut: TransactionObjectArgument }> {
191
+ this.validateWalletAddress();
192
+ this.validateRawRouteResult();
193
+
194
+ let tx: Transaction | null = null;
195
+ let inputCoin = null;
196
+ if (params.txExtensionParams) {
197
+ const { initTx, coinIn } = params.txExtensionParams;
198
+ tx = initTx;
199
+ inputCoin = coinIn;
200
+ } else {
201
+ tx = new Transaction();
202
+ tx.setSender(this.walletAddress);
203
+
204
+ inputCoin = await this.selectCoinInForSwap(
205
+ tx,
206
+ BigInt(this.rawRouteResult.swapAmountWithDecimal),
207
+ this.rawRouteResult.tokenIn
208
+ );
209
+ }
210
+
211
+ const { tx: nextTx, coinOut } = await buildTx({
212
+ quoteResponse: this.rawRouteResult,
213
+ slippage: this.calcSlippage(params.slippage),
214
+ commission: this.commission,
215
+ accountAddress: this.walletAddress,
216
+ extendTx: {
217
+ tx,
218
+ coinIn: inputCoin,
219
+ },
220
+ });
221
+
222
+ if (!coinOut) {
223
+ throw new Error(`${this.name} build swap transaction failed`);
224
+ }
225
+
226
+ return {
227
+ tx: 'txBytes' in nextTx ? Transaction.from(nextTx.txBytes) : nextTx,
228
+ coinOut,
229
+ };
230
+ }
231
+ }
@@ -0,0 +1,132 @@
1
+ import { Transaction } from '@scallop-io/sui-kit';
2
+ import {
3
+ RouterProtocolName,
4
+ RouterCompleteTradeRoute,
5
+ ApiRouterPartialCompleteTradeRouteBody,
6
+ Aftermath,
7
+ ConfigAddresses,
8
+ AftermathApi,
9
+ } from 'aftermath-ts-sdk';
10
+ import { SwapSdkBase } from 'src/class';
11
+ import { afConfigAddresses } from 'src/const/afAddresses';
12
+ import { BigNumber } from 'bignumber.js';
13
+ import {
14
+ FetchRouteParams,
15
+ FetchRouteResult,
16
+ SwapParams,
17
+ SwapSdkConstructorParams,
18
+ } from 'src/interface';
19
+
20
+ export class AftermathSwap extends SwapSdkBase<
21
+ RouterProtocolName,
22
+ RouterCompleteTradeRoute,
23
+ ApiRouterPartialCompleteTradeRouteBody
24
+ > {
25
+ readonly afClient: Aftermath;
26
+ readonly afApi: AftermathApi;
27
+ constructor(
28
+ params: SwapSdkConstructorParams & {
29
+ addresses?: ConfigAddresses;
30
+ }
31
+ ) {
32
+ super('aftermath', params);
33
+ this.afApi = new AftermathApi(
34
+ this.client,
35
+ params.addresses ?? afConfigAddresses
36
+ );
37
+ this.afClient = new Aftermath('MAINNET', this.afApi);
38
+ }
39
+
40
+ get router() {
41
+ return this.afClient.Router();
42
+ }
43
+
44
+ setFetchRouteSettings(
45
+ settings: ApiRouterPartialCompleteTradeRouteBody
46
+ ): void {
47
+ this.fetchSettings = settings;
48
+ }
49
+
50
+ private parseRawRouteToFormattedResult(rawRoute: RouterCompleteTradeRoute) {
51
+ const totalCoinInAmount = rawRoute.coinIn.amount;
52
+ const formattedResult = {
53
+ ...rawRoute,
54
+ name: this.name,
55
+ } as FetchRouteResult<any>['formattedResult'];
56
+ formattedResult.routes.forEach((route) => {
57
+ const routeCoinInAmount = route.paths[0].coinIn.amount;
58
+ route.splitPercentage = BigNumber(routeCoinInAmount.toString())
59
+ .div(totalCoinInAmount.toString())
60
+ .times(100)
61
+ .toNumber();
62
+ });
63
+ return formattedResult;
64
+ }
65
+
66
+ async fetchRoute(
67
+ params: FetchRouteParams
68
+ ): Promise<FetchRouteResult<RouterCompleteTradeRoute>> {
69
+ const normalizedParams = this.normalizeFetchRouteParams(params);
70
+
71
+ const { coinInType, coinOutType, swapAmount } = normalizedParams;
72
+ const rawRouteResult = await this.withTimeout(
73
+ this.router.getCompleteTradeRouteGivenAmountIn({
74
+ ...this.fetchSettings,
75
+ coinInType,
76
+ coinOutType,
77
+ coinInAmount: swapAmount,
78
+ })
79
+ );
80
+
81
+ if (!rawRouteResult) {
82
+ throw new Error(`${this.name} fetch route returned empty response`);
83
+ }
84
+
85
+ this.rawRouteResult = rawRouteResult;
86
+
87
+ return {
88
+ formattedResult: this.parseRawRouteToFormattedResult(rawRouteResult),
89
+ rawRouteResult,
90
+ };
91
+ }
92
+
93
+ async buildSwapTransaction(params: SwapParams) {
94
+ this.validateWalletAddress();
95
+ this.validateRawRouteResult();
96
+
97
+ let tx: Transaction | null = null;
98
+ let inputCoin = null;
99
+ if (params.txExtensionParams) {
100
+ const { initTx, coinIn } = params.txExtensionParams;
101
+ tx = initTx;
102
+ inputCoin = coinIn;
103
+ } else {
104
+ tx = new Transaction();
105
+ tx.setSender(this.walletAddress);
106
+
107
+ inputCoin = await this.selectCoinInForSwap(
108
+ tx,
109
+ this.rawRouteResult.coinIn.amount,
110
+ this.rawRouteResult.coinIn.type
111
+ );
112
+ }
113
+
114
+ const { tx: nextTx, coinOutId: outputCoin } =
115
+ await this.router.addTransactionForCompleteTradeRoute({
116
+ walletAddress: this.walletAddress,
117
+ completeRoute: this.rawRouteResult,
118
+ slippage: this.calcSlippage(params.slippage),
119
+ tx,
120
+ coinInId: inputCoin,
121
+ });
122
+
123
+ if (!outputCoin) {
124
+ throw new Error(`${this.name} build swap transaction failed`);
125
+ }
126
+
127
+ return {
128
+ tx: nextTx,
129
+ coinOut: outputCoin,
130
+ };
131
+ }
132
+ }
@@ -0,0 +1,198 @@
1
+ import { Transaction } from '@mysten/sui/transactions';
2
+ import {
3
+ AggregatorClient,
4
+ AggregatorClientParams,
5
+ ALL_DEXES,
6
+ Env,
7
+ FindRouterParams,
8
+ Path,
9
+ RouterDataV3,
10
+ } from '@cetusprotocol/aggregator-sdk';
11
+ import { SwapSdkBase } from 'src/class';
12
+ import BN from 'bn.js';
13
+ import { BigNumber } from 'bignumber.js';
14
+ import {
15
+ FetchRouteParams,
16
+ SwapParams,
17
+ SwapSdkConstructorParams,
18
+ SwapRoute,
19
+ FormattedRouteResult,
20
+ } from 'src/interface';
21
+ import { normalizeStructTag } from '@mysten/sui/utils';
22
+
23
+ type RawRouteResult = RouterDataV3;
24
+
25
+ export class CetusSwap extends SwapSdkBase<
26
+ string,
27
+ RawRouteResult,
28
+ FindRouterParams
29
+ > {
30
+ readonly cetusClient: AggregatorClient;
31
+ constructor(params: SwapSdkConstructorParams & AggregatorClientParams) {
32
+ super('cetus', params);
33
+ this.cetusClient = new AggregatorClient({
34
+ ...params,
35
+ endpoint:
36
+ params.endpoint ?? 'https://api-sui.cetus.zone/router_v3/find_routes',
37
+ signer: params.signer ?? this.walletAddress,
38
+ client: params.client ?? this.client,
39
+ });
40
+ this.cetusClient = new AggregatorClient({
41
+ endpoint: 'https://api-sui.cetus.zone/router_v3/find_routes',
42
+ signer: this.walletAddress,
43
+ client: this.client,
44
+ env: Env.Mainnet,
45
+ });
46
+ this.pools = ALL_DEXES;
47
+ }
48
+
49
+ private createRoute(
50
+ paths: Path[],
51
+ coinInType: string,
52
+ coinOutType: string
53
+ ): SwapRoute[] {
54
+ const routes: SwapRoute[] = [];
55
+
56
+ let prev = null;
57
+ let idx = 0;
58
+
59
+ for (const path of paths) {
60
+ if (path.from === coinInType) {
61
+ routes.push({
62
+ paths: [],
63
+ coinIn: {
64
+ type: path.from,
65
+ amount: BigInt(path.amountIn),
66
+ },
67
+ coinOut: {
68
+ type: path.target,
69
+ amount: BigInt(path.amountOut),
70
+ },
71
+ });
72
+ prev = path.target;
73
+ } else if (path.from === prev) {
74
+ // Get last route
75
+ const route = routes[idx];
76
+ route.paths.push({
77
+ protocolName: path.provider,
78
+ coinIn: {
79
+ type: path.from,
80
+ amount: BigInt(path.amountIn),
81
+ },
82
+ coinOut: {
83
+ type: path.target,
84
+ amount: BigInt(path.amountOut),
85
+ },
86
+ });
87
+ } else if (path.target === coinOutType) {
88
+ idx++;
89
+ prev = null;
90
+ }
91
+ }
92
+
93
+ const totalSwapAmount = routes
94
+ .reduce((acc, r) => acc + r.coinIn.amount, BigInt(0))
95
+ .toString();
96
+ routes.forEach((r) => {
97
+ r.splitPercentage = BigNumber(r.coinIn.amount.toString())
98
+ .div(totalSwapAmount)
99
+ .times(100)
100
+ .toNumber();
101
+ });
102
+ return routes;
103
+ }
104
+
105
+ /**
106
+ * Parse the raw route data from Cetus to the standardized FetchRouteResult format.
107
+ * @returns FormattedRouteResult
108
+ */
109
+ private parseRawToFormattedResult({
110
+ routerData,
111
+ coinInType,
112
+ coinOutType,
113
+ }: {
114
+ routerData: RouterDataV3;
115
+ coinInType: string;
116
+ coinOutType: string;
117
+ }): Omit<FormattedRouteResult, 'name'> {
118
+ return {
119
+ coinIn: {
120
+ type: coinInType,
121
+ amount: BigInt(routerData.amountIn.toString() ?? '0'),
122
+ },
123
+ coinOut: {
124
+ type: coinOutType,
125
+ amount: BigInt(routerData.amountOut.toString() ?? '0'),
126
+ },
127
+ routes: this.createRoute(routerData.paths, coinInType, coinOutType),
128
+ };
129
+ }
130
+
131
+ setFetchRouteSettings(settings: FindRouterParams) {
132
+ this.fetchSettings = settings;
133
+ }
134
+
135
+ async fetchRoute(params: FetchRouteParams) {
136
+ const normalizedParams = this.normalizeFetchRouteParams(params);
137
+ const { coinInType, coinOutType, swapAmount } = normalizedParams;
138
+ const rawRouteResult = await this.withTimeout(
139
+ this.cetusClient.findRouters({
140
+ from: normalizeStructTag(coinInType),
141
+ target: normalizeStructTag(coinOutType),
142
+ amount: new BN(swapAmount.toString()),
143
+ byAmountIn: true,
144
+ ...this.fetchSettings,
145
+ })
146
+ );
147
+
148
+ if (!rawRouteResult) {
149
+ throw new Error(`${this.name} fetch route returned empty response`);
150
+ }
151
+
152
+ this.rawRouteResult = rawRouteResult;
153
+ return {
154
+ formattedResult: {
155
+ ...this.parseRawToFormattedResult({
156
+ routerData: rawRouteResult,
157
+ coinInType: coinInType,
158
+ coinOutType: coinOutType,
159
+ }),
160
+ name: this.name,
161
+ },
162
+ rawRouteResult,
163
+ };
164
+ }
165
+
166
+ async buildSwapTransaction(params: SwapParams) {
167
+ this.validateWalletAddress();
168
+ this.validateRawRouteResult();
169
+
170
+ let tx: Transaction | null = null;
171
+ let inputCoin = null;
172
+ if (params.txExtensionParams) {
173
+ const { initTx, coinIn } = params.txExtensionParams;
174
+ tx = initTx;
175
+ inputCoin = coinIn;
176
+ } else {
177
+ tx = new Transaction();
178
+ tx.setSender(this.walletAddress);
179
+ inputCoin = await this.selectCoinInForSwap(
180
+ tx,
181
+ BigInt(this.rawRouteResult.amountIn.toString()),
182
+ this.rawRouteResult.paths[0].from
183
+ );
184
+ }
185
+
186
+ const outputCoin = await this.cetusClient.routerSwap({
187
+ router: this.rawRouteResult,
188
+ txb: tx,
189
+ slippage: this.calcSlippage(params.slippage),
190
+ inputCoin,
191
+ });
192
+
193
+ return {
194
+ tx,
195
+ coinOut: outputCoin,
196
+ };
197
+ }
198
+ }