@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,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,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 {};
|
package/dist/sdk/7k.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -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
|
+
}
|