@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,176 @@
1
+ import {
2
+ AggregatorQuoter,
3
+ Coin,
4
+ Commission,
5
+ GetRoutesResult,
6
+ Protocol,
7
+ SingleQuoteQueryParams,
8
+ TradeBuilder,
9
+ } from '@flowx-finance/sdk';
10
+ import {
11
+ Transaction,
12
+ TransactionObjectArgument,
13
+ } from '@mysten/sui/transactions';
14
+ import { normalizeStructTag } from '@mysten/sui/utils';
15
+ import { SwapSdkBase } from 'src/class';
16
+ import {
17
+ FetchRouteParams,
18
+ FetchRouteResult,
19
+ FormattedRouteResult,
20
+ SwapParams,
21
+ SwapRoute,
22
+ SwapSdkConstructorParams,
23
+ } from 'src/interface';
24
+ import { transformProperCase } from 'src/utils';
25
+
26
+ export class FlowXSwap extends SwapSdkBase<
27
+ Protocol,
28
+ GetRoutesResult<Coin, Coin>,
29
+ Omit<SingleQuoteQueryParams, 'amountIn' | 'tokenIn' | 'tokenOut'>
30
+ > {
31
+ private quoter: AggregatorQuoter = new AggregatorQuoter('mainnet');
32
+ commission?: Commission;
33
+
34
+ constructor(
35
+ params: SwapSdkConstructorParams &
36
+ Omit<SingleQuoteQueryParams, 'amountIn' | 'tokenIn' | 'tokenOut'> & {
37
+ commission?: Commission;
38
+ }
39
+ ) {
40
+ super('flowx', params);
41
+ this.commission = params.commission;
42
+ }
43
+
44
+ setFetchRouteSettings(
45
+ settings: Omit<SingleQuoteQueryParams, 'amountIn'>
46
+ ): void {
47
+ this.fetchSettings = settings;
48
+ }
49
+
50
+ calcSlippage(slippageInBps: number): number {
51
+ return slippageInBps * 100;
52
+ }
53
+
54
+ protected parseRawRouteToFormattedResult(
55
+ rawRouteResult: GetRoutesResult<Coin, Coin>
56
+ ): FormattedRouteResult {
57
+ const routes: SwapRoute[] = [];
58
+ for (const smartPath of rawRouteResult.routes) {
59
+ routes.push({
60
+ paths: smartPath.paths.map((path) => ({
61
+ protocolName: transformProperCase(
62
+ (path.protocol()?.toLowerCase() ?? '') as Lowercase<string>
63
+ ),
64
+ coinIn: {
65
+ amount: BigInt((path.amountIn ?? 0).toString()),
66
+ type: path.input.coinType,
67
+ },
68
+ coinOut: {
69
+ amount: BigInt((path.amountOut ?? 0).toString()),
70
+ type: path.output.coinType,
71
+ },
72
+ })),
73
+ splitPercentage:
74
+ (Number(smartPath.amountIn ?? 0) /
75
+ Number(this.rawRouteResult?.amountIn ?? 1)) *
76
+ 100,
77
+ coinIn: {
78
+ amount: BigInt(smartPath.amountIn.toString()),
79
+ type: normalizeStructTag(smartPath.input.coinType),
80
+ },
81
+ coinOut: {
82
+ amount: BigInt(smartPath.amountOut.toString()),
83
+ type: normalizeStructTag(smartPath.output.coinType),
84
+ },
85
+ });
86
+ }
87
+
88
+ return {
89
+ routes,
90
+ coinIn: {
91
+ type: normalizeStructTag(rawRouteResult.coinIn.coinType),
92
+ amount: BigInt(rawRouteResult.amountIn.toString()),
93
+ },
94
+ coinOut: {
95
+ type: normalizeStructTag(rawRouteResult.coinOut.coinType),
96
+ amount: BigInt(rawRouteResult.amountOut.toString()),
97
+ },
98
+ name: this.name,
99
+ };
100
+ }
101
+
102
+ async fetchRoute(
103
+ params: FetchRouteParams
104
+ ): Promise<FetchRouteResult<GetRoutesResult<Coin, Coin>>> {
105
+ const normalizedParams = this.normalizeFetchRouteParams(params);
106
+
107
+ const { coinInType, coinOutType, swapAmount } = normalizedParams;
108
+ const rawRouteResult = await this.withTimeout(
109
+ this.quoter.getRoutes({
110
+ ...this.fetchSettings,
111
+ tokenIn: coinInType,
112
+ tokenOut: coinOutType,
113
+ amountIn: swapAmount.toString(),
114
+ commission: this.commission,
115
+ })
116
+ );
117
+
118
+ if (!rawRouteResult) {
119
+ throw new Error(`${this.name} fetch route returned empty response`);
120
+ }
121
+
122
+ this.rawRouteResult = rawRouteResult;
123
+ return {
124
+ formattedResult: this.parseRawRouteToFormattedResult(rawRouteResult),
125
+ rawRouteResult,
126
+ };
127
+ }
128
+
129
+ async buildSwapTransaction(
130
+ params: SwapParams
131
+ ): Promise<{ tx: Transaction; coinOut: TransactionObjectArgument }> {
132
+ this.validateWalletAddress();
133
+ this.validateRawRouteResult();
134
+
135
+ let tx: Transaction | null = null;
136
+ let inputCoin = null;
137
+ if (params.txExtensionParams) {
138
+ const { initTx, coinIn } = params.txExtensionParams;
139
+ tx = initTx;
140
+ inputCoin = coinIn;
141
+ } else {
142
+ tx = new Transaction();
143
+ tx.setSender(this.walletAddress);
144
+
145
+ inputCoin = await this.selectCoinInForSwap(
146
+ tx,
147
+ BigInt(this.rawRouteResult.amountIn.toString()),
148
+ this.rawRouteResult.coinIn.coinType
149
+ );
150
+ }
151
+
152
+ const tradeBuilder = new TradeBuilder('mainnet', this.rawRouteResult.routes)
153
+ .sender(this.walletAddress)
154
+ .slippage(this.calcSlippage(params.slippage))
155
+ .deadline(Date.now() + 60 * 60 * 1000); // one minute;
156
+
157
+ const trade = this.commission
158
+ ? tradeBuilder.commission(this.commission).build()
159
+ : tradeBuilder.build();
160
+
161
+ const coinOut = await trade.swap({
162
+ client: this.client,
163
+ tx: tx,
164
+ coinIn: inputCoin,
165
+ });
166
+
167
+ if (!coinOut) {
168
+ throw new Error(`${this.name} build swap transaction failed`);
169
+ }
170
+
171
+ return {
172
+ tx,
173
+ coinOut,
174
+ };
175
+ }
176
+ }
@@ -0,0 +1,4 @@
1
+ export * from './cetus';
2
+ export * from './aftermath';
3
+ export * from './7k';
4
+ export * from './flowx';
package/src/utils.ts ADDED
@@ -0,0 +1,114 @@
1
+ import { CoinStruct, SuiClient } from '@mysten/sui/client';
2
+ import {
3
+ Transaction,
4
+ TransactionObjectArgument,
5
+ } from '@mysten/sui/transactions';
6
+ import { normalizeStructTag, SUI_TYPE_ARG } from '@mysten/sui/utils';
7
+ import { BigNumber } from 'bignumber.js';
8
+
9
+ // const DEFAULT_OBJECT_LIMIT = Number.MAX_SAFE_INTEGER;
10
+ const SUI_TYPE = normalizeStructTag(SUI_TYPE_ARG);
11
+
12
+ export const isSuiType = (coinType: string): boolean => {
13
+ return normalizeStructTag(coinType) === SUI_TYPE;
14
+ };
15
+
16
+ export const selectCoins = async (
17
+ client: SuiClient,
18
+ coinType: string,
19
+ amount: string,
20
+ owner: string
21
+ ) => {
22
+ const coins = [];
23
+ let targetAmount = BigNumber(amount);
24
+
25
+ try {
26
+ let cursor = null;
27
+ do {
28
+ const { data, hasNextPage, nextCursor } = await client.getCoins({
29
+ coinType,
30
+ owner,
31
+ limit: 50,
32
+ });
33
+
34
+ coins.push(...data);
35
+ targetAmount = targetAmount.minus(
36
+ data.reduce(
37
+ (acc, coin: CoinStruct) => acc.plus(BigNumber(coin.balance)),
38
+ BigNumber(0)
39
+ )
40
+ );
41
+
42
+ if (targetAmount.lte(0)) {
43
+ break;
44
+ }
45
+ cursor = nextCursor;
46
+ if (!hasNextPage) {
47
+ break;
48
+ }
49
+ } while (cursor);
50
+ } catch (e) {
51
+ console.error(e);
52
+ }
53
+
54
+ if (coins.length === 0) {
55
+ throw new Error(
56
+ `No available coin for type ${coinType} found in wallet ${owner}`
57
+ );
58
+ }
59
+
60
+ if (targetAmount.gt(0)) {
61
+ throw new Error(
62
+ `Insufficient balance for coin type ${coinType} in wallet ${owner}`
63
+ );
64
+ }
65
+
66
+ return coins;
67
+ };
68
+
69
+ export const mergeWithExistingOrTransfer = async (
70
+ tx: Transaction,
71
+ coin: TransactionObjectArgument,
72
+ client: SuiClient,
73
+ coinType: string,
74
+ sender: string
75
+ ) => {
76
+ try {
77
+ if (isSuiType(coinType)) {
78
+ // For SUI, merge the destObject to the gas coin
79
+ tx.mergeCoins(tx.gas, [coin]);
80
+ } else {
81
+ const { data } = await client.getCoins({
82
+ coinType,
83
+ owner: sender,
84
+ limit: 1,
85
+ });
86
+ const existingCoins = data[0];
87
+
88
+ if (!existingCoins) {
89
+ throw new Error('No coins available to merge with');
90
+ }
91
+
92
+ tx.mergeCoins(
93
+ tx.objectRef({
94
+ objectId: existingCoins.coinObjectId,
95
+ version: existingCoins.version,
96
+ digest: existingCoins.digest,
97
+ }),
98
+ [coin]
99
+ );
100
+ }
101
+ } catch (e: any) {
102
+ console.warn(e);
103
+ tx.transferObjects([coin], tx.pure.address(sender));
104
+ }
105
+ };
106
+
107
+ const parseProperCase = (str: string): string => {
108
+ return str[0].toUpperCase() + str.slice(1);
109
+ };
110
+
111
+ export const transformProperCase = (provider: Lowercase<string>): string => {
112
+ const splitted = provider.split('_');
113
+ return splitted.map(parseProperCase).join(' ');
114
+ };