@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
package/src/sdk/flowx.ts
ADDED
|
@@ -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
|
+
}
|
package/src/sdk/index.ts
ADDED
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
|
+
};
|