@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.
Files changed (32) hide show
  1. package/dist/index.d.mts +14 -0
  2. package/dist/index.d.ts +14 -0
  3. package/dist/index.js +534 -134
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +518 -125
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +6 -2
  8. package/src/SuiTradingClient.ts +15 -0
  9. package/src/bigNumberConfig.ts +10 -0
  10. package/src/constants.ts +17 -1
  11. package/src/graphql/queries/fetchNftCollectionChainState.ts +12 -0
  12. package/src/helpers/calculateRoyaltyFee.ts +12 -7
  13. package/src/helpers/isExpiredListing.ts +9 -0
  14. package/src/helpers/kiosk/preProcessSharedBulkBuyingData.ts +9 -8
  15. package/src/helpers/swap.ts +197 -0
  16. package/src/helpers/validateMinFloorPrice.ts +22 -0
  17. package/src/methods/buyListings/addBuyListingTxs.ts +111 -5
  18. package/src/methods/buyLocks/buyLocks.ts +2 -2
  19. package/src/methods/createLongLocks/createLongLocks.ts +2 -2
  20. package/src/methods/createShortLocks/createShortLocks.ts +3 -3
  21. package/src/methods/listNfts/addListTxs.ts +17 -37
  22. package/src/methods/listNfts/addRelistTxs.ts +26 -17
  23. package/src/methods/listNfts/listNfts.ts +27 -16
  24. package/src/methods/placeNftBids/addPlaceNftBidTxs.ts +1 -1
  25. package/src/methods/relistNft/relistNft.ts +19 -28
  26. package/src/methods/sponsorNftListing/addSponsorNftListingTx.ts +71 -0
  27. package/src/methods/sponsorNftListing/sponsorNftListing.ts +62 -0
  28. package/src/methods/unlistListings/addUnlistListingTxs.ts +68 -5
  29. package/src/methods/unlistListings/unlistListings.ts +2 -6
  30. package/src/tests/SuiWallet.ts +4 -1
  31. package/src/utils/printTxBlockTxs.ts +7 -1
  32. 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.39",
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
- "graphql-request": "^6.1.0"
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",
@@ -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;
@@ -0,0 +1,10 @@
1
+ import { BigNumber } from 'bignumber.js';
2
+
3
+ BigNumber.set({
4
+ POW_PRECISION: 64,
5
+ });
6
+
7
+ export const ZERO = new BigNumber(0);
8
+ export const ONE = new BigNumber(1);
9
+
10
+ export default BigNumber;
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: bigint,
4
- ): bigint {
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 0n;
13
+ return ZERO;
11
14
  }
12
15
 
13
- const royaltyFee = royaltyRule.amount_bp ? (BigInt(royaltyRule.amount_bp) * price) / 10000n : 0n;
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 && BigInt(royaltyRule.min_amount) > royaltyFee) {
16
- return BigInt(royaltyRule.min_amount);
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 tradeportKioskListings = listingsByNftType[nftType]?.filter(
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 (tradeportKioskListings.length === 0) {
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
- (policy: any) =>
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 tradeportKioskListings) {
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
- tradeportKioskListings[i].id
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 < tradeportKioskListings.length; i += batchSize) {
130
- const batch = tradeportKioskListings.slice(i, i + batchSize);
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 addTradePortBuyTx({
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 addTradeportKioskBuyTx = async ({
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
- addTradePortBuyTx({ ...txData, sharedObjects });
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 addTradeportKioskBuyTx({
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
- addTradePortBuyTx({ ...txData, sharedObjects });
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, BigInt(lock.maker_price));
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, BigInt(argNft.priceInMist));
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, BigInt(argNft.priceInMist));
45
+ const royalty = calculateRoyaltyFee(transferPolicy.rules, new BigNumber(argNft.priceInMist));
46
46
 
47
47
  const [deposit] = tx.splitCoins(tx.gas, [
48
- BigInt(argNft.priceInMist) + royalty + marketplaceFee,
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,