@cetusprotocol/aggregator-sdk 0.0.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/.env.example +4 -0
- package/README.md +128 -0
- package/bun.lockb +0 -0
- package/dist/index.d.mts +266 -0
- package/dist/index.d.ts +266 -0
- package/dist/index.js +6817 -0
- package/dist/index.mjs +6738 -0
- package/dist/src/client.d.ts +87 -0
- package/dist/src/config.d.ts +26 -0
- package/dist/src/const.d.ts +75 -0
- package/dist/src/errors.d.ts +31 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/math.d.ts +5 -0
- package/dist/src/test_data.test.d.ts +8 -0
- package/dist/src/transaction/aftermath.d.ts +24 -0
- package/dist/src/transaction/cetus.d.ts +39 -0
- package/dist/src/transaction/common.d.ts +12 -0
- package/dist/src/transaction/deepbook.d.ts +21 -0
- package/dist/src/transaction/flowx.d.ts +20 -0
- package/dist/src/transaction/index.d.ts +1 -0
- package/dist/src/transaction/kriya.d.ts +21 -0
- package/dist/src/transaction/router.d.ts +6 -0
- package/dist/src/transaction/swap.d.ts +5 -0
- package/dist/src/transaction/turbos.d.ts +22 -0
- package/dist/src/types/CoinAssist.d.ts +122 -0
- package/dist/src/types/sui.d.ts +112 -0
- package/dist/src/utils/account_cap.d.ts +7 -0
- package/dist/src/utils/coin.d.ts +4 -0
- package/dist/src/utils/coin.spec.d.ts +1 -0
- package/dist/src/utils/contracts.d.ts +16 -0
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/transaction.d.ts +3 -0
- package/dist/tests/router.test.d.ts +2 -0
- package/dist/tests/wallet.test.d.ts +1 -0
- package/jest.config.mjs +13 -0
- package/package.json +41 -0
- package/src/client.ts +383 -0
- package/src/config.ts +65 -0
- package/src/const.ts +126 -0
- package/src/errors.ts +44 -0
- package/src/index.ts +5 -0
- package/src/math.ts +37 -0
- package/src/test_data.test.ts +17 -0
- package/src/transaction/aftermath.ts +143 -0
- package/src/transaction/cetus.ts +282 -0
- package/src/transaction/common.ts +169 -0
- package/src/transaction/deepbook.ts +126 -0
- package/src/transaction/flowx.ts +98 -0
- package/src/transaction/index.ts +1 -0
- package/src/transaction/kriya.ts +77 -0
- package/src/transaction/router.ts +345 -0
- package/src/transaction/swap.ts +163 -0
- package/src/transaction/turbos.ts +114 -0
- package/src/types/CoinAssist.ts +217 -0
- package/src/types/sui.ts +148 -0
- package/src/utils/account_cap.ts +62 -0
- package/src/utils/coin.spec.ts +10 -0
- package/src/utils/coin.ts +61 -0
- package/src/utils/contracts.ts +136 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/transaction.ts +20 -0
- package/tests/router.test.ts +249 -0
- package/tests/wallet.test.ts +17 -0
- package/tsconfig.json +22 -0
- package/tsup.config.ts +23 -0
- package/version.mjs +28 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Transaction } from "@mysten/sui/transactions"
|
|
2
|
+
import { SwapInPoolsParams } from "~/client"
|
|
3
|
+
import { AggregatorConfig } from "~/config"
|
|
4
|
+
import { compareCoins, completionCoin } from "~/utils/coin"
|
|
5
|
+
import {
|
|
6
|
+
CETUS_DEX,
|
|
7
|
+
INTEGRATE,
|
|
8
|
+
RouterData,
|
|
9
|
+
SwapInPoolsResult,
|
|
10
|
+
U64_MAX_BN,
|
|
11
|
+
ZERO,
|
|
12
|
+
} from ".."
|
|
13
|
+
import { ConfigErrorCode, TransactionErrorCode } from "~/errors"
|
|
14
|
+
import { checkInvalidSuiAddress } from "~/utils/transaction"
|
|
15
|
+
import { SuiClient } from "@mysten/sui/client"
|
|
16
|
+
import { BN } from "bn.js"
|
|
17
|
+
import { sqrtPriceX64ToPrice } from "~/math"
|
|
18
|
+
|
|
19
|
+
export async function swapInPools(
|
|
20
|
+
client: SuiClient,
|
|
21
|
+
params: SwapInPoolsParams,
|
|
22
|
+
config: AggregatorConfig
|
|
23
|
+
): Promise<SwapInPoolsResult> {
|
|
24
|
+
const { from, target, amount, byAmountIn, pools } = params
|
|
25
|
+
const fromCoin = completionCoin(from)
|
|
26
|
+
const targetCoin = completionCoin(target)
|
|
27
|
+
|
|
28
|
+
const tx = new Transaction()
|
|
29
|
+
const a2b = compareCoins(fromCoin, targetCoin)
|
|
30
|
+
|
|
31
|
+
const integratePackage = config.getPackage(INTEGRATE)
|
|
32
|
+
if (integratePackage == null) {
|
|
33
|
+
throw new AggregateError(
|
|
34
|
+
"Aggregator package not set",
|
|
35
|
+
ConfigErrorCode.MissAggregatorPackage
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
const integratePublishedAt = integratePackage.publishedAt
|
|
39
|
+
|
|
40
|
+
const coinA = a2b ? fromCoin : targetCoin
|
|
41
|
+
const coinB = a2b ? targetCoin : fromCoin
|
|
42
|
+
|
|
43
|
+
const typeArguments = [coinA, coinB]
|
|
44
|
+
for (let i = 0; i < pools.length; i++) {
|
|
45
|
+
const args = [
|
|
46
|
+
tx.object(pools[i]),
|
|
47
|
+
tx.pure.bool(a2b),
|
|
48
|
+
tx.pure.bool(byAmountIn),
|
|
49
|
+
tx.pure.u64(amount.toString()),
|
|
50
|
+
]
|
|
51
|
+
tx.moveCall({
|
|
52
|
+
target: `${integratePublishedAt}::fetcher_script::calculate_swap_result`,
|
|
53
|
+
arguments: args,
|
|
54
|
+
typeArguments,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!checkInvalidSuiAddress(config.getWallet())) {
|
|
59
|
+
throw new AggregateError(
|
|
60
|
+
"Aggregator package not set",
|
|
61
|
+
ConfigErrorCode.InvalidWallet
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const simulateRes = await client.devInspectTransactionBlock({
|
|
66
|
+
transactionBlock: tx,
|
|
67
|
+
sender: config.getWallet(),
|
|
68
|
+
})
|
|
69
|
+
if (simulateRes.error != null) {
|
|
70
|
+
throw new AggregateError(
|
|
71
|
+
"Aggregator package not set",
|
|
72
|
+
ConfigErrorCode.SimulateError
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const valueData: any = simulateRes.events?.filter((item: any) => {
|
|
77
|
+
return item.type.includes("CalculatedSwapResultEvent")
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
if (valueData.length === 0 || valueData.length !== pools.length) {
|
|
81
|
+
throw new AggregateError(
|
|
82
|
+
"Simulate event result error",
|
|
83
|
+
TransactionErrorCode.SimulateEventError
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let tempMaxAmount = byAmountIn ? ZERO : U64_MAX_BN
|
|
88
|
+
let tempIndex = 0
|
|
89
|
+
for (let i = 0; i < valueData.length; i += 1) {
|
|
90
|
+
if (valueData[i].parsedJson.data.is_exceed) {
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (params.byAmountIn) {
|
|
95
|
+
const amount = new BN(valueData[i].parsedJson.data.amount_out)
|
|
96
|
+
if (amount.gt(tempMaxAmount)) {
|
|
97
|
+
tempIndex = i
|
|
98
|
+
tempMaxAmount = amount
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
const amount = new BN(valueData[i].parsedJson.data.amount_out)
|
|
102
|
+
if (amount.lt(tempMaxAmount)) {
|
|
103
|
+
tempIndex = i
|
|
104
|
+
tempMaxAmount = amount
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const event = valueData[tempIndex].parsedJson.data
|
|
110
|
+
const currentSqrtPrice = event.step_results[0].current_sqrt_price
|
|
111
|
+
|
|
112
|
+
const [decimalA, decimalB] = await Promise.all([
|
|
113
|
+
client
|
|
114
|
+
.getCoinMetadata({ coinType: coinA })
|
|
115
|
+
.then((metadata) => metadata?.decimals),
|
|
116
|
+
client
|
|
117
|
+
.getCoinMetadata({ coinType: coinB })
|
|
118
|
+
.then((metadata) => metadata?.decimals),
|
|
119
|
+
])
|
|
120
|
+
|
|
121
|
+
if (decimalA == null || decimalB == null) {
|
|
122
|
+
throw new AggregateError(
|
|
123
|
+
"Simulate event result error",
|
|
124
|
+
TransactionErrorCode.CannotGetDecimals
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
const initialPrice = sqrtPriceX64ToPrice(
|
|
128
|
+
currentSqrtPrice,
|
|
129
|
+
decimalA!,
|
|
130
|
+
decimalB!
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
const routeData = {
|
|
134
|
+
amountIn: new BN(event.amount_in ?? 0),
|
|
135
|
+
amountOut: new BN(event.amount_out ?? 0),
|
|
136
|
+
routes: [
|
|
137
|
+
{
|
|
138
|
+
path: [
|
|
139
|
+
{
|
|
140
|
+
id: pools[tempIndex],
|
|
141
|
+
a2b,
|
|
142
|
+
provider: CETUS_DEX,
|
|
143
|
+
from: fromCoin,
|
|
144
|
+
target: targetCoin,
|
|
145
|
+
feeRate: 0,
|
|
146
|
+
amountIn: 0,
|
|
147
|
+
amountOut: 0,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
amountIn: new BN(event.amount_in ?? 0),
|
|
151
|
+
amountOut: new BN(event.amount_out ?? 0),
|
|
152
|
+
initialPrice,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const result = {
|
|
158
|
+
isExceed: event.is_exceed,
|
|
159
|
+
routeData,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return result
|
|
163
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TransactionArgument,
|
|
3
|
+
Transaction,
|
|
4
|
+
TransactionObjectArgument,
|
|
5
|
+
} from "@mysten/sui/transactions"
|
|
6
|
+
import { AggregatorConfig } from "../config"
|
|
7
|
+
import {
|
|
8
|
+
AGGREGATOR,
|
|
9
|
+
CLOCK_ADDRESS,
|
|
10
|
+
SWAP_A2B_FUNC,
|
|
11
|
+
SWAP_B2A_FUNC,
|
|
12
|
+
TURBOS_MODULE,
|
|
13
|
+
TURBOS_VERSIONED,
|
|
14
|
+
} from "../const"
|
|
15
|
+
import { ConfigErrorCode, TransactionErrorCode } from "../errors"
|
|
16
|
+
import { createTarget } from "../utils"
|
|
17
|
+
|
|
18
|
+
export type TurbosSwapParams = {
|
|
19
|
+
poolId: string
|
|
20
|
+
amount: TransactionArgument
|
|
21
|
+
amountLimit: number
|
|
22
|
+
a2b: boolean
|
|
23
|
+
byAmountIn: boolean
|
|
24
|
+
coinA?: TransactionObjectArgument
|
|
25
|
+
coinB?: TransactionObjectArgument
|
|
26
|
+
useFullInputCoinAmount: boolean
|
|
27
|
+
coinAType: string
|
|
28
|
+
coinBType: string
|
|
29
|
+
feeType: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type TurbosSwapResult = {
|
|
33
|
+
targetCoin: TransactionObjectArgument
|
|
34
|
+
amountIn: TransactionArgument
|
|
35
|
+
amountOut: TransactionArgument
|
|
36
|
+
txb: Transaction
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function turbosSwapMovecall(
|
|
40
|
+
swapParams: TurbosSwapParams,
|
|
41
|
+
txb: Transaction,
|
|
42
|
+
config: AggregatorConfig
|
|
43
|
+
): Promise<TurbosSwapResult> {
|
|
44
|
+
const aggregatorPackage = config.getPackage(AGGREGATOR)
|
|
45
|
+
if (aggregatorPackage == null) {
|
|
46
|
+
throw new AggregateError(
|
|
47
|
+
"Aggregator package not set",
|
|
48
|
+
ConfigErrorCode.MissAggregatorPackage
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
const aggregatorPublishedAt = aggregatorPackage.publishedAt
|
|
52
|
+
|
|
53
|
+
if (swapParams.a2b) {
|
|
54
|
+
if (swapParams.coinA == null) {
|
|
55
|
+
throw new AggregateError(
|
|
56
|
+
"coinA is required",
|
|
57
|
+
TransactionErrorCode.MissCoinA
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
if (swapParams.coinB == null) {
|
|
62
|
+
throw new AggregateError(
|
|
63
|
+
"coinB is required",
|
|
64
|
+
TransactionErrorCode.MissCoinB
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const sqrtPriceLimit = swapParams.a2b
|
|
70
|
+
? "4295048016"
|
|
71
|
+
: "79226673515401279992447579055"
|
|
72
|
+
|
|
73
|
+
const args = swapParams.a2b
|
|
74
|
+
? [
|
|
75
|
+
txb.object(swapParams.poolId),
|
|
76
|
+
swapParams.amount,
|
|
77
|
+
txb.pure.u64(swapParams.amountLimit),
|
|
78
|
+
swapParams.coinA!,
|
|
79
|
+
txb.pure.bool(swapParams.useFullInputCoinAmount),
|
|
80
|
+
txb.pure.u128(sqrtPriceLimit),
|
|
81
|
+
txb.object(CLOCK_ADDRESS),
|
|
82
|
+
txb.object(TURBOS_VERSIONED),
|
|
83
|
+
]
|
|
84
|
+
: [
|
|
85
|
+
txb.object(swapParams.poolId),
|
|
86
|
+
swapParams.amount,
|
|
87
|
+
txb.pure.u64(swapParams.amountLimit),
|
|
88
|
+
swapParams.coinB!,
|
|
89
|
+
txb.pure.bool(swapParams.useFullInputCoinAmount),
|
|
90
|
+
txb.pure.u128(sqrtPriceLimit),
|
|
91
|
+
txb.object(CLOCK_ADDRESS),
|
|
92
|
+
txb.object(TURBOS_VERSIONED),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
const func = swapParams.a2b ? SWAP_A2B_FUNC : SWAP_B2A_FUNC
|
|
96
|
+
|
|
97
|
+
const target = createTarget(aggregatorPublishedAt, TURBOS_MODULE, func)
|
|
98
|
+
|
|
99
|
+
const res = txb.moveCall({
|
|
100
|
+
target,
|
|
101
|
+
typeArguments: [
|
|
102
|
+
swapParams.coinAType,
|
|
103
|
+
swapParams.coinBType,
|
|
104
|
+
swapParams.feeType,
|
|
105
|
+
],
|
|
106
|
+
arguments: args,
|
|
107
|
+
})
|
|
108
|
+
return {
|
|
109
|
+
targetCoin: res[0],
|
|
110
|
+
amountIn: res[1],
|
|
111
|
+
amountOut: res[2],
|
|
112
|
+
txb,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import type { SuiMoveObject } from '@mysten/sui/client'
|
|
2
|
+
import type { CoinAsset, SuiAddress } from './sui'
|
|
3
|
+
import { extractStructTagFromType, normalizeCoinType } from '../utils/contracts'
|
|
4
|
+
|
|
5
|
+
const COIN_TYPE = '0x2::coin::Coin'
|
|
6
|
+
const COIN_TYPE_ARG_REGEX = /^0x2::coin::Coin<(.+)>$/
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_GAS_BUDGET_FOR_SPLIT = 1000
|
|
9
|
+
export const DEFAULT_GAS_BUDGET_FOR_MERGE = 500
|
|
10
|
+
export const DEFAULT_GAS_BUDGET_FOR_TRANSFER = 100
|
|
11
|
+
export const DEFAULT_GAS_BUDGET_FOR_TRANSFER_SUI = 100
|
|
12
|
+
export const DEFAULT_GAS_BUDGET_FOR_STAKE = 1000
|
|
13
|
+
export const GAS_TYPE_ARG = '0x2::sui::SUI'
|
|
14
|
+
export const GAS_TYPE_ARG_LONG = '0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI'
|
|
15
|
+
export const GAS_SYMBOL = 'SUI'
|
|
16
|
+
export const DEFAULT_NFT_TRANSFER_GAS_FEE = 450
|
|
17
|
+
export const SUI_SYSTEM_STATE_OBJECT_ID = '0x0000000000000000000000000000000000000005'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* This class provides helper methods for working with coins.
|
|
21
|
+
*/
|
|
22
|
+
export class CoinUtils {
|
|
23
|
+
/**
|
|
24
|
+
* Get the coin type argument from a SuiMoveObject.
|
|
25
|
+
*
|
|
26
|
+
* @param obj The SuiMoveObject to get the coin type argument from.
|
|
27
|
+
* @returns The coin type argument, or null if it is not found.
|
|
28
|
+
*/
|
|
29
|
+
public static getCoinTypeArg(obj: SuiMoveObject) {
|
|
30
|
+
const res = obj.type.match(COIN_TYPE_ARG_REGEX)
|
|
31
|
+
return res ? res[1] : null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get whether a SuiMoveObject is a SUI coin.
|
|
36
|
+
*
|
|
37
|
+
* @param obj The SuiMoveObject to check.
|
|
38
|
+
* @returns Whether the SuiMoveObject is a SUI coin.
|
|
39
|
+
*/
|
|
40
|
+
public static isSUI(obj: SuiMoveObject) {
|
|
41
|
+
const arg = CoinUtils.getCoinTypeArg(obj)
|
|
42
|
+
return arg ? CoinUtils.getCoinSymbol(arg) === 'SUI' : false
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the coin symbol from a coin type argument.
|
|
47
|
+
*
|
|
48
|
+
* @param coinTypeArg The coin type argument to get the symbol from.
|
|
49
|
+
* @returns The coin symbol.
|
|
50
|
+
*/
|
|
51
|
+
public static getCoinSymbol(coinTypeArg: string) {
|
|
52
|
+
return coinTypeArg.substring(coinTypeArg.lastIndexOf(':') + 1)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the balance of a SuiMoveObject.
|
|
57
|
+
*
|
|
58
|
+
* @param obj The SuiMoveObject to get the balance from.
|
|
59
|
+
* @returns The balance of the SuiMoveObject.
|
|
60
|
+
*/
|
|
61
|
+
public static getBalance(obj: SuiMoveObject): bigint {
|
|
62
|
+
return BigInt((obj.fields as any).balance)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the total balance of a list of CoinAsset objects for a given coin address.
|
|
67
|
+
*
|
|
68
|
+
* @param objs The list of CoinAsset objects to get the total balance for.
|
|
69
|
+
* @param coinAddress The coin address to get the total balance for.
|
|
70
|
+
* @returns The total balance of the CoinAsset objects for the given coin address.
|
|
71
|
+
*/
|
|
72
|
+
public static totalBalance(objs: CoinAsset[], coinAddress: SuiAddress): bigint {
|
|
73
|
+
let balanceTotal = BigInt(0)
|
|
74
|
+
objs.forEach((obj) => {
|
|
75
|
+
if (coinAddress === obj.coinAddress) {
|
|
76
|
+
balanceTotal += BigInt(obj.balance)
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
return balanceTotal
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get the ID of a SuiMoveObject.
|
|
84
|
+
*
|
|
85
|
+
* @param obj The SuiMoveObject to get the ID from.
|
|
86
|
+
* @returns The ID of the SuiMoveObject.
|
|
87
|
+
*/
|
|
88
|
+
public static getID(obj: SuiMoveObject): string {
|
|
89
|
+
return (obj.fields as any).id.id
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get the coin type from a coin type argument.
|
|
94
|
+
*
|
|
95
|
+
* @param coinTypeArg The coin type argument to get the coin type from.
|
|
96
|
+
* @returns The coin type.
|
|
97
|
+
*/
|
|
98
|
+
public static getCoinTypeFromArg(coinTypeArg: string) {
|
|
99
|
+
return `${COIN_TYPE}<${coinTypeArg}>`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get the CoinAsset objects for a given coin type.
|
|
104
|
+
*
|
|
105
|
+
* @param coinType The coin type to get the CoinAsset objects for.
|
|
106
|
+
* @param allSuiObjects The list of all SuiMoveObjects.
|
|
107
|
+
* @returns The CoinAsset objects for the given coin type.
|
|
108
|
+
*/
|
|
109
|
+
public static getCoinAssets(coinType: string, allSuiObjects: CoinAsset[]): CoinAsset[] {
|
|
110
|
+
const coins: CoinAsset[] = []
|
|
111
|
+
allSuiObjects.forEach((anObj) => {
|
|
112
|
+
if (normalizeCoinType(anObj.coinAddress) === normalizeCoinType(coinType)) {
|
|
113
|
+
coins.push(anObj)
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
return coins
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get whether a coin address is a SUI coin.
|
|
121
|
+
*
|
|
122
|
+
* @param coinAddress The coin address to check.
|
|
123
|
+
* @returns Whether the coin address is a SUI coin.
|
|
124
|
+
*/
|
|
125
|
+
public static isSuiCoin(coinAddress: SuiAddress) {
|
|
126
|
+
return extractStructTagFromType(coinAddress).full_address === GAS_TYPE_ARG
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Select the CoinAsset objects from a list of CoinAsset objects that have a balance greater than or equal to a given amount.
|
|
131
|
+
*
|
|
132
|
+
* @param coins The list of CoinAsset objects to select from.
|
|
133
|
+
* @param amount The amount to select CoinAsset objects with a balance greater than or equal to.
|
|
134
|
+
* @param exclude A list of CoinAsset objects to exclude from the selection.
|
|
135
|
+
* @returns The CoinAsset objects that have a balance greater than or equal to the given amount.
|
|
136
|
+
*/
|
|
137
|
+
static selectCoinObjectIdGreaterThanOrEqual(
|
|
138
|
+
coins: CoinAsset[],
|
|
139
|
+
amount: bigint,
|
|
140
|
+
exclude: string[] = []
|
|
141
|
+
): { objectArray: string[]; remainCoins: CoinAsset[]; amountArray: string[] } {
|
|
142
|
+
const selectedResult = CoinUtils.selectCoinAssetGreaterThanOrEqual(coins, amount, exclude)
|
|
143
|
+
const objectArray = selectedResult.selectedCoins.map((item) => item.coinObjectId)
|
|
144
|
+
const remainCoins = selectedResult.remainingCoins
|
|
145
|
+
const amountArray = selectedResult.selectedCoins.map((item) => item.balance.toString())
|
|
146
|
+
return { objectArray, remainCoins, amountArray }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Select the CoinAsset objects from a list of CoinAsset objects that have a balance greater than or equal to a given amount.
|
|
151
|
+
*
|
|
152
|
+
* @param coins The list of CoinAsset objects to select from.
|
|
153
|
+
* @param amount The amount to select CoinAsset objects with a balance greater than or equal to.
|
|
154
|
+
* @param exclude A list of CoinAsset objects to exclude from the selection.
|
|
155
|
+
* @returns The CoinAsset objects that have a balance greater than or equal to the given amount.
|
|
156
|
+
*/
|
|
157
|
+
static selectCoinAssetGreaterThanOrEqual(
|
|
158
|
+
coins: CoinAsset[],
|
|
159
|
+
amount: bigint,
|
|
160
|
+
exclude: string[] = []
|
|
161
|
+
): { selectedCoins: CoinAsset[]; remainingCoins: CoinAsset[] } {
|
|
162
|
+
const sortedCoins = CoinUtils.sortByBalance(coins.filter((c) => !exclude.includes(c.coinObjectId)))
|
|
163
|
+
|
|
164
|
+
const total = CoinUtils.calculateTotalBalance(sortedCoins)
|
|
165
|
+
|
|
166
|
+
if (total < amount) {
|
|
167
|
+
return { selectedCoins: [], remainingCoins: sortedCoins }
|
|
168
|
+
}
|
|
169
|
+
if (total === amount) {
|
|
170
|
+
return { selectedCoins: sortedCoins, remainingCoins: [] }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let sum = BigInt(0)
|
|
174
|
+
const selectedCoins = []
|
|
175
|
+
const remainingCoins = [...sortedCoins]
|
|
176
|
+
while (sum < total) {
|
|
177
|
+
const target = amount - sum
|
|
178
|
+
const coinWithSmallestSufficientBalanceIndex = remainingCoins.findIndex((c) => c.balance >= target)
|
|
179
|
+
if (coinWithSmallestSufficientBalanceIndex !== -1) {
|
|
180
|
+
selectedCoins.push(remainingCoins[coinWithSmallestSufficientBalanceIndex])
|
|
181
|
+
remainingCoins.splice(coinWithSmallestSufficientBalanceIndex, 1)
|
|
182
|
+
break
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const coinWithLargestBalance = remainingCoins.pop()!
|
|
186
|
+
if (coinWithLargestBalance.balance > 0) {
|
|
187
|
+
selectedCoins.push(coinWithLargestBalance)
|
|
188
|
+
sum += coinWithLargestBalance.balance
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { selectedCoins: CoinUtils.sortByBalance(selectedCoins), remainingCoins: CoinUtils.sortByBalance(remainingCoins) }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Sort the CoinAsset objects by their balance.
|
|
196
|
+
*
|
|
197
|
+
* @param coins The CoinAsset objects to sort.
|
|
198
|
+
* @returns The sorted CoinAsset objects.
|
|
199
|
+
*/
|
|
200
|
+
static sortByBalance(coins: CoinAsset[]): CoinAsset[] {
|
|
201
|
+
return coins.sort((a, b) => (a.balance < b.balance ? -1 : a.balance > b.balance ? 1 : 0))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
static sortByBalanceDes(coins: CoinAsset[]): CoinAsset[] {
|
|
205
|
+
return coins.sort((a, b) => (a.balance > b.balance ? -1 : a.balance < b.balance ? 0 : 1))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Calculate the total balance of a list of CoinAsset objects.
|
|
210
|
+
*
|
|
211
|
+
* @param coins The list of CoinAsset objects to calculate the total balance for.
|
|
212
|
+
* @returns The total balance of the CoinAsset objects.
|
|
213
|
+
*/
|
|
214
|
+
static calculateTotalBalance(coins: CoinAsset[]): bigint {
|
|
215
|
+
return coins.reduce((partialSum, c) => partialSum + c.balance, BigInt(0))
|
|
216
|
+
}
|
|
217
|
+
}
|
package/src/types/sui.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { TransactionArgument } from '@mysten/sui/transactions'
|
|
2
|
+
import Decimal from 'decimal.js'
|
|
3
|
+
import { TypesErrorCode } from '../errors'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents a SUI address, which is a string.
|
|
7
|
+
*/
|
|
8
|
+
export type SuiAddress = string
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Represents a SUI object identifier, which is a string.
|
|
12
|
+
*/
|
|
13
|
+
export type SuiObjectIdType = string
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Represents a BigNumber, which can be a Decimal.Value, number, or string.
|
|
17
|
+
*/
|
|
18
|
+
export type BigNumber = Decimal.Value | number | string
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Represents a SUI resource, which can be of any type.
|
|
23
|
+
*/
|
|
24
|
+
export type SuiResource = any
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Represents a Non-Fungible Token (NFT) with associated metadata.
|
|
28
|
+
*/
|
|
29
|
+
export type NFT = {
|
|
30
|
+
/**
|
|
31
|
+
* The address or identifier of the creator of the NFT.
|
|
32
|
+
*/
|
|
33
|
+
creator: string
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A description providing additional information about the NFT.
|
|
37
|
+
*/
|
|
38
|
+
description: string
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The URL to the image representing the NFT visually.
|
|
42
|
+
*/
|
|
43
|
+
image_url: string
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A link associated with the NFT, providing more details or interactions.
|
|
47
|
+
*/
|
|
48
|
+
link: string
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The name or title of the NFT.
|
|
52
|
+
*/
|
|
53
|
+
name: string
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The URL to the project or collection associated with the NFT.
|
|
57
|
+
*/
|
|
58
|
+
project_url: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Represents a SUI struct tag.
|
|
63
|
+
*/
|
|
64
|
+
export type SuiStructTag = {
|
|
65
|
+
/**
|
|
66
|
+
* The full address of the struct.
|
|
67
|
+
*/
|
|
68
|
+
full_address: string
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The source address of the struct.
|
|
72
|
+
*/
|
|
73
|
+
source_address: string
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The address of the struct.
|
|
77
|
+
*/
|
|
78
|
+
address: SuiAddress
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The module to which the struct belongs.
|
|
82
|
+
*/
|
|
83
|
+
module: string
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The name of the struct.
|
|
87
|
+
*/
|
|
88
|
+
name: string
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* An array of type arguments (SUI addresses) for the struct.
|
|
92
|
+
*/
|
|
93
|
+
type_arguments: SuiAddress[]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Represents basic SUI data types.
|
|
98
|
+
*/
|
|
99
|
+
export type SuiBasicTypes = 'address' | 'bool' | 'u8' | 'u16' | 'u32' | 'u64' | 'u128' | 'u256'
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Represents a SUI transaction argument, which can be of various types.
|
|
103
|
+
*/
|
|
104
|
+
export type SuiTxArg = TransactionArgument | string | number | bigint | boolean
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Represents input types for SUI data.
|
|
108
|
+
*/
|
|
109
|
+
export type SuiInputTypes = 'object' | SuiBasicTypes
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Gets the default SUI input type based on the provided value.
|
|
113
|
+
* @param value - The value to determine the default input type for.
|
|
114
|
+
* @returns The default SUI input type.
|
|
115
|
+
* @throws Error if the type of the value is unknown.
|
|
116
|
+
*/
|
|
117
|
+
export const getDefaultSuiInputType = (value: any): SuiInputTypes => {
|
|
118
|
+
if (typeof value === 'string' && value.startsWith('0x')) {
|
|
119
|
+
return 'object'
|
|
120
|
+
}
|
|
121
|
+
if (typeof value === 'number' || typeof value === 'bigint') {
|
|
122
|
+
return 'u64'
|
|
123
|
+
}
|
|
124
|
+
if (typeof value === 'boolean') {
|
|
125
|
+
return 'bool'
|
|
126
|
+
}
|
|
127
|
+
throw new AggregateError(`Unknown type for value: ${value}`, TypesErrorCode.InvalidType)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Represents a coin asset with address, object ID, and balance information.
|
|
132
|
+
*/
|
|
133
|
+
export type CoinAsset = {
|
|
134
|
+
/**
|
|
135
|
+
* The address type of the coin asset.
|
|
136
|
+
*/
|
|
137
|
+
coinAddress: SuiAddress
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The object identifier of the coin asset.
|
|
141
|
+
*/
|
|
142
|
+
coinObjectId: SuiObjectIdType
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The balance amount of the coin asset.
|
|
146
|
+
*/
|
|
147
|
+
balance: bigint
|
|
148
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { SuiClient } from "@mysten/sui/client"
|
|
2
|
+
import { DEEPBOOK_CLOB_V2_MODULE, DEEPBOOK_CUSTODIAN_V2_MODULE, DEEPBOOK_PACKAGE_ID, DEEPBOOK_PUBLISHED_AT } from "../const"
|
|
3
|
+
import { Transaction, TransactionObjectArgument } from "@mysten/sui/transactions"
|
|
4
|
+
|
|
5
|
+
export type GetOrCreateAccountCapResult = {
|
|
6
|
+
accountCap: TransactionObjectArgument,
|
|
7
|
+
isCreate: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function getOrCreateAccountCap(txb: Transaction, client: SuiClient, owner: string): Promise<GetOrCreateAccountCapResult> {
|
|
11
|
+
let accountCapStr = await getAccountCap(client, owner)
|
|
12
|
+
if (accountCapStr !== null) {
|
|
13
|
+
return {
|
|
14
|
+
accountCap: txb.object(accountCapStr),
|
|
15
|
+
isCreate: false,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const accountCap = txb.moveCall({
|
|
20
|
+
target: `${DEEPBOOK_PUBLISHED_AT}::${DEEPBOOK_CLOB_V2_MODULE}::create_account`,
|
|
21
|
+
typeArguments: [],
|
|
22
|
+
arguments: [],
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
accountCap,
|
|
27
|
+
isCreate: true,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function getAccountCap(client: SuiClient, owner: string): Promise<string | null> {
|
|
32
|
+
let limit = 50;
|
|
33
|
+
let cursor = null;
|
|
34
|
+
|
|
35
|
+
while (true) {
|
|
36
|
+
const ownedObjects: any = client.getOwnedObjects({
|
|
37
|
+
owner,
|
|
38
|
+
cursor,
|
|
39
|
+
limit,
|
|
40
|
+
filter: {
|
|
41
|
+
MoveModule: {
|
|
42
|
+
package: DEEPBOOK_PACKAGE_ID,
|
|
43
|
+
module: DEEPBOOK_CUSTODIAN_V2_MODULE,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
if (ownedObjects != null && ownedObjects.data != null) {
|
|
49
|
+
if (ownedObjects.data.length !== 0) {
|
|
50
|
+
return ownedObjects.data[0].data.objectId
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (ownedObjects.data.length < 50) {
|
|
54
|
+
break
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
break
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { parseTurbosPoolFeeType } from "./coin"
|
|
2
|
+
|
|
3
|
+
describe("Coin Utils", () => {
|
|
4
|
+
it("should fetch token infos by URL and return data", async () => {
|
|
5
|
+
const typeDate =
|
|
6
|
+
"0x91bfbc386a41afcfd9b2533058d7e915a1d3829089cc268ff4333d54d6339ca1::pool::Pool<0xc91acfb75009c5ff2fd57c54f3caaee12ad1fbe997681334adc0b574fc277a07::icorgi::ICORGI, 0x2::sui::SUI, 0x91bfbc386a41afcfd9b2533058d7e915a1d3829089cc268ff4333d54d6339ca1::fee10000bps::FEE10000BPS>"
|
|
7
|
+
const result = parseTurbosPoolFeeType(typeDate)
|
|
8
|
+
console.log("parse turbos pool type", result)
|
|
9
|
+
})
|
|
10
|
+
})
|