@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,73 @@
1
+ import { Transaction, TransactionObjectArgument } from '@mysten/sui/transactions';
2
+ import { FetchRouteResult } from './route';
3
+ import { SuiClient } from '@mysten/sui/client';
4
+ export type FetchRouteParams = {
5
+ coinInType: string;
6
+ coinOutType: string;
7
+ swapAmount: bigint;
8
+ };
9
+ export type SwapParams = {
10
+ slippage: number;
11
+ minAmountOut?: bigint;
12
+ txExtensionParams?: {
13
+ initTx: Transaction;
14
+ coinIn: TransactionObjectArgument;
15
+ };
16
+ mergeOutputCoin?: boolean;
17
+ };
18
+ export interface SwapBuildResult {
19
+ tx: Transaction;
20
+ coinOut: TransactionObjectArgument;
21
+ }
22
+ export interface SwapSdkConstructorParams {
23
+ client?: SuiClient;
24
+ fetchTimeoutInMs?: number;
25
+ cacheTimeInMs?: number;
26
+ walletAddress: string;
27
+ }
28
+ /**
29
+ * Base interface for Swap SDK
30
+ */
31
+ export interface SwapSdkBaseInterface<T = string, U = any, V = any> {
32
+ /**
33
+ * How long the route result is cached (in milliseconds)
34
+ */
35
+ cacheTimeInMs?: number;
36
+ lastFetchTimestamp?: number;
37
+ /**
38
+ * How long the route fetch request should wait before timing out (in milliseconds)
39
+ */
40
+ fetchTimeoutInMs?: number;
41
+ /**
42
+ * Pools or DEXes available in the SDK
43
+ */
44
+ pools?: T[];
45
+ /**
46
+ * Fetch route settings based on SDK-specific settings
47
+ */
48
+ fetchSettings?: V;
49
+ /**
50
+ * Set fetch route settings based on SDK-specific settings
51
+ * @param settings
52
+ * @returns
53
+ */
54
+ setFetchRouteSettings: (settings: V) => void;
55
+ changeClient: (client: SuiClient) => void;
56
+ setWalletAddress: (address: string) => void;
57
+ /**
58
+ * Calculate slippage based on basis points (bps) input.
59
+ * @param slippageInBps
60
+ * @returns slippage in required format or notation
61
+ */
62
+ calcSlippage: (slippageInBps: number) => number;
63
+ fetchRoute: (params: FetchRouteParams) => Promise<FetchRouteResult<U>>;
64
+ /**
65
+ * Select coinIn for swap transaction
66
+ * @param tx
67
+ * @param amount
68
+ * @param coinType
69
+ * @returns Promise<TransactionObjectArgument>
70
+ */
71
+ selectCoinInForSwap: (tx: Transaction, amount: bigint, coinType: string) => Promise<TransactionObjectArgument>;
72
+ buildSwapTransaction: (params: SwapParams) => Promise<SwapBuildResult>;
73
+ }
@@ -0,0 +1,2 @@
1
+ export type * from './base';
2
+ export type * from './route';
@@ -0,0 +1,29 @@
1
+ export interface Coin {
2
+ type: string;
3
+ amount: bigint;
4
+ }
5
+ interface SwapCoinInfo {
6
+ coinIn: Coin;
7
+ coinOut: Coin;
8
+ }
9
+ interface SwapPath extends SwapCoinInfo {
10
+ protocolName: string;
11
+ }
12
+ export interface SwapRoute extends SwapCoinInfo {
13
+ paths: SwapPath[];
14
+ splitPercentage?: number;
15
+ }
16
+ /**
17
+ * Common interface for route result (for frontend display purposes)
18
+ */
19
+ export interface FormattedRouteResult {
20
+ routes: SwapRoute[];
21
+ coinIn: Coin;
22
+ coinOut: Coin;
23
+ name: string;
24
+ }
25
+ export type FetchRouteResult<T> = {
26
+ rawRouteResult: T;
27
+ formattedResult: FormattedRouteResult;
28
+ };
29
+ export {};
@@ -0,0 +1,38 @@
1
+ import { Commission, QuoteResponse, SourceDex } from '@7kprotocol/sdk-ts';
2
+ import { Transaction, TransactionObjectArgument } from '@scallop-io/sui-kit';
3
+ import { SwapSdkBase } from 'src/class';
4
+ import { FetchRouteParams, FetchRouteResult, SwapParams, SwapSdkConstructorParams } from 'src/interface';
5
+ interface Params {
6
+ tokenIn: string;
7
+ tokenOut: string;
8
+ amountIn: string;
9
+ /**
10
+ * @default DEFAULT_SOURCES
11
+ * @warning BluefinX must be explicitly specified if needed
12
+ * @example ```sources: [...DEFAULT_SOURCES, "bluefinx"]``` */
13
+ sources?: SourceDex[];
14
+ commissionBps?: number;
15
+ /** Limit the route to a specific set of pools */
16
+ targetPools?: string[];
17
+ /** Exclude a specific set of pools from the route */
18
+ excludedPools?: string[];
19
+ /** The taker address, required for bluefinx */
20
+ taker?: string;
21
+ /** If true, excludes all liquidity sources that depend on pyth price feeds - pyth client use tx.gas to pay the fee*/
22
+ isSponsored?: boolean;
23
+ }
24
+ export declare class _7kSwap extends SwapSdkBase<SourceDex, QuoteResponse, Omit<Params, 'tokenIn' | 'tokenOut' | 'amountIn'>> {
25
+ commission: Commission;
26
+ constructor(params: SwapSdkConstructorParams & {
27
+ commission?: Commission;
28
+ });
29
+ setFetchRouteSettings(settings: Omit<Params, 'tokenIn' | 'tokenOut' | 'amountIn'>): void;
30
+ private createRoute;
31
+ private parseRawRouteToFormattedResult;
32
+ fetchRoute(params: FetchRouteParams): Promise<FetchRouteResult<QuoteResponse>>;
33
+ buildSwapTransaction(params: SwapParams): Promise<{
34
+ tx: Transaction;
35
+ coinOut: TransactionObjectArgument;
36
+ }>;
37
+ }
38
+ export {};
@@ -0,0 +1,19 @@
1
+ import { Transaction } from '@scallop-io/sui-kit';
2
+ import { RouterProtocolName, RouterCompleteTradeRoute, ApiRouterPartialCompleteTradeRouteBody, Aftermath, ConfigAddresses, AftermathApi } from 'aftermath-ts-sdk';
3
+ import { SwapSdkBase } from 'src/class';
4
+ import { FetchRouteParams, FetchRouteResult, SwapParams, SwapSdkConstructorParams } from 'src/interface';
5
+ export declare class AftermathSwap extends SwapSdkBase<RouterProtocolName, RouterCompleteTradeRoute, ApiRouterPartialCompleteTradeRouteBody> {
6
+ readonly afClient: Aftermath;
7
+ readonly afApi: AftermathApi;
8
+ constructor(params: SwapSdkConstructorParams & {
9
+ addresses?: ConfigAddresses;
10
+ });
11
+ get router(): import("aftermath-ts-sdk").Router;
12
+ setFetchRouteSettings(settings: ApiRouterPartialCompleteTradeRouteBody): void;
13
+ private parseRawRouteToFormattedResult;
14
+ fetchRoute(params: FetchRouteParams): Promise<FetchRouteResult<RouterCompleteTradeRoute>>;
15
+ buildSwapTransaction(params: SwapParams): Promise<{
16
+ tx: Transaction;
17
+ coinOut: any;
18
+ }>;
19
+ }
@@ -0,0 +1,30 @@
1
+ import { Transaction } from '@mysten/sui/transactions';
2
+ import { AggregatorClient, AggregatorClientParams, FindRouterParams, RouterDataV3 } from '@cetusprotocol/aggregator-sdk';
3
+ import { SwapSdkBase } from 'src/class';
4
+ import { FetchRouteParams, SwapParams, SwapSdkConstructorParams, SwapRoute } from 'src/interface';
5
+ type RawRouteResult = RouterDataV3;
6
+ export declare class CetusSwap extends SwapSdkBase<string, RawRouteResult, FindRouterParams> {
7
+ readonly cetusClient: AggregatorClient;
8
+ constructor(params: SwapSdkConstructorParams & AggregatorClientParams);
9
+ private createRoute;
10
+ /**
11
+ * Parse the raw route data from Cetus to the standardized FetchRouteResult format.
12
+ * @returns FormattedRouteResult
13
+ */
14
+ private parseRawToFormattedResult;
15
+ setFetchRouteSettings(settings: FindRouterParams): void;
16
+ fetchRoute(params: FetchRouteParams): Promise<{
17
+ formattedResult: {
18
+ name: string;
19
+ routes: SwapRoute[];
20
+ coinIn: import("src/interface").Coin;
21
+ coinOut: import("src/interface").Coin;
22
+ };
23
+ rawRouteResult: RouterDataV3;
24
+ }>;
25
+ buildSwapTransaction(params: SwapParams): Promise<{
26
+ tx: Transaction;
27
+ coinOut: any;
28
+ }>;
29
+ }
30
+ export {};
@@ -0,0 +1,19 @@
1
+ import { Coin, Commission, GetRoutesResult, Protocol, SingleQuoteQueryParams } from '@flowx-finance/sdk';
2
+ import { Transaction, TransactionObjectArgument } from '@mysten/sui/transactions';
3
+ import { SwapSdkBase } from 'src/class';
4
+ import { FetchRouteParams, FetchRouteResult, FormattedRouteResult, SwapParams, SwapSdkConstructorParams } from 'src/interface';
5
+ export declare class FlowXSwap extends SwapSdkBase<Protocol, GetRoutesResult<Coin, Coin>, Omit<SingleQuoteQueryParams, 'amountIn' | 'tokenIn' | 'tokenOut'>> {
6
+ private quoter;
7
+ commission?: Commission;
8
+ constructor(params: SwapSdkConstructorParams & Omit<SingleQuoteQueryParams, 'amountIn' | 'tokenIn' | 'tokenOut'> & {
9
+ commission?: Commission;
10
+ });
11
+ setFetchRouteSettings(settings: Omit<SingleQuoteQueryParams, 'amountIn'>): void;
12
+ calcSlippage(slippageInBps: number): number;
13
+ protected parseRawRouteToFormattedResult(rawRouteResult: GetRoutesResult<Coin, Coin>): FormattedRouteResult;
14
+ fetchRoute(params: FetchRouteParams): Promise<FetchRouteResult<GetRoutesResult<Coin, Coin>>>;
15
+ buildSwapTransaction(params: SwapParams): Promise<{
16
+ tx: Transaction;
17
+ coinOut: TransactionObjectArgument;
18
+ }>;
19
+ }
@@ -0,0 +1,4 @@
1
+ export * from './cetus';
2
+ export * from './aftermath';
3
+ export * from './7k';
4
+ export * from './flowx';
@@ -0,0 +1,6 @@
1
+ import { CoinStruct, SuiClient } from '@mysten/sui/client';
2
+ import { Transaction, TransactionObjectArgument } from '@mysten/sui/transactions';
3
+ export declare const isSuiType: (coinType: string) => boolean;
4
+ export declare const selectCoins: (client: SuiClient, coinType: string, amount: string, owner: string) => Promise<CoinStruct[]>;
5
+ export declare const mergeWithExistingOrTransfer: (tx: Transaction, coin: TransactionObjectArgument, client: SuiClient, coinType: string, sender: string) => Promise<void>;
6
+ export declare const transformProperCase: (provider: Lowercase<string>) => string;
package/package.json ADDED
@@ -0,0 +1,168 @@
1
+ {
2
+ "name": "@scallop-io/scallop-swap-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Swap SDK Aggregator for Sui Blockchain developed by Scallop Labs.",
5
+ "keywords": [
6
+ "sui",
7
+ "scallop labs",
8
+ "move",
9
+ "blockchain",
10
+ "swap sdk aggregator",
11
+ "scallop-swap-sdk-aggregator"
12
+ ],
13
+ "author": "team@scallop.io",
14
+ "homepage": "https://github.com/scallop-io/scallop-swap-sdk-aggregator#readme",
15
+ "bugs": "https://github.com/scallop-io/scallop-swap-sdk-aggregator/issues",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/scallop-io/scallop-swap-sdk-aggregator.git"
19
+ },
20
+ "license": "Apache-2.0",
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "engines": {
25
+ "node": ">=16"
26
+ },
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.mjs",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "source": "./src/index.ts",
33
+ "import": "./dist/index.mjs",
34
+ "require": "./dist/index.js"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "src"
40
+ ],
41
+ "dependencies": {
42
+ "@7kprotocol/sdk-ts": "^3.4.1",
43
+ "@cetusprotocol/aggregator-sdk": "^1.4.1",
44
+ "@flowx-finance/sdk": "^1.13.8",
45
+ "@mysten/sui": "^1.43.1",
46
+ "@scallop-io/sui-kit": "^1.4.3",
47
+ "aftermath-ts-sdk": "^1.3.23",
48
+ "assert": "^2.1.0",
49
+ "bignumber.js": "^9.3.1",
50
+ "bn.js": "^5.2.2"
51
+ },
52
+ "devDependencies": {
53
+ "@commitlint/cli": "^18.0.0",
54
+ "@commitlint/config-conventional": "^18.0.0",
55
+ "@commitlint/prompt-cli": "^18.0.0",
56
+ "@types/bn.js": "^5.2.0",
57
+ "@types/node": "^20.8.7",
58
+ "@types/tmp": "^0.2.5",
59
+ "@typescript-eslint/eslint-plugin": "^8.11.0",
60
+ "@typescript-eslint/parser": "8.10.0",
61
+ "@vitest/coverage-v8": "3.1.1",
62
+ "@vitest/expect": "^3.1.1",
63
+ "@vitest/runner": "^3.1.1",
64
+ "@vitest/spy": "^3.1.1",
65
+ "dotenv": "^16.6.1",
66
+ "eslint": "^8.52.0",
67
+ "eslint-config-prettier": "^9.0.0",
68
+ "eslint-plugin-prettier": "^5.0.1",
69
+ "husky": "^8.0.3",
70
+ "lint-staged": "^15.0.2",
71
+ "prettier": "^3.0.3",
72
+ "standard-version": "^9.5.0",
73
+ "ts-node": "^10.9.1",
74
+ "tsconfig-paths": "^4.2.0",
75
+ "tsup": "^7.2.0",
76
+ "typedoc": "^0.25.2",
77
+ "typescript": "5.5.4",
78
+ "vitest": "^3.1.1"
79
+ },
80
+ "resolutions": {
81
+ "@mysten/sui": "^1.43.1"
82
+ },
83
+ "lint-staged": {
84
+ "**/*.ts": [
85
+ "pnpm run format:fix",
86
+ "pnpm run lint:fix"
87
+ ],
88
+ "**/*.json|md": [
89
+ "pnpm run format:fix"
90
+ ]
91
+ },
92
+ "husky": {
93
+ "hooks": {
94
+ "pre-commit": "lint-staged"
95
+ }
96
+ },
97
+ "commitlint": {
98
+ "extends": [
99
+ "@commitlint/config-conventional"
100
+ ]
101
+ },
102
+ "prettier": {
103
+ "trailingComma": "es5",
104
+ "tabWidth": 2,
105
+ "semi": true,
106
+ "singleQuote": true,
107
+ "useTabs": false,
108
+ "quoteProps": "as-needed",
109
+ "bracketSpacing": true,
110
+ "arrowParens": "always",
111
+ "endOfLine": "lf"
112
+ },
113
+ "eslintConfig": {
114
+ "root": true,
115
+ "env": {
116
+ "browser": true,
117
+ "node": true,
118
+ "es2022": true
119
+ },
120
+ "extends": [
121
+ "eslint:recommended",
122
+ "plugin:@typescript-eslint/eslint-recommended",
123
+ "plugin:prettier/recommended"
124
+ ],
125
+ "plugins": [
126
+ "@typescript-eslint",
127
+ "prettier"
128
+ ],
129
+ "parser": "@typescript-eslint/parser",
130
+ "rules": {
131
+ "prettier/prettier": "warn",
132
+ "@typescript-eslint/no-explicit-any": "off",
133
+ "no-unused-vars": "off",
134
+ "@typescript-eslint/no-unused-vars": [
135
+ "error",
136
+ {
137
+ "argsIgnorePattern": "^_",
138
+ "varsIgnorePattern": "^_",
139
+ "caughtErrorsIgnorePattern": "^_"
140
+ }
141
+ ]
142
+ }
143
+ },
144
+ "scripts": {
145
+ "clean": "rm -rf tsconfig.tsbuildinfo ./dist",
146
+ "build": "npm run build:types && npm run build:tsup",
147
+ "build:tsup": "tsup ./src/index.ts --format esm,cjs --splitting --minify --treeshake",
148
+ "build:types": "tsc --build",
149
+ "watch:tsup": "tsup ./src/index.ts --format esm,cjs --clean --splitting --watch",
150
+ "watch:types": "tsc --watch",
151
+ "watch": "pnpm run clean & pnpm run watch:types & pnpm run watch:tsup",
152
+ "test": "pnpm test:typecheck && pnpm test:unit",
153
+ "test:typecheck": "tsc -p ./test",
154
+ "test:unit": "vitest run --test-timeout=60000",
155
+ "test:watch": "vitest",
156
+ "test:coverage-unit": "vitest run test/unit --coverage --test-timeout=60000",
157
+ "test:coverage-integration": "vitest run test/integration --coverage --test-timeout=60000",
158
+ "test:coverage": "vitest run --coverage --test-timeout=60000",
159
+ "format:fix": "prettier --ignore-path 'dist/* docs/*' --write '**/*.{ts,json,md}'",
160
+ "lint:fix": "eslint . --ignore-pattern dist --ext .ts --fix",
161
+ "commit": "commit",
162
+ "release": "standard-version -f",
163
+ "release:major": "standard-version -r major",
164
+ "release:minor": "standard-version -r minor",
165
+ "release:patch": "standard-version -r patch",
166
+ "doc": "typedoc --out docs src/index.ts"
167
+ }
168
+ }
@@ -0,0 +1,245 @@
1
+ import {
2
+ Transaction,
3
+ TransactionObjectArgument,
4
+ } from '@mysten/sui/transactions';
5
+ import { isValidSuiAddress } from '@mysten/sui/utils';
6
+ import { SwapSdkBase } from './base';
7
+ import { AggregatorConstructorParams } from 'src/interface/aggregator';
8
+ import { FetchRouteParams, SwapBuildResult } from 'src/interface/base';
9
+ import { BigNumber } from 'bignumber.js';
10
+ import {
11
+ DryRunTransactionBlockResponse,
12
+ SuiClient,
13
+ SuiTransactionBlockResponse,
14
+ } from '@mysten/sui/client';
15
+ import { FetchRouteResult } from 'src/interface/route';
16
+ import { CoinMetadataRegistry } from './coinMetadata';
17
+ import { SuiKit } from '@scallop-io/sui-kit';
18
+ import { mergeWithExistingOrTransfer } from 'src/utils';
19
+
20
+ export class Aggregator {
21
+ readonly coinMetadata: CoinMetadataRegistry;
22
+ readonly suiKit: SuiKit;
23
+ readonly aggregators: SwapSdkBase[] = [];
24
+
25
+ protected slippage: number = 30; // in bps, 1bps = 0.01% = 0.0001, defaults to 0.3%
26
+ protected routes: FetchRouteResult<any>[] = [];
27
+ protected buildResult?: SwapBuildResult & { coinOutType: string };
28
+
29
+ walletAddress: string;
30
+ client: SuiClient;
31
+
32
+ constructor(params: AggregatorConstructorParams) {
33
+ this.client = new SuiClient({
34
+ url: params.fullnodeUrl || 'https://fullnode.mainnet.sui.io:443',
35
+ });
36
+ this.suiKit = new SuiKit({
37
+ ...params,
38
+ suiClients: [this.client],
39
+ });
40
+
41
+ this.coinMetadata = new CoinMetadataRegistry(this.client);
42
+
43
+ if (params.secretKey || params.mnemonics) {
44
+ this.walletAddress = this.suiKit.currentAddress;
45
+ } else if (params.walletAddress) {
46
+ if (!isValidSuiAddress(params.walletAddress)) {
47
+ throw new Error('Invalid Sui wallet address');
48
+ }
49
+ this.walletAddress = params.walletAddress;
50
+ } else {
51
+ throw new Error(
52
+ 'One of secretKey, mnemonics or walletAddress must be provided'
53
+ );
54
+ }
55
+ }
56
+
57
+ // ------------ AGGREGATOR MANAGEMENT ---------------
58
+ addAggregator(aggregator: SwapSdkBase[]) {
59
+ this.aggregators.push(...aggregator);
60
+ }
61
+
62
+ getAggregators() {
63
+ return this.aggregators;
64
+ }
65
+
66
+ removeAggregator(name: string) {
67
+ const idx = this.aggregators.findIndex((t) => t.name === name);
68
+ if (idx === -1) {
69
+ throw new Error(`Aggregator ${name} not found`);
70
+ }
71
+
72
+ this.aggregators.splice(idx, 1);
73
+ }
74
+
75
+ /**
76
+ * Set slippage in bps (1bps = 0.01% = 0.0001)
77
+ * @param slippageInBps
78
+ */
79
+ setSlippage(slippageInBps: number) {
80
+ this.slippage = slippageInBps;
81
+ }
82
+
83
+ // ------------ GUARDS ---------------
84
+ #validateSuiKit(): asserts this is typeof Aggregator & { suiKit: SuiKit } {
85
+ if (!this.suiKit) {
86
+ throw new Error('SuiKit is not initialized.');
87
+ }
88
+ }
89
+
90
+ #validateBuildResult(): asserts this is typeof Aggregator & {
91
+ buildResult: SwapBuildResult;
92
+ } {
93
+ if (!this.buildResult) {
94
+ throw new Error(
95
+ 'Build result is not available. Please build the best route transaction first.'
96
+ );
97
+ }
98
+ }
99
+
100
+ // ------------ CLIENT MANAGEMENT ---------------
101
+ changeSuiClient(client: SuiClient) {
102
+ this.aggregators.forEach((aggregator) => {
103
+ aggregator.changeClient(client);
104
+ });
105
+ this.client = client;
106
+ }
107
+
108
+ changeWalletAddress(address: string) {
109
+ if (this.suiKit) {
110
+ throw new Error(
111
+ 'Cannot change wallet address when SuiKit is initialized with secretKey or mnemonics.'
112
+ );
113
+ }
114
+
115
+ if (!isValidSuiAddress(address)) {
116
+ throw new Error('Invalid Sui wallet address');
117
+ }
118
+
119
+ this.walletAddress = address;
120
+ this.aggregators.forEach((aggregator) => {
121
+ aggregator.setWalletAddress(address);
122
+ });
123
+ }
124
+
125
+ // ------------ COINS METADATA ---------------
126
+ async getCoinMetadata(coinType: string) {
127
+ return this.coinMetadata.getCoinMetadata(coinType);
128
+ }
129
+
130
+ // ------------ METHODS ---------------
131
+ /**
132
+ * Fetch swap routes from all aggregators, and sort them by best output amount
133
+ * @param params FetchRouteParams
134
+ */
135
+ async fetchRoute(params: FetchRouteParams) {
136
+ console.log(
137
+ 'Available aggregators: ',
138
+ this.aggregators.map((t) => t.name).join(', ')
139
+ );
140
+ console.log(
141
+ `Fetching routes for ${params.coinInType} -> ${params.coinOutType}...`
142
+ );
143
+ const results = await Promise.allSettled(
144
+ this.aggregators.map((aggregator) => aggregator.fetchRoute(params))
145
+ );
146
+
147
+ // Filter only successful results
148
+ const successfulResults = results
149
+ .filter((t) => t.status === 'fulfilled')
150
+ .map((t) => t.value);
151
+
152
+ // Sort by best output amount
153
+ const sorted = successfulResults.sort((a, b) =>
154
+ BigNumber(b.formattedResult.coinOut.amount)
155
+ .minus(a.formattedResult.coinOut.amount)
156
+ .toNumber()
157
+ );
158
+
159
+ this.routes = sorted;
160
+ return sorted;
161
+ }
162
+
163
+ /**
164
+ * Build transaction with the best route
165
+ * @param txExtensionParams Optional transaction extension parameters
166
+ */
167
+ async buildBestRouteTransaction(txExtensionParams?: {
168
+ initTx: Transaction;
169
+ coinIn: TransactionObjectArgument;
170
+ }) {
171
+ if (this.routes.length === 0) {
172
+ throw new Error('No routes available. Please fetch routes first.');
173
+ }
174
+
175
+ const bestRoute = this.routes[0];
176
+
177
+ // Get aggregator
178
+ const aggregator = this.aggregators.find(
179
+ (t) => t.name === bestRoute.formattedResult.name
180
+ );
181
+ if (!aggregator) {
182
+ throw new Error(
183
+ `Aggregator ${bestRoute.formattedResult.name} not found in the aggregator list`
184
+ );
185
+ }
186
+
187
+ const [
188
+ { decimals: decimalsIn, symbol: symbolIn },
189
+ { decimals: decimalsOut, symbol: symbolOut },
190
+ ] = await Promise.all([
191
+ this.coinMetadata.getCoinMetadata(bestRoute.formattedResult.coinIn.type),
192
+ this.coinMetadata.getCoinMetadata(bestRoute.formattedResult.coinOut.type),
193
+ ]);
194
+
195
+ const amountIn = BigNumber(bestRoute.formattedResult.coinIn.amount)
196
+ .shiftedBy(-decimalsIn)
197
+ .toString();
198
+
199
+ const amountOut = BigNumber(bestRoute.formattedResult.coinOut.amount)
200
+ .shiftedBy(-decimalsOut)
201
+ .toString();
202
+
203
+ console.log(
204
+ `Swapping ${amountIn} ${symbolIn} for ${amountOut} ${symbolOut} via ${bestRoute.formattedResult.name}`
205
+ );
206
+
207
+ // Build transaction
208
+ const buildResult = await aggregator.buildSwapTransaction({
209
+ slippage: this.slippage,
210
+ txExtensionParams,
211
+ });
212
+ this.buildResult = {
213
+ ...buildResult,
214
+ coinOutType: bestRoute.formattedResult.coinOut.type,
215
+ };
216
+ return buildResult;
217
+ }
218
+
219
+ /**
220
+ * Merge or transfer the output coin to user's wallet and execute the best route transaction
221
+ */
222
+ async executeBestRoute(): Promise<SuiTransactionBlockResponse>;
223
+ async executeBestRoute(dryRun: true): Promise<DryRunTransactionBlockResponse>;
224
+ async executeBestRoute(dryRun: false): Promise<SuiTransactionBlockResponse>;
225
+
226
+ async executeBestRoute(dryRun: boolean = false) {
227
+ this.#validateSuiKit();
228
+ this.#validateBuildResult();
229
+
230
+ await mergeWithExistingOrTransfer(
231
+ this.buildResult.tx,
232
+ this.buildResult.coinOut,
233
+ this.suiKit.client,
234
+ this.buildResult.coinOutType,
235
+ this.suiKit.currentAddress
236
+ );
237
+
238
+ if (dryRun) {
239
+ // inferred: Promise<DryRunTransactionBlockResponse>
240
+ return this.suiKit.dryRunTxn(this.buildResult.tx);
241
+ }
242
+ // inferred: Promise<SuiTransactionBlockResponse>
243
+ return this.suiKit.signAndSendTxn(this.buildResult.tx);
244
+ }
245
+ }