@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.
- package/LICENSE +202 -0
- package/README.md +3 -0
- package/dist/class/aggregator.d.ts +55 -0
- package/dist/class/base.d.ts +36 -0
- package/dist/class/coinMetadata.d.ts +9 -0
- package/dist/class/index.d.ts +2 -0
- package/dist/const/afAddresses.d.ts +2 -0
- package/dist/const/index.d.ts +1 -0
- package/dist/example/index.d.ts +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +30 -0
- package/dist/index.mjs +14 -0
- package/dist/interface/aggregator.d.ts +18 -0
- package/dist/interface/base.d.ts +73 -0
- package/dist/interface/index.d.ts +2 -0
- package/dist/interface/route.d.ts +29 -0
- package/dist/sdk/7k.d.ts +38 -0
- package/dist/sdk/aftermath.d.ts +19 -0
- package/dist/sdk/cetus.d.ts +30 -0
- package/dist/sdk/flowx.d.ts +19 -0
- package/dist/sdk/index.d.ts +4 -0
- package/dist/utils.d.ts +6 -0
- package/package.json +168 -0
- package/src/class/aggregator.ts +245 -0
- package/src/class/base.ts +164 -0
- package/src/class/coinMetadata.ts +33 -0
- package/src/class/index.ts +2 -0
- package/src/const/afAddresses.ts +230 -0
- package/src/const/index.ts +1 -0
- package/src/example/index.ts +47 -0
- package/src/index.ts +5 -0
- package/src/interface/aggregator.ts +24 -0
- package/src/interface/base.ts +85 -0
- package/src/interface/index.ts +2 -0
- package/src/interface/route.ts +33 -0
- package/src/sdk/7k.ts +231 -0
- package/src/sdk/aftermath.ts +132 -0
- package/src/sdk/cetus.ts +198 -0
- package/src/sdk/flowx.ts +176 -0
- package/src/sdk/index.ts +4 -0
- package/src/utils.ts +114 -0
|
@@ -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
|
+
}
|
package/src/sdk/cetus.ts
ADDED
|
@@ -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
|
+
}
|