@tradeport/sui-trading-sdk 0.4.39 → 0.4.41
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 +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +534 -134
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +518 -125
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
- package/src/SuiTradingClient.ts +15 -0
- package/src/bigNumberConfig.ts +10 -0
- package/src/constants.ts +17 -1
- package/src/graphql/queries/fetchNftCollectionChainState.ts +12 -0
- package/src/helpers/calculateRoyaltyFee.ts +12 -7
- package/src/helpers/isExpiredListing.ts +9 -0
- package/src/helpers/kiosk/preProcessSharedBulkBuyingData.ts +9 -8
- package/src/helpers/swap.ts +197 -0
- package/src/helpers/validateMinFloorPrice.ts +22 -0
- package/src/methods/buyListings/addBuyListingTxs.ts +111 -5
- package/src/methods/buyLocks/buyLocks.ts +2 -2
- package/src/methods/createLongLocks/createLongLocks.ts +2 -2
- package/src/methods/createShortLocks/createShortLocks.ts +3 -3
- package/src/methods/listNfts/addListTxs.ts +17 -37
- package/src/methods/listNfts/addRelistTxs.ts +26 -17
- package/src/methods/listNfts/listNfts.ts +27 -16
- package/src/methods/placeNftBids/addPlaceNftBidTxs.ts +1 -1
- package/src/methods/relistNft/relistNft.ts +19 -28
- package/src/methods/sponsorNftListing/addSponsorNftListingTx.ts +71 -0
- package/src/methods/sponsorNftListing/sponsorNftListing.ts +62 -0
- package/src/methods/unlistListings/addUnlistListingTxs.ts +68 -5
- package/src/methods/unlistListings/unlistListings.ts +2 -6
- package/src/tests/SuiWallet.ts +4 -1
- package/src/utils/printTxBlockTxs.ts +7 -1
- package/src/utils/pureValues.ts +13 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradeport/sui-trading-sdk",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.41",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"@babel/preset-env": "^7.23.3",
|
|
11
11
|
"@changesets/cli": "^2.26.2",
|
|
12
12
|
"@types/jest": "^29.5.10",
|
|
13
|
+
"@types/ms": "^2.1.0",
|
|
13
14
|
"@types/node": "^20.10.0",
|
|
14
15
|
"@typescript-eslint/eslint-plugin": "^6.13.1",
|
|
15
16
|
"@typescript-eslint/parser": "^6.13.1",
|
|
@@ -27,12 +28,15 @@
|
|
|
27
28
|
"typescript": "^5.3.2"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
31
|
+
"@flowx-finance/sdk": "^1.13.5",
|
|
30
32
|
"@mysten/kiosk": "^0.7.13",
|
|
31
33
|
"@mysten/sui": "^1.17.0",
|
|
32
34
|
"@mysten/sui.js": "^0.47.0",
|
|
33
35
|
"@types/analytics-node": "^3.1.14",
|
|
34
36
|
"analytics-node": "^6.2.0",
|
|
35
|
-
"
|
|
37
|
+
"bignumber.js": "^9.3.1",
|
|
38
|
+
"graphql-request": "^6.1.0",
|
|
39
|
+
"ms": "^2.1.3"
|
|
36
40
|
},
|
|
37
41
|
"publishConfig": {
|
|
38
42
|
"access": "public",
|
package/src/SuiTradingClient.ts
CHANGED
|
@@ -66,6 +66,10 @@ import {
|
|
|
66
66
|
type WithdrawProfitsFromKiosks,
|
|
67
67
|
} from './methods/withdrawProfitsFromKiosks/withdrawProfitsFromKiosks';
|
|
68
68
|
import { addLeadingZerosAfter0x } from './utils/addLeadingZerosAfter0x';
|
|
69
|
+
import {
|
|
70
|
+
type SponsorNftListing,
|
|
71
|
+
sponsorNftListing,
|
|
72
|
+
} from './methods/sponsorNftListing/sponsorNftListing';
|
|
69
73
|
|
|
70
74
|
type ApiConfig = {
|
|
71
75
|
apiUser: string;
|
|
@@ -484,6 +488,17 @@ class SuiTradingClient {
|
|
|
484
488
|
public async updateMultiBid(args: UpdateMultiBid) {
|
|
485
489
|
return updateMultiBid(args);
|
|
486
490
|
}
|
|
491
|
+
|
|
492
|
+
public async sponsorNftListing(args: SponsorNftListing) {
|
|
493
|
+
const context: RequestContext = {
|
|
494
|
+
apiUser: this.apiUser,
|
|
495
|
+
apiKey: this.apiKey,
|
|
496
|
+
suiClient: this.suiClient,
|
|
497
|
+
kioskClient: this.kioskClient,
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
return sponsorNftListing(args, context);
|
|
501
|
+
}
|
|
487
502
|
}
|
|
488
503
|
|
|
489
504
|
export default SuiTradingClient;
|
package/src/constants.ts
CHANGED
|
@@ -3,10 +3,12 @@ export const MYSTEN_TRANFER_POLICY_RULES_PACKAGE_ID = '0x434b5bd8f6a7b05fede0ff4
|
|
|
3
3
|
export const TRADEPORT_BENEFICIARY_ADDRESS = '0xbca3b5c01c8d1a93aed3a036feff45145518292dd3c1db1d67cc99a699a7b517';
|
|
4
4
|
export const TRADEPORT_DEFAULT_FEE_DECIMAL_PERCENT = 0.03;
|
|
5
5
|
export const TRADEPORT_THIRD_PARTY_FEE_DECIMAL_PERCENT = 0.005;
|
|
6
|
+
export const TRADEPORT_LISTINGS_DEFAULT_COMMISSION_BPS = 300; // 3%
|
|
6
7
|
export const TRADEPORT_LISTING_STORE = '0x47cba0b6309a12ce39f9306e28b899ed4b3698bce4f4911fd0c58ff2329a2ff6';
|
|
7
8
|
export const TRADEPORT_BIDDING_STORE = '0x3d3b0c6e616fdc1a45b2b73d022bc085448e78bd729d28081c7a340abb33cba1';
|
|
8
9
|
export const TRADEPORT_MARKETPLACE_OBJECT = '0xc5f87120e388b532ee774f7bac23347679f632a1f2d59f4ed2bbf2ffe5a61b6f';
|
|
9
|
-
|
|
10
|
+
export const TRADEPORT_LISTINGS_PACKAGE = '0x318d9bbbbc65be1fe53f0de1af9fd93dcbcb8d57146aba9754cf7a3b55880f5c';
|
|
11
|
+
export const TRADEPORT_LISTINGS_STORE = '0x90626186ce022b50109628f44ef09e183e55961972d9b1b79de53558103161ef';
|
|
10
12
|
export const TRADEPORT_KIOSK_LISTING_STORE = '0xbff3161b047fb8b727883838c09b08afa9e0dd0f5488bde9e99e80643da9b9e0';
|
|
11
13
|
export const TRADEPORT_KIOSK_TRANSFERS_STORE = '0xe0f61b6915d3fd4ec8bbe4618514e2152a82841605bfa00cde22ace434153a1b';
|
|
12
14
|
export const TRADEPORT_KIOSK_TRANSFERS_ESCROW_KIOSK = '0xa677dab85a93bd8d6845b6cc7914f3bbb334fef7fbcbe0e997b5f75b5922d106';
|
|
@@ -21,6 +23,20 @@ export const TRADEPORT_MULTI_BID_PACKAGE = '0xd1dedf8379f1781469e95cb92eed7e155c
|
|
|
21
23
|
export const TRADEPORT_MULTI_BID_PACKAGE_ORIGINAL = '0x53134eb544c5a0b5085e99efaf7eab13b28ad123de35d61f941f8c8c40b72033'; // V1
|
|
22
24
|
export const TRADEPORT_MULTI_BID_STORE = '0x8aaed7e884343fb8b222c721d02eaac2c6ae2abbb4ddcdf16cb55cf8754ee860';
|
|
23
25
|
|
|
26
|
+
export const DexConstants = {
|
|
27
|
+
commission: '0x24f5f2258ef80c0a3243088199faeb95ad50516ca1517dbd93be398d759057bb',
|
|
28
|
+
commissionManager: '0xd19a03d4ec3d12b0ce407b54eb676cc0f8e1403621deda77d9677bfcb9d738c1',
|
|
29
|
+
poolsToExclude: [
|
|
30
|
+
'0x3addbbc82866c0bbd93e51b6e2d75c0a4faaf270cc0e281d8f4de5df48bebfa4',
|
|
31
|
+
'0x19d614f421046cae90f5a3a976816063478f648087a12adc74e90ebd54dfaf19',
|
|
32
|
+
'0x514c74e28a9720366abc4a65a5688f47c72e784dbf035844d3f59059bfaed056',
|
|
33
|
+
'0x0854de4e9d64716b757b2f6f22258467f59cc1b4bc0cc64c70086549faaddedf',
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const TRADEPORT_SPONSOR_USDC_FEE_AMOUNT_PER_PERIOD = 20000000; // 20 USDC
|
|
38
|
+
export const USDC_COIN_TYPE = '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';
|
|
39
|
+
|
|
24
40
|
export const COLLECTION_IDS_WITH_ZERO_COMMISSION = [
|
|
25
41
|
'',
|
|
26
42
|
];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { gql } from 'graphql-request';
|
|
2
|
+
|
|
3
|
+
export const fetchNftCollectionChainState = gql`
|
|
4
|
+
query fetchNftCollectionChainState($nftTokenId: String!) {
|
|
5
|
+
nfts(where: { token_id: { _eq: $nftTokenId } }) {
|
|
6
|
+
collection {
|
|
7
|
+
id
|
|
8
|
+
chain_state
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
`;
|
|
@@ -1,19 +1,24 @@
|
|
|
1
|
+
import BigNumber from '../bigNumberConfig';
|
|
2
|
+
import { ZERO } from '../bigNumberConfig';
|
|
3
|
+
|
|
1
4
|
export function calculateRoyaltyFee(
|
|
2
5
|
transferPolicyRules: Array<{ type: string; amount_bp?: string; min_amount?: string }>,
|
|
3
|
-
price:
|
|
4
|
-
):
|
|
6
|
+
price: BigNumber,
|
|
7
|
+
): BigNumber {
|
|
5
8
|
const royaltyRule = transferPolicyRules?.filter(
|
|
6
|
-
(rule: { type: string }) => rule.type === 'royalty_rule',
|
|
9
|
+
(rule: { type: string }) => rule.type === 'royalty_rule' || rule.type === 'kiosk_royalty_rule',
|
|
7
10
|
)?.[0];
|
|
8
11
|
|
|
9
12
|
if (!royaltyRule || (!royaltyRule.amount_bp && !royaltyRule.min_amount)) {
|
|
10
|
-
return
|
|
13
|
+
return ZERO;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
const royaltyFee = royaltyRule.amount_bp
|
|
16
|
+
const royaltyFee = royaltyRule.amount_bp
|
|
17
|
+
? new BigNumber(royaltyRule.amount_bp).times(price).div(10000)
|
|
18
|
+
: ZERO;
|
|
14
19
|
|
|
15
|
-
if (royaltyRule.min_amount &&
|
|
16
|
-
return
|
|
20
|
+
if (royaltyRule.min_amount && new BigNumber(royaltyRule.min_amount).isGreaterThan(royaltyFee)) {
|
|
21
|
+
return new BigNumber(royaltyRule.min_amount);
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
return royaltyFee;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { normalizeSuiAddress } from '@mysten/sui/utils';
|
|
2
|
+
|
|
3
|
+
export const isExpiredListing = (
|
|
4
|
+
listing: { nonce?: string; seller?: string },
|
|
5
|
+
walletAddress: string,
|
|
6
|
+
) =>
|
|
7
|
+
listing.nonce &&
|
|
8
|
+
listing.seller &&
|
|
9
|
+
normalizeSuiAddress(listing.seller) === normalizeSuiAddress(walletAddress);
|
|
@@ -63,16 +63,18 @@ export const preProcessSharedBulkBuyingData = async ({
|
|
|
63
63
|
continue;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
const
|
|
66
|
+
const tradeportLegacyKioskListings = listingsByNftType[nftType]?.filter(
|
|
67
67
|
(listing: any) =>
|
|
68
68
|
listing.market_name === 'tradeport' &&
|
|
69
|
+
!listing.nonce?.startsWith('0::') &&
|
|
70
|
+
!listing.nonce?.startsWith('1::') &&
|
|
69
71
|
listing.price &&
|
|
70
72
|
listing.listed &&
|
|
71
73
|
!(listing?.nonce && isOriginByteCollection(transferPolicies)) &&
|
|
72
74
|
listing.nft?.chain_state?.kiosk_id,
|
|
73
75
|
);
|
|
74
76
|
|
|
75
|
-
if (
|
|
77
|
+
if (tradeportLegacyKioskListings.length === 0) {
|
|
76
78
|
continue;
|
|
77
79
|
}
|
|
78
80
|
|
|
@@ -87,14 +89,13 @@ export const preProcessSharedBulkBuyingData = async ({
|
|
|
87
89
|
const transferPolicyToResolve = transferPoliciesToResolve.at(0);
|
|
88
90
|
|
|
89
91
|
const royaltyRuleToResolve = transferPolicyToResolve.rules.find(
|
|
90
|
-
(
|
|
91
|
-
policy.moduleName === 'royalty_rule' || policy.moduleName === 'kiosk_royalty_rule',
|
|
92
|
+
(rule: any) => rule.moduleName === 'royalty_rule' || rule.moduleName === 'kiosk_royalty_rule',
|
|
92
93
|
);
|
|
93
94
|
|
|
94
95
|
if (royaltyRuleToResolve?.rulePackageId && royaltyRuleToResolve?.moduleName) {
|
|
95
96
|
// for each tp kiosk listing, create fee_amount txs and dev inspect the tx block to get the royalty fee amounts and store them in shared data
|
|
96
97
|
const feeTx = new Transaction();
|
|
97
|
-
for (const listing of
|
|
98
|
+
for (const listing of tradeportLegacyKioskListings) {
|
|
98
99
|
feeTx.moveCall({
|
|
99
100
|
target: `${royaltyRuleToResolve.rulePackageId}::${royaltyRuleToResolve.moduleName}::fee_amount`,
|
|
100
101
|
arguments: [feeTx.object(transferPolicyToResolve?.id), feeTx.pure.u64(listing.price)],
|
|
@@ -118,7 +119,7 @@ export const preProcessSharedBulkBuyingData = async ({
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
sharedBulkBuyingDataByNftType[nftType].royaltyFeeAmountsByListingId[
|
|
121
|
-
|
|
122
|
+
tradeportLegacyKioskListings[i].id
|
|
122
123
|
] = feeAmount;
|
|
123
124
|
}
|
|
124
125
|
}
|
|
@@ -126,8 +127,8 @@ export const preProcessSharedBulkBuyingData = async ({
|
|
|
126
127
|
// for each tp kiosk listing, pre-fetch commission fee amounts in batches and store them in shared data
|
|
127
128
|
const batchSize = 10;
|
|
128
129
|
const commissionResults: Array<{ listingId: string; commission: number }> = [];
|
|
129
|
-
for (let i = 0; i <
|
|
130
|
-
const batch =
|
|
130
|
+
for (let i = 0; i < tradeportLegacyKioskListings.length; i += batchSize) {
|
|
131
|
+
const batch = tradeportLegacyKioskListings.slice(i, i + batchSize);
|
|
131
132
|
const batchPromises = batch.map(async (listing: any) => {
|
|
132
133
|
const response = await suiClient.getDynamicFieldObject({
|
|
133
134
|
parentId: TRADEPORT_KIOSK_LISTING_STORE,
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { AggregatorQuoter, CoinProvider, Protocol, TradeBuilder } from '@flowx-finance/sdk';
|
|
2
|
+
import { type SuiClient } from '@mysten/sui/client';
|
|
3
|
+
import {
|
|
4
|
+
coinWithBalance,
|
|
5
|
+
Transaction,
|
|
6
|
+
type TransactionObjectArgument,
|
|
7
|
+
} from '@mysten/sui/transactions';
|
|
8
|
+
import { normalizeStructTag, normalizeSuiAddress } from '@mysten/sui/utils';
|
|
9
|
+
import ms, { type StringValue } from 'ms';
|
|
10
|
+
import { bcs } from '@mysten/sui/bcs';
|
|
11
|
+
import BigNumber from '../bigNumberConfig';
|
|
12
|
+
import { DexConstants } from '../constants';
|
|
13
|
+
import { ZERO } from '../bigNumberConfig';
|
|
14
|
+
import { toDecimalValue, toPureValue } from '../utils/pureValues';
|
|
15
|
+
import { destroyZeroCoin } from './destroyZeroCoin';
|
|
16
|
+
|
|
17
|
+
const aggregatorQuoter = new AggregatorQuoter('mainnet');
|
|
18
|
+
|
|
19
|
+
const suiCoinType = '0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI';
|
|
20
|
+
|
|
21
|
+
export interface SwapOptions {
|
|
22
|
+
coinInAmount: string;
|
|
23
|
+
coinInType?: string;
|
|
24
|
+
coinOutType?: string;
|
|
25
|
+
coinOutAmount?: string;
|
|
26
|
+
slippage?: number;
|
|
27
|
+
ttl?: StringValue;
|
|
28
|
+
excludeSources?: Protocol[];
|
|
29
|
+
amountToSplitFromSuiBalance?: string;
|
|
30
|
+
rewardNftType?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function createSwapTransaction({
|
|
34
|
+
suiClient,
|
|
35
|
+
walletAddress,
|
|
36
|
+
options,
|
|
37
|
+
}: {
|
|
38
|
+
suiClient: SuiClient;
|
|
39
|
+
walletAddress: string;
|
|
40
|
+
options: {
|
|
41
|
+
transaction: Transaction;
|
|
42
|
+
coinInType: string;
|
|
43
|
+
coinInAmount: BigNumber;
|
|
44
|
+
coinOutType?: string;
|
|
45
|
+
slippage?: number;
|
|
46
|
+
ttl?: StringValue;
|
|
47
|
+
excludeSources?: Protocol[];
|
|
48
|
+
amountToSplitFromSuiBalance?: bigint;
|
|
49
|
+
rewardNftType?: string;
|
|
50
|
+
};
|
|
51
|
+
}): Promise<{
|
|
52
|
+
transaction: Transaction;
|
|
53
|
+
coinOut: TransactionObjectArgument;
|
|
54
|
+
}> {
|
|
55
|
+
const tx = options?.transaction ?? new Transaction();
|
|
56
|
+
tx.setSender(normalizeSuiAddress(walletAddress));
|
|
57
|
+
|
|
58
|
+
const {
|
|
59
|
+
coinInType,
|
|
60
|
+
coinInAmount = ZERO,
|
|
61
|
+
coinOutType,
|
|
62
|
+
slippage = 1,
|
|
63
|
+
ttl = '15m',
|
|
64
|
+
excludeSources = [Protocol.STEAMM, ...DexConstants.poolsToExclude],
|
|
65
|
+
rewardNftType,
|
|
66
|
+
} = options ?? {};
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
normalizeStructTag(coinOutType) === suiCoinType &&
|
|
70
|
+
normalizeStructTag(coinInType) === suiCoinType
|
|
71
|
+
) {
|
|
72
|
+
const [coin] = tx.splitCoins(tx.gas, [coinInAmount.toFixed()]);
|
|
73
|
+
return {
|
|
74
|
+
transaction: tx,
|
|
75
|
+
coinOut: coin,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const amountInSwap = coinInAmount.minus(coinInAmount.div(100).toFixed(0));
|
|
80
|
+
const commissionAmount = coinInAmount.div(100).toFixed(0);
|
|
81
|
+
|
|
82
|
+
const { routes } = await aggregatorQuoter.getRoutes({
|
|
83
|
+
tokenIn: coinInType,
|
|
84
|
+
tokenOut: coinOutType ?? suiCoinType,
|
|
85
|
+
amountIn: amountInSwap.toFixed(),
|
|
86
|
+
excludeSources: excludeSources as Protocol[],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const tradeBuilder = new TradeBuilder('mainnet', routes);
|
|
90
|
+
|
|
91
|
+
const trade = tradeBuilder
|
|
92
|
+
.sender(normalizeSuiAddress(walletAddress))
|
|
93
|
+
.slippage(slippage)
|
|
94
|
+
.deadline(Date.now() + ms(ttl))
|
|
95
|
+
.build();
|
|
96
|
+
|
|
97
|
+
let coinIn: TransactionObjectArgument;
|
|
98
|
+
|
|
99
|
+
if (normalizeStructTag(coinInType) === suiCoinType) {
|
|
100
|
+
[coinIn] = tx.splitCoins(tx.gas, [
|
|
101
|
+
tx.pure.u64((BigInt(amountInSwap.toFixed()) + BigInt(commissionAmount)).toString()),
|
|
102
|
+
]);
|
|
103
|
+
} else {
|
|
104
|
+
coinIn = coinWithBalance({
|
|
105
|
+
balance: BigInt(amountInSwap.toFixed()) + BigInt(commissionAmount),
|
|
106
|
+
type: coinInType,
|
|
107
|
+
})(tx);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const [commissionCoin] = tx.splitCoins(coinIn, [tx.pure.u64(commissionAmount.toString())]);
|
|
111
|
+
|
|
112
|
+
tx.moveCall({
|
|
113
|
+
target: `${DexConstants.commission}::commission::pay`,
|
|
114
|
+
arguments: [
|
|
115
|
+
tx.object(DexConstants.commissionManager),
|
|
116
|
+
tx.pure.string('swap'),
|
|
117
|
+
tx.object(commissionCoin),
|
|
118
|
+
tx.pure(bcs.option(bcs.String).serialize(rewardNftType).toBytes()),
|
|
119
|
+
],
|
|
120
|
+
typeArguments: [coinInType],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const coinOut = await trade.swap({ client: suiClient, tx, coinIn });
|
|
124
|
+
|
|
125
|
+
if (!coinOut) {
|
|
126
|
+
throw new Error('Swap did not return a valid coin out.');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (coinOut && options?.amountToSplitFromSuiBalance > 0n) {
|
|
130
|
+
mergeSwapCoinWithSuiBalance(tx, coinOut, options?.amountToSplitFromSuiBalance);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { transaction: tx, coinOut };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function mergeSwapCoinWithSuiBalance(
|
|
137
|
+
transaction: Transaction,
|
|
138
|
+
coinToMergeInto: TransactionObjectArgument | undefined,
|
|
139
|
+
amountToSplitFromSuiBalance: bigint | undefined,
|
|
140
|
+
): void {
|
|
141
|
+
if (coinToMergeInto && amountToSplitFromSuiBalance > 0n) {
|
|
142
|
+
const [coinFromSuiBalance] = transaction.splitCoins(transaction.gas, [
|
|
143
|
+
transaction.pure.u64(amountToSplitFromSuiBalance),
|
|
144
|
+
]);
|
|
145
|
+
transaction.mergeCoins(coinToMergeInto, [coinFromSuiBalance]);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function transferBackExtraSwappedCoin(
|
|
150
|
+
transaction: Transaction,
|
|
151
|
+
swapResultCoin: TransactionObjectArgument | undefined,
|
|
152
|
+
walletAddress: string,
|
|
153
|
+
): void {
|
|
154
|
+
if (swapResultCoin) {
|
|
155
|
+
transaction.transferObjects([swapResultCoin], walletAddress);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function calculateSwapInputAmountWithSlippage({
|
|
160
|
+
coinInType,
|
|
161
|
+
coinOutType,
|
|
162
|
+
coinOutAmount,
|
|
163
|
+
slippage = 0,
|
|
164
|
+
}: {
|
|
165
|
+
coinInType: string;
|
|
166
|
+
coinOutType: string;
|
|
167
|
+
coinOutAmount: string | number;
|
|
168
|
+
slippage?: number;
|
|
169
|
+
}): Promise<BigNumber> {
|
|
170
|
+
if (coinInType === coinOutType) {
|
|
171
|
+
return new BigNumber(coinOutAmount);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const provider = new CoinProvider('mainnet');
|
|
175
|
+
|
|
176
|
+
const coinQueryResult = await provider.getCoins({
|
|
177
|
+
limit: 2,
|
|
178
|
+
coinTypes: [coinInType, coinOutType],
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const coinIn = coinQueryResult[0];
|
|
182
|
+
const coinOut = coinQueryResult[1];
|
|
183
|
+
|
|
184
|
+
const exchangeRate = new BigNumber(coinOut.derivedPriceInUSD ?? 0)?.gt(0)
|
|
185
|
+
? new BigNumber(coinIn?.derivedPriceInUSD ?? 1)?.div(coinOut?.derivedPriceInUSD ?? 1)
|
|
186
|
+
: new BigNumber(0);
|
|
187
|
+
|
|
188
|
+
const amountSell = toDecimalValue(coinOutAmount.toString(), coinOut?.decimals).div(exchangeRate);
|
|
189
|
+
|
|
190
|
+
const slippageMultiplier = 1 + parseInt(slippage.toString() || '0', 10) / 1000000;
|
|
191
|
+
|
|
192
|
+
const amountWithSlippage = amountSell.times(slippageMultiplier);
|
|
193
|
+
|
|
194
|
+
const coinInAmount = toPureValue(amountWithSlippage, coinIn?.decimals);
|
|
195
|
+
|
|
196
|
+
return coinInAmount;
|
|
197
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getNativeKioskTransferPolicies } from './kiosk/getNativeKioskTransferPolicies';
|
|
2
|
+
|
|
3
|
+
interface ValidateMinFloorPriceArgs {
|
|
4
|
+
transferPolicies: any;
|
|
5
|
+
listPrice: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function validateMinFloorPrice({ transferPolicies, listPrice }: ValidateMinFloorPriceArgs) {
|
|
9
|
+
const transferPolicy = getNativeKioskTransferPolicies(transferPolicies)?.at(0);
|
|
10
|
+
const minFloorPrice = transferPolicy?.rules?.filter(
|
|
11
|
+
(rule: any) => rule?.type === 'floor_price_rule',
|
|
12
|
+
)?.[0]?.floor_price;
|
|
13
|
+
|
|
14
|
+
if (minFloorPrice) {
|
|
15
|
+
if (listPrice < minFloorPrice) {
|
|
16
|
+
const formattedMinFloorPrice = Number(minFloorPrice) / 1000000000;
|
|
17
|
+
throw new Error(
|
|
18
|
+
`NFT Transfer Policy has a miminum floor price rule. Item cannot be listed for less than ${formattedMinFloorPrice} SUI`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -14,8 +14,11 @@ import {
|
|
|
14
14
|
SOUFFL3_MARKETPLACE_OBJECT,
|
|
15
15
|
SOUFFL3_VERSION_OBJECT,
|
|
16
16
|
TOCEN_MARKETPLACE_OBJECT,
|
|
17
|
+
TRADEPORT_LISTINGS_DEFAULT_COMMISSION_BPS,
|
|
17
18
|
TRADEPORT_KIOSK_LISTING_STORE,
|
|
18
19
|
TRADEPORT_LISTING_STORE,
|
|
20
|
+
TRADEPORT_LISTINGS_PACKAGE,
|
|
21
|
+
TRADEPORT_LISTINGS_STORE,
|
|
19
22
|
} from '../../constants';
|
|
20
23
|
import { getCommissionByNftContractId } from '../../graphql/queries/getCommissionByListingId';
|
|
21
24
|
import { destroyZeroCoin } from '../../helpers/destroyZeroCoin';
|
|
@@ -31,8 +34,10 @@ import { isOriginByteCollection } from '../../helpers/originByte/isOriginByteCol
|
|
|
31
34
|
import { shareOriginByteKiosk } from '../../helpers/originByte/shareOriginByteKiosk';
|
|
32
35
|
import { addLeadingZerosAfter0x } from '../../utils/addLeadingZerosAfter0x';
|
|
33
36
|
import { type BuyTx, type TocenBuyTx } from './buyListings';
|
|
37
|
+
import BigNumber from '../../bigNumberConfig';
|
|
38
|
+
import { calculateRoyaltyFee } from '../../helpers/calculateRoyaltyFee';
|
|
34
39
|
|
|
35
|
-
export function
|
|
40
|
+
export function addLegacyTradePortBuyTx({
|
|
36
41
|
tx,
|
|
37
42
|
nftType,
|
|
38
43
|
listingNonce,
|
|
@@ -73,7 +78,7 @@ export function addTradePortBuyTx({
|
|
|
73
78
|
destroyZeroCoin({ tx, coin });
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
export const
|
|
81
|
+
export const addLegacyTradeportKioskBuyTx = async ({
|
|
77
82
|
tx,
|
|
78
83
|
transferPolicies,
|
|
79
84
|
kioskTx,
|
|
@@ -151,6 +156,78 @@ export const addTradeportKioskBuyTx = async ({
|
|
|
151
156
|
destroyZeroCoin({ tx, coin });
|
|
152
157
|
};
|
|
153
158
|
|
|
159
|
+
export async function addTradePortBuyTx({ tx, nftType, nftTokenId, price, coinToSplit }: BuyTx) {
|
|
160
|
+
const marketFee = new BigNumber(price)
|
|
161
|
+
.times(TRADEPORT_LISTINGS_DEFAULT_COMMISSION_BPS)
|
|
162
|
+
.div(10_000)
|
|
163
|
+
.integerValue(BigNumber.ROUND_DOWN)
|
|
164
|
+
.toNumber();
|
|
165
|
+
const totalAmount = new BigNumber(price).plus(marketFee).toNumber();
|
|
166
|
+
|
|
167
|
+
const [coin] = tx.splitCoins(coinToSplit ? coinToSplit : tx.gas, [tx.pure.u64(totalAmount)]);
|
|
168
|
+
|
|
169
|
+
if (!coin) throw new Error('Coin could not be split');
|
|
170
|
+
|
|
171
|
+
tx.moveCall({
|
|
172
|
+
target: `${TRADEPORT_LISTINGS_PACKAGE}::tradeport_listings::buy_listing_without_transfer_policy`,
|
|
173
|
+
arguments: [tx.object(TRADEPORT_LISTINGS_STORE), tx.pure.id(nftTokenId), tx.object(coin)],
|
|
174
|
+
typeArguments: [nftType],
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
destroyZeroCoin({ tx, coin });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function addKioskTradePortBuyTx({
|
|
181
|
+
tx,
|
|
182
|
+
transferPolicies,
|
|
183
|
+
kioskTx,
|
|
184
|
+
nftType,
|
|
185
|
+
nftTokenId,
|
|
186
|
+
price,
|
|
187
|
+
sellerKiosk,
|
|
188
|
+
coinToSplit,
|
|
189
|
+
}: BuyTx) {
|
|
190
|
+
const transferPolicyId = getNativeKioskTransferPolicies(transferPolicies)?.at(0)?.id;
|
|
191
|
+
|
|
192
|
+
const marketFee = new BigNumber(price)
|
|
193
|
+
.times(TRADEPORT_LISTINGS_DEFAULT_COMMISSION_BPS)
|
|
194
|
+
.div(10000)
|
|
195
|
+
.integerValue(BigNumber.ROUND_DOWN);
|
|
196
|
+
|
|
197
|
+
const royaltyFee = calculateRoyaltyFee(
|
|
198
|
+
getNativeKioskTransferPolicies(transferPolicies)?.at(0)?.rules,
|
|
199
|
+
new BigNumber(price),
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const totalAmount = new BigNumber(price).plus(marketFee).plus(royaltyFee).toNumber();
|
|
203
|
+
|
|
204
|
+
const [coin] = tx.splitCoins(coinToSplit ? coinToSplit : tx.gas, [tx.pure.u64(totalAmount)]);
|
|
205
|
+
|
|
206
|
+
if (!coin) throw new Error('Coin could not be split');
|
|
207
|
+
|
|
208
|
+
const transferRequest = tx.moveCall({
|
|
209
|
+
target: `${TRADEPORT_LISTINGS_PACKAGE}::tradeport_listings::buy_listing_with_transfer_policy`,
|
|
210
|
+
arguments: [
|
|
211
|
+
tx.object(TRADEPORT_LISTINGS_STORE),
|
|
212
|
+
tx.object(sellerKiosk),
|
|
213
|
+
tx.object('value' in kioskTx.kiosk ? kioskTx.kiosk.value : kioskTx.kiosk),
|
|
214
|
+
tx.object('value' in kioskTx.kioskCap ? kioskTx.kioskCap.value : kioskTx.kioskCap),
|
|
215
|
+
tx.pure.id(nftTokenId),
|
|
216
|
+
tx.object(coin),
|
|
217
|
+
tx.object(transferPolicyId),
|
|
218
|
+
],
|
|
219
|
+
typeArguments: [nftType],
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
tx.moveCall({
|
|
223
|
+
target: '0x2::transfer_policy::confirm_request',
|
|
224
|
+
arguments: [tx.object(transferPolicyId), transferRequest],
|
|
225
|
+
typeArguments: [nftType],
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
destroyZeroCoin({ tx, coin });
|
|
229
|
+
}
|
|
230
|
+
|
|
154
231
|
export async function addOriginByteBuyTx({
|
|
155
232
|
tx,
|
|
156
233
|
sharedObjects,
|
|
@@ -494,6 +571,7 @@ export const addTocenBuyTx = ({ tx, nftTokenIds, nftType, price, coinToSplit }:
|
|
|
494
571
|
};
|
|
495
572
|
|
|
496
573
|
export async function addTradePortBuyTxHandler(txData: BuyTx) {
|
|
574
|
+
// If origin byte collection, add origin byte buy tx
|
|
497
575
|
if (txData?.listingNonce && isOriginByteCollection(txData?.transferPolicies)) {
|
|
498
576
|
const sharedObjects =
|
|
499
577
|
txData?.sharedBulkBuyingDataByNftType?.[txData?.nftType]?.sharedObjects ??
|
|
@@ -503,6 +581,31 @@ export async function addTradePortBuyTxHandler(txData: BuyTx) {
|
|
|
503
581
|
return;
|
|
504
582
|
}
|
|
505
583
|
|
|
584
|
+
// If non kiosk listing (nonce starts with "0::")
|
|
585
|
+
if (txData?.listingNonce?.startsWith('0::')) {
|
|
586
|
+
await addTradePortBuyTx(txData);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// If kiosk listing (nonce starts with "1::")
|
|
591
|
+
if (txData?.listingNonce?.startsWith('1::')) {
|
|
592
|
+
return kioskTxWrapper({
|
|
593
|
+
tx: txData?.tx,
|
|
594
|
+
kioskClient: txData?.kioskClient,
|
|
595
|
+
kioskOwner: txData?.buyer,
|
|
596
|
+
kiosk: txData?.sellerKiosk,
|
|
597
|
+
shouldConvertToPersonalKiosk: true,
|
|
598
|
+
sharedKioskState: txData?.sharedKioskState,
|
|
599
|
+
async runCommands(kioskTx) {
|
|
600
|
+
await addKioskTradePortBuyTx({
|
|
601
|
+
...txData,
|
|
602
|
+
kioskTx,
|
|
603
|
+
});
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Legacy contract logic below
|
|
506
609
|
if (txData.listingNonce?.startsWith('0x')) {
|
|
507
610
|
const response = await txData?.suiClient.getObject({
|
|
508
611
|
id: txData.listingNonce,
|
|
@@ -516,11 +619,12 @@ export async function addTradePortBuyTxHandler(txData: BuyTx) {
|
|
|
516
619
|
const sharedObjects =
|
|
517
620
|
txData?.sharedBulkBuyingDataByNftType?.[txData?.nftType]?.sharedObjects ??
|
|
518
621
|
(await getSharedObjects(txData?.nftType));
|
|
519
|
-
|
|
622
|
+
addLegacyTradePortBuyTx({ ...txData, sharedObjects });
|
|
520
623
|
return;
|
|
521
624
|
}
|
|
522
625
|
}
|
|
523
626
|
|
|
627
|
+
// If legacy kiosk listing
|
|
524
628
|
if (txData?.sellerKiosk) {
|
|
525
629
|
return kioskTxWrapper({
|
|
526
630
|
tx: txData?.tx,
|
|
@@ -530,7 +634,7 @@ export async function addTradePortBuyTxHandler(txData: BuyTx) {
|
|
|
530
634
|
shouldConvertToPersonalKiosk: true,
|
|
531
635
|
sharedKioskState: txData?.sharedKioskState,
|
|
532
636
|
async runCommands(kioskTx) {
|
|
533
|
-
await
|
|
637
|
+
await addLegacyTradeportKioskBuyTx({
|
|
534
638
|
...txData,
|
|
535
639
|
kioskTx,
|
|
536
640
|
});
|
|
@@ -541,7 +645,9 @@ export async function addTradePortBuyTxHandler(txData: BuyTx) {
|
|
|
541
645
|
const sharedObjects =
|
|
542
646
|
txData?.sharedBulkBuyingDataByNftType?.[txData?.nftType]?.sharedObjects ??
|
|
543
647
|
(await getSharedObjects(txData?.nftType));
|
|
544
|
-
|
|
648
|
+
|
|
649
|
+
// If legacy non kiosk listing
|
|
650
|
+
addLegacyTradePortBuyTx({ ...txData, sharedObjects });
|
|
545
651
|
}
|
|
546
652
|
|
|
547
653
|
export async function addHyperspaceBuyTxHandler(txData: BuyTx) {
|
|
@@ -41,7 +41,7 @@ export async function buyLocks(
|
|
|
41
41
|
}),
|
|
42
42
|
);
|
|
43
43
|
|
|
44
|
-
const royalty = calculateRoyaltyFee(transferPolicy.rules,
|
|
44
|
+
const royalty = calculateRoyaltyFee(transferPolicy.rules, new BigNumber(lock.maker_price));
|
|
45
45
|
|
|
46
46
|
const premiumAmount = calculatePremium(lock.maker_price);
|
|
47
47
|
|
|
@@ -56,7 +56,7 @@ export async function buyLocks(
|
|
|
56
56
|
tx.pure.id(lock.lock_id),
|
|
57
57
|
tx.pure.u64(lock.maker_price),
|
|
58
58
|
tx.pure.u64(marketplaceFee),
|
|
59
|
-
tx.pure.u64(royalty),
|
|
59
|
+
tx.pure.u64(royalty.toNumber()),
|
|
60
60
|
tx.pure.u64(lock.expire_in),
|
|
61
61
|
premium,
|
|
62
62
|
],
|
|
@@ -63,7 +63,7 @@ export async function createLongLocks(
|
|
|
63
63
|
throw new Error(`Missing transfer policy for ${nftType}`);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
const royalty = calculateRoyaltyFee(transferPolicy.rules,
|
|
66
|
+
const royalty = calculateRoyaltyFee(transferPolicy.rules, new BigNumber(argNft.priceInMist));
|
|
67
67
|
|
|
68
68
|
await kioskTxWrapper({
|
|
69
69
|
tx,
|
|
@@ -88,7 +88,7 @@ export async function createLongLocks(
|
|
|
88
88
|
}),
|
|
89
89
|
),
|
|
90
90
|
),
|
|
91
|
-
tx.pure.u64(royalty),
|
|
91
|
+
tx.pure.u64(royalty.toNumber()),
|
|
92
92
|
tx.pure.u64(premium),
|
|
93
93
|
tx.pure.u64(expireIn),
|
|
94
94
|
],
|
|
@@ -42,10 +42,10 @@ export async function createShortLocks(
|
|
|
42
42
|
}),
|
|
43
43
|
);
|
|
44
44
|
|
|
45
|
-
const royalty = calculateRoyaltyFee(transferPolicy.rules,
|
|
45
|
+
const royalty = calculateRoyaltyFee(transferPolicy.rules, new BigNumber(argNft.priceInMist));
|
|
46
46
|
|
|
47
47
|
const [deposit] = tx.splitCoins(tx.gas, [
|
|
48
|
-
|
|
48
|
+
new BigNumber(argNft.priceInMist).plus(royalty).plus(marketplaceFee).toNumber(),
|
|
49
49
|
]);
|
|
50
50
|
|
|
51
51
|
tx.moveCall({
|
|
@@ -55,7 +55,7 @@ export async function createShortLocks(
|
|
|
55
55
|
tx.object(TRADEPORT_PRICE_LOCK_STORE),
|
|
56
56
|
tx.pure.u64(BigInt(argNft.priceInMist)),
|
|
57
57
|
tx.pure.u64(marketplaceFee),
|
|
58
|
-
tx.pure.u64(royalty),
|
|
58
|
+
tx.pure.u64(royalty.toNumber()),
|
|
59
59
|
tx.pure.u64(premium),
|
|
60
60
|
tx.pure.u64(expireIn),
|
|
61
61
|
deposit,
|