@cetusprotocol/aggregator-sdk 0.3.32 → 0.4.1
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/README.md +19 -8
- package/dist/index.d.mts +21 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.js +211 -33
- package/dist/index.mjs +211 -34
- package/dist/src/api.d.ts +5 -1
- package/dist/src/client.d.ts +15 -1
- package/dist/src/transaction/obric.d.ts +8 -0
- package/dist/tests/router/alphafi.test.d.ts +2 -0
- package/dist/tests/router/obric.test.d.ts +2 -0
- package/package.json +1 -1
- package/src/api.ts +12 -3
- package/src/client.ts +184 -31
- package/src/transaction/obric.ts +90 -0
- package/tests/router/alphafi.test.ts +132 -0
- package/tests/router/metastable.test.ts +7 -1
- package/tests/router/obric.test.ts +203 -0
- package/tests/router/scallop.test.ts +7 -1
- package/tests/router/steamm.test.ts +7 -1
- package/tests/router.test.ts +7 -1
package/src/client.ts
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
} from "@pythnetwork/pyth-sui-js"
|
|
50
50
|
import { Steamm } from "./transaction/steamm"
|
|
51
51
|
import { Metastable } from "./transaction/metastable"
|
|
52
|
+
import { Obric } from "./transaction/obric"
|
|
52
53
|
|
|
53
54
|
export const CETUS = "CETUS"
|
|
54
55
|
export const DEEPBOOKV2 = "DEEPBOOK"
|
|
@@ -71,6 +72,7 @@ export const ALPHAFI = "ALPHAFI"
|
|
|
71
72
|
export const SPRINGSUI = "SPRINGSUI"
|
|
72
73
|
export const STEAMM = "STEAMM"
|
|
73
74
|
export const METASTABLE = "METASTABLE"
|
|
75
|
+
export const OBRIC = "OBRIC"
|
|
74
76
|
export const DEFAULT_ENDPOINT = "https://api-sui.cetus.zone/router_v2"
|
|
75
77
|
|
|
76
78
|
export type BuildRouterSwapParams = {
|
|
@@ -124,7 +126,6 @@ export interface SwapInPoolsParams {
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
interface PythConfig {
|
|
127
|
-
connections: SuiPriceServiceConnection[]
|
|
128
129
|
wormholeStateId: string
|
|
129
130
|
pythStateId: string
|
|
130
131
|
}
|
|
@@ -146,11 +147,21 @@ function isBuilderFastRouterSwapParams(
|
|
|
146
147
|
return Array.isArray((params as BuildFastRouterSwapParams).routers)
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
export type AggregatorClientParams = {
|
|
151
|
+
endpoint?: string
|
|
152
|
+
signer?: string
|
|
153
|
+
client?: SuiClient
|
|
154
|
+
env?: Env
|
|
155
|
+
pythUrls?: string[]
|
|
156
|
+
apiKey?: string
|
|
157
|
+
}
|
|
158
|
+
|
|
149
159
|
export class AggregatorClient {
|
|
150
160
|
public endpoint: string
|
|
151
161
|
public signer: string
|
|
152
162
|
public client: SuiClient
|
|
153
163
|
public env: Env
|
|
164
|
+
public apiKey: string
|
|
154
165
|
private allCoins: Map<string, CoinAsset[]>
|
|
155
166
|
|
|
156
167
|
private pythConnections: SuiPriceServiceConnection[]
|
|
@@ -158,26 +169,12 @@ export class AggregatorClient {
|
|
|
158
169
|
|
|
159
170
|
private static readonly CONFIG: Record<Env, PythConfig> = {
|
|
160
171
|
[Env.Testnet]: {
|
|
161
|
-
connections: [
|
|
162
|
-
new SuiPriceServiceConnection("https://hermes-beta.pyth.network"),
|
|
163
|
-
],
|
|
164
172
|
wormholeStateId:
|
|
165
173
|
"0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790",
|
|
166
174
|
pythStateId:
|
|
167
175
|
"0x243759059f4c3111179da5878c12f68d612c21a8d54d85edc86164bb18be1c7c",
|
|
168
176
|
},
|
|
169
177
|
[Env.Mainnet]: {
|
|
170
|
-
connections: [
|
|
171
|
-
new SuiPriceServiceConnection(
|
|
172
|
-
"https://cetus-pythnet-a648.mainnet.pythnet.rpcpool.com/219cf7a8-6d75-432d-a648-d487a6dd5dc3/hermes",
|
|
173
|
-
{
|
|
174
|
-
timeout: 3000,
|
|
175
|
-
}
|
|
176
|
-
),
|
|
177
|
-
new SuiPriceServiceConnection("https://hermes.pyth.network", {
|
|
178
|
-
timeout: 3000,
|
|
179
|
-
}),
|
|
180
|
-
],
|
|
181
178
|
wormholeStateId:
|
|
182
179
|
"0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c",
|
|
183
180
|
pythStateId:
|
|
@@ -185,25 +182,30 @@ export class AggregatorClient {
|
|
|
185
182
|
},
|
|
186
183
|
}
|
|
187
184
|
|
|
188
|
-
constructor(
|
|
189
|
-
endpoint
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
env
|
|
193
|
-
) {
|
|
194
|
-
this.endpoint = endpoint ? processEndpoint(endpoint) : DEFAULT_ENDPOINT
|
|
195
|
-
this.client = client || new SuiClient({ url: getFullnodeUrl("mainnet") })
|
|
196
|
-
this.signer = signer || ""
|
|
197
|
-
this.env = env || Env.Mainnet
|
|
185
|
+
constructor(params: AggregatorClientParams) {
|
|
186
|
+
this.endpoint = params.endpoint ? processEndpoint(params.endpoint) : DEFAULT_ENDPOINT
|
|
187
|
+
this.client = params.client || new SuiClient({ url: getFullnodeUrl("mainnet") })
|
|
188
|
+
this.signer = params.signer || ""
|
|
189
|
+
this.env = params.env || Env.Mainnet
|
|
198
190
|
this.allCoins = new Map<string, CoinAsset[]>()
|
|
199
191
|
|
|
200
192
|
const config = AggregatorClient.CONFIG[this.env]
|
|
201
|
-
this.pythConnections =
|
|
193
|
+
this.pythConnections = this.newPythClients(params.pythUrls ?? [])
|
|
202
194
|
this.pythClient = new SuiPythClient(
|
|
203
195
|
this.client,
|
|
204
196
|
config.pythStateId,
|
|
205
197
|
config.wormholeStateId
|
|
206
198
|
)
|
|
199
|
+
this.apiKey = params.apiKey || ""
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
newPythClients(pythUrls: string[]) {
|
|
203
|
+
if (!pythUrls.includes("https://hermes.pyth.network")) {
|
|
204
|
+
pythUrls.push("https://hermes.pyth.network")
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const connections = pythUrls.map(url => new SuiPriceServiceConnection(url, { timeout: 3000 }))
|
|
208
|
+
return connections
|
|
207
209
|
}
|
|
208
210
|
|
|
209
211
|
async getCoins(
|
|
@@ -255,7 +257,79 @@ export class AggregatorClient {
|
|
|
255
257
|
}
|
|
256
258
|
|
|
257
259
|
async findRouters(params: FindRouterParams): Promise<RouterData | null> {
|
|
258
|
-
return getRouterResult(this.endpoint, params)
|
|
260
|
+
return getRouterResult(this.endpoint, this.apiKey, params)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async executeFlexibleInputSwap(
|
|
264
|
+
txb: Transaction,
|
|
265
|
+
inputCoin: TransactionObjectArgument,
|
|
266
|
+
routers: Router[],
|
|
267
|
+
amountOutLimit: BN,
|
|
268
|
+
pythPriceIDs: Map<string, string>,
|
|
269
|
+
partner?: string,
|
|
270
|
+
deepbookv3DeepFee?: TransactionObjectArgument,
|
|
271
|
+
packages?: Map<string, string>
|
|
272
|
+
) {
|
|
273
|
+
if (routers.length === 0) {
|
|
274
|
+
throw new Error("No router found")
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const outputCoinType = routers[0].path[routers[0].path.length - 1].target
|
|
278
|
+
const outputCoins = []
|
|
279
|
+
|
|
280
|
+
for (let i = 0; i < routers.length - 1; i++) {
|
|
281
|
+
if (routers[i].path.length === 0) {
|
|
282
|
+
throw new Error("Empty path")
|
|
283
|
+
}
|
|
284
|
+
// 为每条路径分割所需的代币
|
|
285
|
+
const splitCoin = txb.splitCoins(inputCoin, [routers[i].amountIn.toString()])
|
|
286
|
+
let nextCoin = splitCoin[0] as TransactionObjectArgument
|
|
287
|
+
|
|
288
|
+
for (const path of routers[i].path) {
|
|
289
|
+
const dex = this.newDex(path.provider, pythPriceIDs, partner)
|
|
290
|
+
nextCoin = await dex.swap(
|
|
291
|
+
this,
|
|
292
|
+
txb,
|
|
293
|
+
path,
|
|
294
|
+
nextCoin,
|
|
295
|
+
packages,
|
|
296
|
+
deepbookv3DeepFee
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
outputCoins.push(nextCoin)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 处理最后一条路径,使用剩余的所有代币
|
|
303
|
+
if (routers[routers.length - 1].path.length === 0) {
|
|
304
|
+
throw new Error("Empty path")
|
|
305
|
+
}
|
|
306
|
+
let lastCoin = inputCoin
|
|
307
|
+
for (const path of routers[routers.length - 1].path) {
|
|
308
|
+
const dex = this.newDex(path.provider, pythPriceIDs, partner)
|
|
309
|
+
lastCoin = await dex.swap(
|
|
310
|
+
this,
|
|
311
|
+
txb,
|
|
312
|
+
path,
|
|
313
|
+
lastCoin,
|
|
314
|
+
packages,
|
|
315
|
+
deepbookv3DeepFee
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
outputCoins.push(lastCoin)
|
|
319
|
+
|
|
320
|
+
const aggregatorV2PublishedAt = getAggregatorV2PublishedAt(
|
|
321
|
+
this.publishedAtV2(),
|
|
322
|
+
packages
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
const mergedTargetCoin = this.checkCoinThresholdAndMergeCoin(
|
|
326
|
+
txb,
|
|
327
|
+
outputCoins,
|
|
328
|
+
outputCoinType,
|
|
329
|
+
amountOutLimit,
|
|
330
|
+
aggregatorV2PublishedAt
|
|
331
|
+
)
|
|
332
|
+
return mergedTargetCoin
|
|
259
333
|
}
|
|
260
334
|
|
|
261
335
|
async expectInputSwap(
|
|
@@ -476,6 +550,69 @@ export class AggregatorClient {
|
|
|
476
550
|
return targetCoin
|
|
477
551
|
}
|
|
478
552
|
|
|
553
|
+
async fixableRouterSwap(
|
|
554
|
+
params: BuildRouterSwapParamsV2
|
|
555
|
+
): Promise<TransactionObjectArgument> {
|
|
556
|
+
const { routers, inputCoin, slippage, txb, partner, deepbookv3DeepFee } =
|
|
557
|
+
params
|
|
558
|
+
|
|
559
|
+
const routerData = Array.isArray(routers) ? routers : routers.routes
|
|
560
|
+
const byAmountIn = params.routers.byAmountIn
|
|
561
|
+
|
|
562
|
+
const amountIn = routerData.reduce(
|
|
563
|
+
(acc, router) => acc.add(router.amountIn),
|
|
564
|
+
new BN(0)
|
|
565
|
+
)
|
|
566
|
+
const amountOut = routerData.reduce(
|
|
567
|
+
(acc, router) => acc.add(router.amountOut),
|
|
568
|
+
new BN(0)
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
const amountLimit = CalculateAmountLimitBN(
|
|
572
|
+
byAmountIn ? amountOut : amountIn,
|
|
573
|
+
byAmountIn,
|
|
574
|
+
slippage
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
const packages = isBuilderRouterSwapParams(params)
|
|
578
|
+
? undefined
|
|
579
|
+
: params.routers.packages
|
|
580
|
+
|
|
581
|
+
const aggregatorV2PublishedAt = getAggregatorV2PublishedAt(
|
|
582
|
+
this.publishedAtV2(),
|
|
583
|
+
packages
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
const priceIDs = findPythPriceIDs(routerData)
|
|
587
|
+
|
|
588
|
+
const priceInfoObjectIds =
|
|
589
|
+
priceIDs.length > 0
|
|
590
|
+
? await this.updatePythPriceIDs(priceIDs, txb)
|
|
591
|
+
: new Map<string, string>()
|
|
592
|
+
|
|
593
|
+
if (byAmountIn) {
|
|
594
|
+
const targetCoin = await this.executeFlexibleInputSwap(
|
|
595
|
+
txb,
|
|
596
|
+
inputCoin,
|
|
597
|
+
routerData,
|
|
598
|
+
amountLimit,
|
|
599
|
+
priceInfoObjectIds,
|
|
600
|
+
partner,
|
|
601
|
+
deepbookv3DeepFee,
|
|
602
|
+
packages
|
|
603
|
+
)
|
|
604
|
+
return targetCoin
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const targetCoin = await this.expectOutputSwap(
|
|
608
|
+
txb,
|
|
609
|
+
inputCoin,
|
|
610
|
+
routerData,
|
|
611
|
+
partner
|
|
612
|
+
)
|
|
613
|
+
return targetCoin
|
|
614
|
+
}
|
|
615
|
+
|
|
479
616
|
// auto build input coin
|
|
480
617
|
// auto merge, transfer or destory target coin.
|
|
481
618
|
async fastRouterSwap(
|
|
@@ -611,7 +748,8 @@ export class AggregatorClient {
|
|
|
611
748
|
// return "0xf2fcea41dc217385019828375764fa06d9bd25e8e4726ba1962680849fb8d613" // version 8
|
|
612
749
|
// return "0xa2d8a4279d69d8fec04b2fea8852d0d467d3cc0d39c5890180d439ae7a9953ed" // version 9
|
|
613
750
|
// return "0x34ef25b60b51f9d07cd9b7dc5b08dfdf26c7b0ff00c57bb17454c161fa6b6b83" // version 10
|
|
614
|
-
return "0xf57be4b9f9036034b1c5484d299d8fb68d5f43862d6afe8886d67db293dfc4bc" // version 11
|
|
751
|
+
// return "0xf57be4b9f9036034b1c5484d299d8fb68d5f43862d6afe8886d67db293dfc4bc" // version 11
|
|
752
|
+
return "0x200e762fa2c49f3dc150813038fbf22fd4f894ac6f23ebe1085c62f2ef97f1ca" // version 12
|
|
615
753
|
} else {
|
|
616
754
|
return "0xabb6a81c8a216828e317719e06125de5bb2cb0fe8f9916ff8c023ca5be224c78"
|
|
617
755
|
}
|
|
@@ -712,6 +850,8 @@ export class AggregatorClient {
|
|
|
712
850
|
return new Steamm(this.env)
|
|
713
851
|
case METASTABLE:
|
|
714
852
|
return new Metastable(this.env, pythPriceIDs)
|
|
853
|
+
case OBRIC:
|
|
854
|
+
return new Obric(this.env, pythPriceIDs)
|
|
715
855
|
default:
|
|
716
856
|
throw new Error(`Unsupported dex ${provider}`)
|
|
717
857
|
}
|
|
@@ -776,7 +916,7 @@ export class AggregatorClient {
|
|
|
776
916
|
|
|
777
917
|
if (priceUpdateData == null) {
|
|
778
918
|
throw new Error(
|
|
779
|
-
`
|
|
919
|
+
`All Pyth price nodes are unavailable. Cannot fetch price data. Please switch to or add new available Pyth nodes. Detailed error: ${lastError?.message}`
|
|
780
920
|
)
|
|
781
921
|
}
|
|
782
922
|
|
|
@@ -788,7 +928,7 @@ export class AggregatorClient {
|
|
|
788
928
|
priceIDs
|
|
789
929
|
)
|
|
790
930
|
} catch (e) {
|
|
791
|
-
throw new Error(`
|
|
931
|
+
throw new Error(`All Pyth price nodes are unavailable. Cannot fetch price data. Please switch to or add new available Pyth nodes in the pythUrls parameter when initializing AggregatorClient, for example: new AggregatorClient({ pythUrls: ["https://your-pyth-node-url"] }). Detailed error: ${e}`)
|
|
792
932
|
}
|
|
793
933
|
|
|
794
934
|
let priceInfoObjectIdsMap = new Map<string, string>()
|
|
@@ -822,6 +962,14 @@ export function findPythPriceIDs(routes: Router[]): string[] {
|
|
|
822
962
|
priceIDs.add(path.extendedDetails.metastableETHPriceSeed)
|
|
823
963
|
}
|
|
824
964
|
}
|
|
965
|
+
if (path.provider === OBRIC) {
|
|
966
|
+
if (path.extendedDetails && path.extendedDetails.obricCoinAPriceSeed) {
|
|
967
|
+
priceIDs.add(path.extendedDetails.obricCoinAPriceSeed)
|
|
968
|
+
}
|
|
969
|
+
if (path.extendedDetails && path.extendedDetails.obricCoinBPriceSeed) {
|
|
970
|
+
priceIDs.add(path.extendedDetails.obricCoinBPriceSeed)
|
|
971
|
+
}
|
|
972
|
+
}
|
|
825
973
|
}
|
|
826
974
|
}
|
|
827
975
|
return Array.from(priceIDs)
|
|
@@ -871,7 +1019,8 @@ export function parseRouterResponse(
|
|
|
871
1019
|
path.provider === SCALLOP ||
|
|
872
1020
|
path.provider === HAEDALPMM ||
|
|
873
1021
|
path.provider === STEAMM ||
|
|
874
|
-
path.provider === METASTABLE
|
|
1022
|
+
path.provider === METASTABLE ||
|
|
1023
|
+
path.provider === OBRIC
|
|
875
1024
|
) {
|
|
876
1025
|
extendedDetails = {
|
|
877
1026
|
aftermathLpSupplyType:
|
|
@@ -899,6 +1048,10 @@ export function parseRouterResponse(
|
|
|
899
1048
|
metastableCreateCapModule: path.extended_details?.metastable_create_cap_module,
|
|
900
1049
|
metastableCreateCapAllTypeParams: path.extended_details?.metastable_create_cap_all_type_params,
|
|
901
1050
|
metastableRegistryId: path.extended_details?.metastable_registry_id,
|
|
1051
|
+
obricCoinAPriceSeed: path.extended_details?.obric_coin_a_price_seed,
|
|
1052
|
+
obricCoinBPriceSeed: path.extended_details?.obric_coin_b_price_seed,
|
|
1053
|
+
obricCoinAPriceId: path.extended_details?.obric_coin_a_price_id,
|
|
1054
|
+
obricCoinBPriceId: path.extended_details?.obric_coin_b_price_id,
|
|
902
1055
|
}
|
|
903
1056
|
}
|
|
904
1057
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Transaction,
|
|
3
|
+
TransactionArgument,
|
|
4
|
+
TransactionObjectArgument,
|
|
5
|
+
} from "@mysten/sui/transactions"
|
|
6
|
+
import {
|
|
7
|
+
AggregatorClient,
|
|
8
|
+
CLOCK_ADDRESS,
|
|
9
|
+
Dex,
|
|
10
|
+
Env,
|
|
11
|
+
getAggregatorV2ExtendPublishedAt,
|
|
12
|
+
Path,
|
|
13
|
+
} from ".."
|
|
14
|
+
|
|
15
|
+
export class Obric implements Dex {
|
|
16
|
+
private pythPriceIDs: Map<string, string>
|
|
17
|
+
private pythStateObjectId: string
|
|
18
|
+
|
|
19
|
+
constructor(env: Env, pythPriceIDs: Map<string, string>) {
|
|
20
|
+
if (env === Env.Testnet) {
|
|
21
|
+
throw new Error("Obric is not supported on testnet")
|
|
22
|
+
}
|
|
23
|
+
this.pythPriceIDs = pythPriceIDs
|
|
24
|
+
this.pythStateObjectId = "0x1f9310238ee9298fb703c3419030b35b22bb1cc37113e3bb5007c99aec79e5b8"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async swap(
|
|
28
|
+
client: AggregatorClient,
|
|
29
|
+
txb: Transaction,
|
|
30
|
+
path: Path,
|
|
31
|
+
inputCoin: TransactionObjectArgument,
|
|
32
|
+
packages?: Map<string, string>
|
|
33
|
+
): Promise<TransactionObjectArgument> {
|
|
34
|
+
const { direction, from, target } = path
|
|
35
|
+
const [func, coinAType, coinBType] = direction
|
|
36
|
+
? ["swap_a2b", from, target]
|
|
37
|
+
: ["swap_b2a", target, from]
|
|
38
|
+
|
|
39
|
+
let coinAPriceSeed
|
|
40
|
+
let coinBPriceSeed
|
|
41
|
+
|
|
42
|
+
let coinAPriceInfoObjectId
|
|
43
|
+
let coinBPriceInfoObjectId
|
|
44
|
+
|
|
45
|
+
if (path.extendedDetails == null) {
|
|
46
|
+
throw new Error("Extended details not supported in obric")
|
|
47
|
+
} else {
|
|
48
|
+
if (
|
|
49
|
+
path.extendedDetails.obricCoinAPriceSeed && path.extendedDetails.obricCoinBPriceSeed
|
|
50
|
+
) {
|
|
51
|
+
coinAPriceSeed = path.extendedDetails.obricCoinAPriceSeed
|
|
52
|
+
coinAPriceInfoObjectId = this.pythPriceIDs.get(coinAPriceSeed!)
|
|
53
|
+
coinBPriceSeed = path.extendedDetails.obricCoinBPriceSeed
|
|
54
|
+
coinBPriceInfoObjectId = this.pythPriceIDs.get(coinBPriceSeed!)
|
|
55
|
+
} else {
|
|
56
|
+
if (!path.extendedDetails.obricCoinAPriceId || !path.extendedDetails.obricCoinBPriceId) {
|
|
57
|
+
throw new Error("Base price id or quote price id not supported")
|
|
58
|
+
} else {
|
|
59
|
+
coinAPriceInfoObjectId = path.extendedDetails.obricCoinAPriceId
|
|
60
|
+
coinBPriceInfoObjectId = path.extendedDetails.obricCoinBPriceId
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!coinAPriceInfoObjectId || !coinBPriceInfoObjectId) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
"Base price info object id or quote price info object id not found"
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const args = [
|
|
72
|
+
txb.object(path.id),
|
|
73
|
+
inputCoin,
|
|
74
|
+
txb.object(this.pythStateObjectId),
|
|
75
|
+
txb.object(coinAPriceInfoObjectId),
|
|
76
|
+
txb.object(coinBPriceInfoObjectId),
|
|
77
|
+
txb.object(CLOCK_ADDRESS),
|
|
78
|
+
]
|
|
79
|
+
const publishedAt = getAggregatorV2ExtendPublishedAt(
|
|
80
|
+
client.publishedAtV2Extend(),
|
|
81
|
+
packages
|
|
82
|
+
)
|
|
83
|
+
const res = txb.moveCall({
|
|
84
|
+
target: `${publishedAt}::obric::${func}`,
|
|
85
|
+
typeArguments: [coinAType, coinBType],
|
|
86
|
+
arguments: args,
|
|
87
|
+
}) as TransactionArgument
|
|
88
|
+
return res
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { describe, test } from "@jest/globals"
|
|
2
|
+
import dotenv from "dotenv"
|
|
3
|
+
import { AggregatorClient } from "~/client"
|
|
4
|
+
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519"
|
|
5
|
+
import { printTransaction } from "~/utils/transaction"
|
|
6
|
+
import BN from "bn.js"
|
|
7
|
+
import { fromB64 } from "@mysten/sui/utils"
|
|
8
|
+
import { SuiClient } from "@mysten/sui/client"
|
|
9
|
+
import { Env } from "~/index"
|
|
10
|
+
import { Transaction } from "@mysten/sui/transactions"
|
|
11
|
+
|
|
12
|
+
dotenv.config()
|
|
13
|
+
|
|
14
|
+
export function buildTestAccount(): Ed25519Keypair {
|
|
15
|
+
const mnemonics = process.env.SUI_WALLET_MNEMONICS || ""
|
|
16
|
+
const testAccountObject = Ed25519Keypair.deriveKeypair(mnemonics)
|
|
17
|
+
return testAccountObject
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("Test scallop provider", () => {
|
|
21
|
+
let client: AggregatorClient
|
|
22
|
+
let keypair: Ed25519Keypair
|
|
23
|
+
|
|
24
|
+
const T_SUI = "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI"
|
|
25
|
+
const LSDT_SUI = "0xd1b72982e40348d069bb1ff701e634c117bb5f741f44dff91e472d3b01461e55::stsui::STSUI"
|
|
26
|
+
const T_USDC = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC"
|
|
27
|
+
|
|
28
|
+
beforeAll(() => {
|
|
29
|
+
const fullNodeURL = process.env.SUI_RPC!
|
|
30
|
+
const aggregatorURL = process.env.CETUS_AGGREGATOR!
|
|
31
|
+
const secret = process.env.SUI_WALLET_SECRET!
|
|
32
|
+
|
|
33
|
+
if (secret) {
|
|
34
|
+
keypair = Ed25519Keypair.fromSecretKey(fromB64(secret).slice(1, 33))
|
|
35
|
+
} else {
|
|
36
|
+
keypair = buildTestAccount()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const wallet =
|
|
40
|
+
"0x4dde66fc52ec16d5e6c0fbd0968580cdf0d962cbb970591ec1e47617b9265617"
|
|
41
|
+
|
|
42
|
+
console.log("wallet: ", wallet)
|
|
43
|
+
|
|
44
|
+
const endpoint = aggregatorURL
|
|
45
|
+
|
|
46
|
+
const suiClient = new SuiClient({
|
|
47
|
+
url: fullNodeURL,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
client = new AggregatorClient({
|
|
51
|
+
endpoint,
|
|
52
|
+
signer: wallet,
|
|
53
|
+
client: suiClient,
|
|
54
|
+
env: Env.Mainnet,
|
|
55
|
+
pythUrls: ["https://cetus-pythnet-a648.mainnet.pythnet.rpcpool.com/219cf7a8-6d75-432d-a648-d487a6dd5dc3/hermes"],
|
|
56
|
+
apiKey: "8MJDUzLDPJxCgbc7I0bHXSg994mVfh8NRMqV6hcQ",
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test("Find Routers", async () => {
|
|
61
|
+
const amounts = ["1000", "1000000", "100000000", "5000000000", "1000000000000000000000000000"]
|
|
62
|
+
|
|
63
|
+
while (true) {
|
|
64
|
+
const res = await client.findRouters({
|
|
65
|
+
from: LSDT_SUI,
|
|
66
|
+
target: T_USDC,
|
|
67
|
+
amount: new BN("1000000000000"),
|
|
68
|
+
byAmountIn: true,
|
|
69
|
+
depth: 3,
|
|
70
|
+
splitCount: 1,
|
|
71
|
+
providers: ["ALPHAFI"],
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
if (res != null) {
|
|
75
|
+
console.log(JSON.stringify(res, null, 2))
|
|
76
|
+
}
|
|
77
|
+
console.log("amount in", res?.amountIn.toString())
|
|
78
|
+
console.log("amount out", res?.amountOut.toString())
|
|
79
|
+
}
|
|
80
|
+
}, 6000000)
|
|
81
|
+
|
|
82
|
+
test("Build Router TX", async () => {
|
|
83
|
+
const amount = "100000"
|
|
84
|
+
|
|
85
|
+
const res = await client.findRouters({
|
|
86
|
+
from: LSDT_SUI,
|
|
87
|
+
target: T_SUI,
|
|
88
|
+
amount: new BN(amount),
|
|
89
|
+
byAmountIn: true,
|
|
90
|
+
depth: 3,
|
|
91
|
+
splitCount: 2,
|
|
92
|
+
providers: ["ALPHAFI", "CETUS"],
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
console.log("amount in", res?.amountIn.toString())
|
|
96
|
+
console.log("amount out", res?.amountOut.toString())
|
|
97
|
+
|
|
98
|
+
const txb = new Transaction()
|
|
99
|
+
|
|
100
|
+
if (res != null) {
|
|
101
|
+
console.log(JSON.stringify(res, null, 2))
|
|
102
|
+
await client.fastRouterSwap({
|
|
103
|
+
routers: res,
|
|
104
|
+
txb,
|
|
105
|
+
slippage: 0.01,
|
|
106
|
+
refreshAllCoins: true,
|
|
107
|
+
payDeepFeeAmount: 0,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
txb.setSender(client.signer)
|
|
111
|
+
const buildTxb = await txb.build({ client: client.client })
|
|
112
|
+
// const buildTxb = await txb.getData()
|
|
113
|
+
|
|
114
|
+
console.log("buildTxb", buildTxb)
|
|
115
|
+
|
|
116
|
+
printTransaction(txb)
|
|
117
|
+
|
|
118
|
+
let result = await client.devInspectTransactionBlock(txb)
|
|
119
|
+
console.log("🚀 ~ file: router.test.ts:180 ~ test ~ result:", result)
|
|
120
|
+
for (const event of result.events) {
|
|
121
|
+
console.log("event", JSON.stringify(event, null, 2))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (result.effects.status.status === "success") {
|
|
125
|
+
const result = await client.signAndExecuteTransaction(txb, keypair)
|
|
126
|
+
console.log("result", result)
|
|
127
|
+
} else {
|
|
128
|
+
console.log("result", result)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}, 600000)
|
|
132
|
+
})
|
|
@@ -57,7 +57,13 @@ describe("Test metastable provider", () => {
|
|
|
57
57
|
const suiClient = new SuiClient({
|
|
58
58
|
url: fullNodeURL,
|
|
59
59
|
})
|
|
60
|
-
client = new AggregatorClient(
|
|
60
|
+
client = new AggregatorClient({
|
|
61
|
+
endpoint,
|
|
62
|
+
signer: wallet,
|
|
63
|
+
client: suiClient,
|
|
64
|
+
env: Env.Mainnet,
|
|
65
|
+
pythUrls: ["https://cetus-pythnet-a648.mainnet.pythnet.rpcpool.com/219cf7a8-6d75-432d-a648-d487a6dd5dc3/hermes"],
|
|
66
|
+
})
|
|
61
67
|
})
|
|
62
68
|
|
|
63
69
|
test("Find Routers --> SUI -> SUPER_SUI", async () => {
|