@tradeport/sui-trading-sdk 0.4.62 → 0.4.64
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/dist/index.d.mts +21 -50
- package/dist/index.d.ts +21 -50
- package/dist/index.js +255 -631
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +248 -619
- package/dist/index.mjs.map +1 -1
- package/package.json +59 -61
- package/src/SuiTradingClient.ts +37 -65
- package/src/index.ts +0 -1
- package/src/methods/buyListings/buyListings.ts +4 -11
- package/src/methods/createMultiBid/createMultiBid.ts +3 -4
- package/src/methods/listNfts/listNfts.ts +7 -9
- package/src/methods/placeCollectionBids/placeCollectionBids.ts +14 -13
- package/src/methods/placeNftBids/placeNftBids.ts +6 -12
- package/src/methods/sponsorNftListing/addSponsorNftListingTx.ts +14 -44
- package/src/methods/sponsorNftListing/sponsorNftListing.ts +10 -15
- package/src/methods/updateMultiBid/updateMultiBid.ts +19 -15
- package/src/utils/addLeadingZerosAfter0x.ts +7 -0
- package/.claude/settings.local.json +0 -5
- package/src/helpers/deserializeOrCreateTxBlock.ts +0 -13
- package/src/helpers/extractSwapResultCoin.ts +0 -17
- package/src/methods/swapCoins/swapCoins.ts +0 -400
|
@@ -1,58 +1,30 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { USDC_COIN_TYPE } from '../../constants';
|
|
3
|
-
import type { SuiClient } from '@mysten/sui/client';
|
|
4
|
-
import { TRADEPORT_LISTINGS_PACKAGE, TRADEPORT_LISTINGS_STORE } from '../../constants';
|
|
5
|
-
import { type SponsorNftListingOptions } from './sponsorNftListing';
|
|
1
|
+
import { coinWithBalance, type Transaction } from '@mysten/sui/transactions';
|
|
6
2
|
import BigNumber from '../../bigNumberConfig';
|
|
7
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
TRADEPORT_LISTINGS_PACKAGE,
|
|
5
|
+
TRADEPORT_LISTINGS_STORE,
|
|
6
|
+
USDC_COIN_TYPE,
|
|
7
|
+
} from '../../constants';
|
|
8
|
+
import { type SponsorNftListingOptions } from './sponsorNftListing';
|
|
8
9
|
|
|
9
10
|
export const addSponsorListingTx = async ({
|
|
10
11
|
tx,
|
|
11
12
|
nftTokenId,
|
|
12
13
|
nftType,
|
|
13
|
-
suiClient,
|
|
14
|
-
walletAddress,
|
|
15
14
|
sponsorOptions,
|
|
16
|
-
defiRouterUrl,
|
|
17
15
|
}: {
|
|
18
16
|
tx: Transaction;
|
|
19
17
|
nftTokenId: string;
|
|
20
18
|
nftType: string;
|
|
21
|
-
suiClient: SuiClient;
|
|
22
|
-
walletAddress: string;
|
|
23
19
|
sponsorOptions: SponsorNftListingOptions;
|
|
24
|
-
defiRouterUrl: string;
|
|
25
20
|
}) => {
|
|
26
|
-
const
|
|
27
|
-
sponsorOptions?.
|
|
28
|
-
(
|
|
29
|
-
coinInType: sponsorOptions?.coinInType,
|
|
30
|
-
coinOutType: USDC_COIN_TYPE,
|
|
31
|
-
coinOutAmount: new BigNumber(sponsorOptions?.usdcFeeAmountPerPeriod)
|
|
32
|
-
?.times(sponsorOptions?.numOfPeriods)
|
|
33
|
-
.toString(),
|
|
34
|
-
slippage: sponsorOptions?.slippage,
|
|
35
|
-
}));
|
|
21
|
+
const totalFee = new BigNumber(sponsorOptions?.usdcFeeAmountPerPeriod)
|
|
22
|
+
?.times(sponsorOptions?.numOfPeriods)
|
|
23
|
+
.toString();
|
|
36
24
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
coinInType: sponsorOptions?.coinInType,
|
|
41
|
-
coinInAmount: coinInAmount.toString(),
|
|
42
|
-
coinOutType: USDC_COIN_TYPE,
|
|
43
|
-
slippage: sponsorOptions?.slippage,
|
|
44
|
-
tx,
|
|
45
|
-
},
|
|
46
|
-
{ suiClient, defiRouterUrl },
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
const [sponsorFeeCoin] = tx.splitCoins(coinOut, [
|
|
50
|
-
tx.pure.u64(
|
|
51
|
-
new BigNumber(sponsorOptions?.usdcFeeAmountPerPeriod)
|
|
52
|
-
?.times(sponsorOptions?.numOfPeriods)
|
|
53
|
-
.toString(),
|
|
54
|
-
),
|
|
55
|
-
]);
|
|
25
|
+
const sponsorCoin = sponsorOptions.coinToSplit
|
|
26
|
+
? tx.splitCoins(sponsorOptions.coinToSplit, [tx.pure.u64(totalFee)])[0]
|
|
27
|
+
: coinWithBalance({ type: USDC_COIN_TYPE, balance: BigInt(totalFee) });
|
|
56
28
|
|
|
57
29
|
tx.moveCall({
|
|
58
30
|
target: `${TRADEPORT_LISTINGS_PACKAGE}::tradeport_listings::add_sponsored_listing`,
|
|
@@ -61,10 +33,8 @@ export const addSponsorListingTx = async ({
|
|
|
61
33
|
tx.object.clock(),
|
|
62
34
|
tx.pure.id(nftTokenId),
|
|
63
35
|
tx.pure.u64(sponsorOptions?.numOfPeriods),
|
|
64
|
-
tx.object(
|
|
36
|
+
tx.object(sponsorCoin),
|
|
65
37
|
],
|
|
66
38
|
typeArguments: [nftType],
|
|
67
39
|
});
|
|
68
|
-
|
|
69
|
-
tx.transferObjects([coinOut], walletAddress);
|
|
70
40
|
};
|
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
import { Transaction } from '@mysten/sui/transactions';
|
|
2
2
|
import { gqlChainRequest } from '../../graphql/gqlChainRequest';
|
|
3
3
|
import { fetchNftCollectionChainState } from '../../graphql/queries/fetchNftCollectionChainState';
|
|
4
|
+
import { getNftType } from '../../helpers/getNftType';
|
|
4
5
|
import { isOriginByteCollection } from '../../helpers/originByte/isOriginByteCollection';
|
|
5
|
-
import type { RequestContext } from '../../SuiTradingClient';
|
|
6
6
|
import { addSponsorListingTx } from './addSponsorNftListingTx';
|
|
7
|
-
import { getNftType } from '../../helpers/getNftType';
|
|
8
7
|
|
|
9
8
|
export type SponsorNftListingOptions = {
|
|
10
9
|
shouldSponsor?: boolean;
|
|
11
10
|
numOfPeriods?: number;
|
|
12
|
-
slippage?: number;
|
|
13
|
-
coinInType?: string;
|
|
14
|
-
coinInAmount?: string;
|
|
15
11
|
usdcFeeAmountPerPeriod?: number;
|
|
12
|
+
coinToSplit?: any;
|
|
16
13
|
};
|
|
17
14
|
|
|
18
15
|
export type SponsorNftListing = {
|
|
16
|
+
tx?: Transaction;
|
|
19
17
|
nftTokenId: string;
|
|
20
|
-
walletAddress: string;
|
|
21
18
|
options: SponsorNftListingOptions;
|
|
22
19
|
};
|
|
23
20
|
|
|
24
|
-
export const sponsorNftListing = async (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
export const sponsorNftListing = async ({
|
|
22
|
+
tx: existingTx,
|
|
23
|
+
nftTokenId,
|
|
24
|
+
options,
|
|
25
|
+
}: SponsorNftListing): Promise<Transaction> => {
|
|
28
26
|
const res = await gqlChainRequest({
|
|
29
27
|
chain: 'sui',
|
|
30
28
|
query: fetchNftCollectionChainState,
|
|
@@ -49,17 +47,14 @@ export const sponsorNftListing = async (
|
|
|
49
47
|
throw new Error(`You cannot sponsor an Origin Byte NFT. Nft Token Id: ${nftTokenId}`);
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
const tx = new Transaction();
|
|
50
|
+
const tx = existingTx ?? new Transaction();
|
|
53
51
|
|
|
54
52
|
await addSponsorListingTx({
|
|
55
53
|
tx,
|
|
56
|
-
suiClient: context.suiClient,
|
|
57
54
|
nftTokenId,
|
|
58
55
|
nftType,
|
|
59
|
-
walletAddress,
|
|
60
56
|
sponsorOptions: options,
|
|
61
|
-
defiRouterUrl: context.defiRouterUrl,
|
|
62
57
|
});
|
|
63
58
|
|
|
64
|
-
return tx
|
|
59
|
+
return tx;
|
|
65
60
|
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Transaction } from '@mysten/sui/transactions';
|
|
2
2
|
import { TRADEPORT_MULTI_BID_PACKAGE, TRADEPORT_MULTI_BID_STORE } from '../../constants';
|
|
3
3
|
import { gqlChainRequest } from '../../graphql/gqlChainRequest';
|
|
4
4
|
import { fetchMultibidChainIdById } from '../../graphql/queries/fetchMultibidById';
|
|
5
|
-
import {
|
|
6
|
-
import { extractSwapResultCoinFromTxBlock } from '../../helpers/extractSwapResultCoin';
|
|
5
|
+
import { type RequestContext } from '../../SuiTradingClient';
|
|
7
6
|
|
|
8
7
|
export interface UpdateMultiBid {
|
|
9
8
|
walletAddress: string;
|
|
@@ -11,20 +10,25 @@ export interface UpdateMultiBid {
|
|
|
11
10
|
name?: string;
|
|
12
11
|
amount?: bigint;
|
|
13
12
|
amountToWithdraw?: bigint;
|
|
14
|
-
tx?: Transaction
|
|
13
|
+
tx?: Transaction;
|
|
15
14
|
multiBidChainId?: any;
|
|
15
|
+
coinToSplit?: any;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export async function updateMultiBid(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
export async function updateMultiBid(
|
|
19
|
+
{
|
|
20
|
+
walletAddress,
|
|
21
|
+
multiBidId,
|
|
22
|
+
name,
|
|
23
|
+
amount,
|
|
24
|
+
amountToWithdraw,
|
|
25
|
+
tx: existingTx,
|
|
26
|
+
multiBidChainId,
|
|
27
|
+
coinToSplit,
|
|
28
|
+
}: UpdateMultiBid,
|
|
29
|
+
context?: Pick<RequestContext, 'suiClient' | 'tradeportRouterUrl'>,
|
|
30
|
+
): Promise<Transaction> {
|
|
31
|
+
const tx = existingTx ?? new Transaction();
|
|
28
32
|
|
|
29
33
|
if (!multiBidChainId) {
|
|
30
34
|
const { chain_id: chainId, cancelled_at } =
|
|
@@ -46,7 +50,7 @@ export async function updateMultiBid({
|
|
|
46
50
|
multiBidChainId = chainId;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
const [coin] = tx.splitCoins(
|
|
53
|
+
const [coin] = tx.splitCoins(coinToSplit ? coinToSplit : tx.gas, [amount ?? 0n]);
|
|
50
54
|
tx.moveCall({
|
|
51
55
|
target: `${TRADEPORT_MULTI_BID_PACKAGE}::tradeport_biddings::update_multi_bid`,
|
|
52
56
|
arguments: [
|
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
export const addLeadingZerosAfter0x = (str: string): string =>
|
|
2
2
|
`0x${str?.replace('0x', '').padStart(64, '0')}`;
|
|
3
|
+
|
|
4
|
+
export const addLeadingZerosToPackage = (str: string, skip0x?: boolean) => {
|
|
5
|
+
const address = str?.split('::')[0];
|
|
6
|
+
const suffix = `::${str?.split('::').slice(1).join('::')}`;
|
|
7
|
+
|
|
8
|
+
return `${skip0x ? '' : '0x'}${address?.replace('0x', '').padStart(64, '0')}${suffix}`;
|
|
9
|
+
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Transaction } from '@mysten/sui/transactions';
|
|
2
|
-
|
|
3
|
-
export const deserializeOrCreateTxBlock = ({
|
|
4
|
-
existingTx,
|
|
5
|
-
}: {
|
|
6
|
-
existingTx: Transaction | string;
|
|
7
|
-
}) => {
|
|
8
|
-
if (typeof existingTx === 'string') {
|
|
9
|
-
return Transaction.from(existingTx);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return existingTx ?? new Transaction();
|
|
13
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { type Transaction } from '@mysten/sui/transactions';
|
|
2
|
-
|
|
3
|
-
export const extractSwapResultCoinFromTxBlock = (txBlock: Transaction | string) => {
|
|
4
|
-
if (!txBlock) return undefined;
|
|
5
|
-
|
|
6
|
-
const tx = typeof txBlock === 'string' ? JSON.parse(txBlock) : txBlock?.getData();
|
|
7
|
-
const commands = tx.commands ?? [];
|
|
8
|
-
|
|
9
|
-
const index = commands.findIndex(
|
|
10
|
-
(cmd: any) =>
|
|
11
|
-
cmd?.MoveCall?.module === 'universal_router' && cmd?.MoveCall?.function === 'settle',
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
if (index === -1) return undefined;
|
|
15
|
-
|
|
16
|
-
return { $kind: 'Result', Result: index };
|
|
17
|
-
};
|
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AggregatorQuoter,
|
|
3
|
-
type Coin,
|
|
4
|
-
CoinProvider,
|
|
5
|
-
type Commission,
|
|
6
|
-
type GetRoutesResult,
|
|
7
|
-
Protocol,
|
|
8
|
-
type Route,
|
|
9
|
-
TradeBuilder,
|
|
10
|
-
} from '@flowx-finance/sdk';
|
|
11
|
-
import BigNumber from '../../bigNumberConfig';
|
|
12
|
-
import { coinWithBalance, Transaction, type TransactionResult } from '@mysten/sui/transactions';
|
|
13
|
-
import { normalizeStructTag, normalizeSuiAddress, SUI_TYPE_ARG } from '@mysten/sui/utils';
|
|
14
|
-
import ms, { type StringValue } from 'ms';
|
|
15
|
-
import { type RequestContext } from '../../SuiTradingClient';
|
|
16
|
-
import {
|
|
17
|
-
DexConstants,
|
|
18
|
-
TRADEPORT_NFT_STRATEGY_MANAGER_ID,
|
|
19
|
-
TRADEPORT_NFT_STRATEGY_PACKAGE_ID,
|
|
20
|
-
} from '../../constants';
|
|
21
|
-
import { getCollectionChainStateByFtType } from '../../helpers/getCollectionChainState';
|
|
22
|
-
import { toDecimalValue, toPureValue } from '../../utils/pureValues';
|
|
23
|
-
|
|
24
|
-
export type SwapCoins = {
|
|
25
|
-
walletAddress: string;
|
|
26
|
-
coinInType: string;
|
|
27
|
-
coinInAmount: string;
|
|
28
|
-
coinOutType: string;
|
|
29
|
-
slippage?: number;
|
|
30
|
-
ttl?: StringValue;
|
|
31
|
-
excludeSources?: Protocol[];
|
|
32
|
-
routes?: Array<Route<Coin, Coin>>;
|
|
33
|
-
commission?: Commission;
|
|
34
|
-
tx?: Transaction;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export type SwapRoutesArgs = {
|
|
38
|
-
walletAddress: string;
|
|
39
|
-
coinInType: string;
|
|
40
|
-
coinOutType: string;
|
|
41
|
-
amountToSwap: bigint;
|
|
42
|
-
excludeSources?: Protocol[];
|
|
43
|
-
commission?: Commission;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export async function swapCoins(
|
|
47
|
-
{
|
|
48
|
-
walletAddress,
|
|
49
|
-
coinInType,
|
|
50
|
-
coinInAmount,
|
|
51
|
-
coinOutType,
|
|
52
|
-
slippage = 1000, // 0.1%
|
|
53
|
-
ttl = '5m',
|
|
54
|
-
excludeSources = [Protocol.STEAMM, Protocol.BOLT],
|
|
55
|
-
routes,
|
|
56
|
-
commission,
|
|
57
|
-
tx: existingTx,
|
|
58
|
-
}: SwapCoins,
|
|
59
|
-
{ suiClient, defiRouterUrl }: Pick<RequestContext, 'suiClient' | 'defiRouterUrl'>,
|
|
60
|
-
): Promise<{
|
|
61
|
-
tx: Transaction;
|
|
62
|
-
coinOut: TransactionResult;
|
|
63
|
-
}> {
|
|
64
|
-
const inAmount = BigInt(coinInAmount);
|
|
65
|
-
const tx = existingTx ?? new Transaction();
|
|
66
|
-
coinInType = normalizeStructTag(coinInType);
|
|
67
|
-
coinOutType = normalizeStructTag(coinOutType);
|
|
68
|
-
walletAddress = normalizeSuiAddress(walletAddress);
|
|
69
|
-
tx.setSenderIfNotSet(normalizeSuiAddress(walletAddress));
|
|
70
|
-
|
|
71
|
-
// Zero input amount, return zero output amount
|
|
72
|
-
if (inAmount === 0n) {
|
|
73
|
-
const zeroCoin = tx.moveCall({
|
|
74
|
-
target: '0x2::coin::zero',
|
|
75
|
-
typeArguments: [coinOutType],
|
|
76
|
-
});
|
|
77
|
-
return {
|
|
78
|
-
tx,
|
|
79
|
-
coinOut: zeroCoin,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Input Coin and Out Coin are the same, no need to swap
|
|
84
|
-
if (coinInType === coinOutType) {
|
|
85
|
-
const getCoin = coinWithBalance({
|
|
86
|
-
type: coinInType,
|
|
87
|
-
balance: inAmount,
|
|
88
|
-
});
|
|
89
|
-
return {
|
|
90
|
-
tx,
|
|
91
|
-
coinOut: getCoin(tx),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const types = await getAffectedNftAndFtTypes(coinInType, coinOutType);
|
|
96
|
-
const { feeAndReward, amountToSwap } = calculateSwapAmounts(types.length, inAmount);
|
|
97
|
-
|
|
98
|
-
if (!routes) {
|
|
99
|
-
routes = (
|
|
100
|
-
await buildSwapRoutes(
|
|
101
|
-
{
|
|
102
|
-
walletAddress,
|
|
103
|
-
coinInType,
|
|
104
|
-
coinOutType,
|
|
105
|
-
|
|
106
|
-
amountToSwap,
|
|
107
|
-
excludeSources,
|
|
108
|
-
commission,
|
|
109
|
-
},
|
|
110
|
-
{ defiRouterUrl },
|
|
111
|
-
)
|
|
112
|
-
).routes;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const tradeBuilder = new TradeBuilder('mainnet', routes);
|
|
116
|
-
if (commission) {
|
|
117
|
-
tradeBuilder.commission(commission);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const trade = tradeBuilder
|
|
121
|
-
.sender(walletAddress)
|
|
122
|
-
.slippage(slippage)
|
|
123
|
-
.deadline(Date.now() + ms(ttl))
|
|
124
|
-
.build();
|
|
125
|
-
|
|
126
|
-
const coinOut = (await trade.swap({ client: suiClient, tx })) as TransactionResult;
|
|
127
|
-
|
|
128
|
-
let suiFeeAndRewardCoin: TransactionResult;
|
|
129
|
-
if (coinInType === normalizeStructTag(SUI_TYPE_ARG)) {
|
|
130
|
-
suiFeeAndRewardCoin = tx.splitCoins(tx.gas, [
|
|
131
|
-
tx.pure.u64(feeAndReward),
|
|
132
|
-
])[0] as unknown as TransactionResult;
|
|
133
|
-
} else {
|
|
134
|
-
// Swap to SUI for fee and reward
|
|
135
|
-
const suiFeeAndRewardRoutes = await buildSwapRoutes(
|
|
136
|
-
{
|
|
137
|
-
walletAddress,
|
|
138
|
-
coinInType,
|
|
139
|
-
coinOutType: SUI_TYPE_ARG,
|
|
140
|
-
amountToSwap: feeAndReward,
|
|
141
|
-
excludeSources,
|
|
142
|
-
},
|
|
143
|
-
{ defiRouterUrl },
|
|
144
|
-
);
|
|
145
|
-
const suiFeeTradeBuilder = new TradeBuilder('mainnet', suiFeeAndRewardRoutes.routes);
|
|
146
|
-
const suiFeeTrade = suiFeeTradeBuilder
|
|
147
|
-
.sender(walletAddress)
|
|
148
|
-
.slippage(slippage)
|
|
149
|
-
.deadline(Date.now() + ms(ttl))
|
|
150
|
-
.build();
|
|
151
|
-
suiFeeAndRewardCoin = (await suiFeeTrade.swap({ client: suiClient, tx })) as TransactionResult;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (types.length > 0) {
|
|
155
|
-
if (types.length > 2) {
|
|
156
|
-
throw new Error('Unexpected affected types count greater than 2');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
let usedSuiCoin = suiFeeAndRewardCoin;
|
|
160
|
-
if (types.length === 2) {
|
|
161
|
-
const suiFeeAndRewardCoinBalance = tx.moveCall({
|
|
162
|
-
target: '0x2::coin::value',
|
|
163
|
-
typeArguments: [SUI_TYPE_ARG],
|
|
164
|
-
arguments: [suiFeeAndRewardCoin],
|
|
165
|
-
});
|
|
166
|
-
const halfSuiCoinAmount = tx.moveCall({
|
|
167
|
-
target: '0x2::math::divide_and_round_up',
|
|
168
|
-
arguments: [suiFeeAndRewardCoinBalance, tx.pure.u64(2)],
|
|
169
|
-
});
|
|
170
|
-
const [coin] = tx.splitCoins(usedSuiCoin, [halfSuiCoinAmount]);
|
|
171
|
-
usedSuiCoin = coin as unknown as TransactionResult; // 1/2 of SUI for 1st iteration, remaining SUI for 2nd iteration
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
for (const data of types) {
|
|
175
|
-
// max 2 iterations
|
|
176
|
-
const { nftType, ftType, type } = data;
|
|
177
|
-
switch (type) {
|
|
178
|
-
case 'liquidNft': {
|
|
179
|
-
// Pay 4% fee + reward for liquid nft, the contract will split this balance itself
|
|
180
|
-
tx.moveCall({
|
|
181
|
-
target: `${DexConstants.commission}::commission::pay`,
|
|
182
|
-
arguments: [
|
|
183
|
-
tx.object(DexConstants.commissionManager),
|
|
184
|
-
tx.pure.string('swap'),
|
|
185
|
-
usedSuiCoin,
|
|
186
|
-
tx.pure.option('string', nftType),
|
|
187
|
-
],
|
|
188
|
-
typeArguments: [SUI_TYPE_ARG],
|
|
189
|
-
});
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
case 'nftStrategy': {
|
|
194
|
-
// Pay 1% fee + 3% reward for nft strategy
|
|
195
|
-
const suiFeeAndRewardCoinBalance = tx.moveCall({
|
|
196
|
-
target: '0x2::coin::value',
|
|
197
|
-
typeArguments: [SUI_TYPE_ARG],
|
|
198
|
-
arguments: [usedSuiCoin],
|
|
199
|
-
});
|
|
200
|
-
const feeAmount = tx.moveCall({
|
|
201
|
-
target: '0x2::math::divide_and_round_up',
|
|
202
|
-
arguments: [suiFeeAndRewardCoinBalance, tx.pure.u64(4)],
|
|
203
|
-
});
|
|
204
|
-
const [feeCoin] = tx.splitCoins(usedSuiCoin, [feeAmount]);
|
|
205
|
-
tx.moveCall({
|
|
206
|
-
target: `${DexConstants.commission}::commission::pay`,
|
|
207
|
-
arguments: [
|
|
208
|
-
tx.object(DexConstants.commissionManager),
|
|
209
|
-
tx.pure.string('swap'),
|
|
210
|
-
feeCoin,
|
|
211
|
-
tx.pure.option('string', undefined),
|
|
212
|
-
],
|
|
213
|
-
typeArguments: [SUI_TYPE_ARG],
|
|
214
|
-
});
|
|
215
|
-
tx.moveCall({
|
|
216
|
-
target: `${TRADEPORT_NFT_STRATEGY_PACKAGE_ID}::tradeport_nft_strategy::reward_strategy`,
|
|
217
|
-
arguments: [
|
|
218
|
-
tx.object(TRADEPORT_NFT_STRATEGY_MANAGER_ID),
|
|
219
|
-
usedSuiCoin, // 3/4 retained coin for reward
|
|
220
|
-
],
|
|
221
|
-
typeArguments: [nftType, ftType],
|
|
222
|
-
});
|
|
223
|
-
break;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
default: {
|
|
227
|
-
throw new Error(`Unexpected type ${type as string}`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
usedSuiCoin = suiFeeAndRewardCoin; // remaining SUI after 1st iteration
|
|
232
|
-
}
|
|
233
|
-
} else {
|
|
234
|
-
// Default, get 1% fee
|
|
235
|
-
tx.moveCall({
|
|
236
|
-
target: `${DexConstants.commission}::commission::pay`,
|
|
237
|
-
arguments: [
|
|
238
|
-
tx.object(DexConstants.commissionManager),
|
|
239
|
-
tx.pure.string('swap'),
|
|
240
|
-
suiFeeAndRewardCoin,
|
|
241
|
-
tx.pure.option('string', undefined),
|
|
242
|
-
],
|
|
243
|
-
typeArguments: [SUI_TYPE_ARG],
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return {
|
|
248
|
-
tx,
|
|
249
|
-
coinOut,
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export async function calculateAmountToSwap({
|
|
254
|
-
coinInType,
|
|
255
|
-
coinOutType,
|
|
256
|
-
coinInAmount,
|
|
257
|
-
}: Pick<SwapCoins, 'coinInType' | 'coinOutType' | 'coinInAmount'>): Promise<bigint> {
|
|
258
|
-
const types = await getAffectedNftAndFtTypes(coinInType, coinOutType);
|
|
259
|
-
const { amountToSwap } = calculateSwapAmounts(types.length, BigInt(coinInAmount));
|
|
260
|
-
return amountToSwap;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
export async function calculateSwapInputAmountWithSlippage({
|
|
264
|
-
coinInType,
|
|
265
|
-
coinOutType,
|
|
266
|
-
coinOutAmount,
|
|
267
|
-
slippage = 0,
|
|
268
|
-
}: {
|
|
269
|
-
coinInType: string;
|
|
270
|
-
coinOutType: string;
|
|
271
|
-
coinOutAmount: string | number;
|
|
272
|
-
slippage?: number;
|
|
273
|
-
}): Promise<BigNumber> {
|
|
274
|
-
if (coinInType === coinOutType) {
|
|
275
|
-
return new BigNumber(coinOutAmount);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const provider = new CoinProvider('mainnet');
|
|
279
|
-
|
|
280
|
-
const coinQueryResult = await provider.getCoins({
|
|
281
|
-
limit: 2,
|
|
282
|
-
coinTypes: [coinInType, coinOutType],
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
const coinIn = coinQueryResult?.find(
|
|
286
|
-
(coin) => normalizeStructTag(coin.coinType) === normalizeStructTag(coinInType),
|
|
287
|
-
);
|
|
288
|
-
const coinOut = coinQueryResult?.find(
|
|
289
|
-
(coin) => normalizeStructTag(coin.coinType) === normalizeStructTag(coinOutType),
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
const exchangeRate = new BigNumber(coinOut.derivedPriceInUSD ?? 0)?.gt(0)
|
|
293
|
-
? new BigNumber(coinIn?.derivedPriceInUSD ?? 1)?.div(coinOut?.derivedPriceInUSD ?? 1)
|
|
294
|
-
: new BigNumber(0);
|
|
295
|
-
|
|
296
|
-
const amountSell = toDecimalValue(coinOutAmount.toString(), coinOut?.decimals).div(exchangeRate);
|
|
297
|
-
|
|
298
|
-
const slippageMultiplier = 1 + parseInt(slippage.toString() || '0', 10) / 1000000;
|
|
299
|
-
|
|
300
|
-
const amountWithSlippage = amountSell.times(slippageMultiplier);
|
|
301
|
-
|
|
302
|
-
const coinInAmount = toPureValue(amountWithSlippage, coinIn?.decimals);
|
|
303
|
-
|
|
304
|
-
return coinInAmount;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export async function buildSwapRoutes(
|
|
308
|
-
options: SwapRoutesArgs,
|
|
309
|
-
{ defiRouterUrl }: { defiRouterUrl?: string },
|
|
310
|
-
): Promise<GetRoutesResult<Coin, Coin>> {
|
|
311
|
-
const aggregatorQuoter = new AggregatorQuoter('mainnet');
|
|
312
|
-
const routes = await aggregatorQuoter.getRoutes({
|
|
313
|
-
tokenIn: options.coinInType,
|
|
314
|
-
tokenOut: options.coinOutType,
|
|
315
|
-
amountIn: options.amountToSwap.toString(),
|
|
316
|
-
commission: options.commission,
|
|
317
|
-
excludeSources: options.excludeSources,
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
if (defiRouterUrl && options.walletAddress) {
|
|
321
|
-
await makeDefiRouterRequest(`${defiRouterUrl}/start`, {
|
|
322
|
-
walletId: options.walletAddress,
|
|
323
|
-
coinTypeIn: options.coinInType,
|
|
324
|
-
coinTypeOut: options.coinOutType,
|
|
325
|
-
amountIn: options.amountToSwap.toString(),
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return routes;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function calculateSwapAmounts(
|
|
333
|
-
affectedTypesCount: number,
|
|
334
|
-
inAmount: bigint,
|
|
335
|
-
): { feeAndReward: bigint; amountToSwap: bigint } {
|
|
336
|
-
// 4% (or 8%) fee and reward budget or 1% fee if neither nft strategy, nor liquid nft involved
|
|
337
|
-
const feeAndReward =
|
|
338
|
-
affectedTypesCount > 0 ? (BigInt(affectedTypesCount) * inAmount) / 25n : inAmount / 100n;
|
|
339
|
-
const amountToSwap = inAmount - feeAndReward; // amount after deducting fee and reward budget
|
|
340
|
-
|
|
341
|
-
if (amountToSwap <= 0n) {
|
|
342
|
-
throw new Error('Amount to swap is too small');
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return { feeAndReward, amountToSwap };
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
async function getAffectedNftAndFtTypes(
|
|
349
|
-
coinInType: string,
|
|
350
|
-
coinOutType: string,
|
|
351
|
-
): Promise<Array<{ nftType: string; ftType: string; type: 'liquidNft' | 'nftStrategy' }>> {
|
|
352
|
-
const normalizedSuiType = normalizeStructTag(SUI_TYPE_ARG);
|
|
353
|
-
const coinTypes = [coinInType, coinOutType].filter((type) => type !== normalizedSuiType);
|
|
354
|
-
const types = await Promise.all(
|
|
355
|
-
coinTypes.map(async (type) => {
|
|
356
|
-
const chainState = await getCollectionChainStateByFtType(type);
|
|
357
|
-
return chainState
|
|
358
|
-
? {
|
|
359
|
-
nftType: chainState.nft_type as string,
|
|
360
|
-
ftType: type,
|
|
361
|
-
type: chainState.ft_type ? 'liquidNft' : 'nftStrategy',
|
|
362
|
-
}
|
|
363
|
-
: undefined;
|
|
364
|
-
}),
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
return types.filter(Boolean) as Array<{
|
|
368
|
-
nftType: string;
|
|
369
|
-
ftType: string;
|
|
370
|
-
type: 'liquidNft' | 'nftStrategy';
|
|
371
|
-
}>;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
async function makeDefiRouterRequest(
|
|
375
|
-
url: string,
|
|
376
|
-
body: Record<string, unknown>,
|
|
377
|
-
defaultResponse: unknown = undefined,
|
|
378
|
-
): Promise<unknown> {
|
|
379
|
-
try {
|
|
380
|
-
const response = await fetch(url, {
|
|
381
|
-
method: 'POST',
|
|
382
|
-
headers: { 'Content-Type': 'application/json' },
|
|
383
|
-
body: JSON.stringify(body),
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
if (!response.ok) {
|
|
387
|
-
console.warn(`DeFi router request failed with status ${response.status}`);
|
|
388
|
-
return defaultResponse;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (response.headers.get('Content-Type')?.includes('application/json')) {
|
|
392
|
-
return await response.json();
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return defaultResponse;
|
|
396
|
-
} catch (error) {
|
|
397
|
-
console.warn(`DeFi router request error: ${(error as Error).message}`);
|
|
398
|
-
return defaultResponse;
|
|
399
|
-
}
|
|
400
|
-
}
|