@zoralabs/protocol-sdk 0.11.1 → 0.11.3-COMMENTS.0
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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +26 -0
- package/dist/index.cjs +155 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +155 -91
- package/dist/index.js.map +1 -1
- package/dist/mint/mint-queries.d.ts +2 -0
- package/dist/mint/mint-queries.d.ts.map +1 -1
- package/dist/mint/subgraph-mint-getter.d.ts +2 -2
- package/dist/mint/subgraph-mint-getter.d.ts.map +1 -1
- package/dist/mint/types.d.ts +18 -4
- package/dist/mint/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/fixtures/mint-query-results.ts +1 -1
- package/src/index.ts +20 -0
- package/src/mint/mint-client.test.ts +41 -13
- package/src/mint/mint-client.ts +6 -2
- package/src/mint/mint-queries.ts +63 -26
- package/src/mint/subgraph-mint-getter.ts +146 -90
- package/src/mint/types.ts +23 -5
- package/src/rewards/rewards-client.test.ts +2 -2
- package/src/secondary/secondary-client.test.ts +10 -3
|
@@ -6,15 +6,19 @@ import {
|
|
|
6
6
|
IOnchainMintGetter,
|
|
7
7
|
SaleType,
|
|
8
8
|
OnchainMintable,
|
|
9
|
-
OnchainSalesConfigAndTokenInfo,
|
|
10
9
|
OnchainSalesStrategies,
|
|
10
|
+
GetMintableReturn,
|
|
11
11
|
} from "./types";
|
|
12
12
|
import {
|
|
13
13
|
buildContractTokensQuery,
|
|
14
14
|
buildNftTokenSalesQuery,
|
|
15
15
|
buildPremintsOfContractQuery,
|
|
16
|
+
ERC20SaleStrategyResult,
|
|
17
|
+
FixedPriceSaleStrategyResult,
|
|
18
|
+
PresaleSalesStrategyResult,
|
|
16
19
|
SalesStrategyResult,
|
|
17
20
|
TokenQueryResult,
|
|
21
|
+
ZoraTimedMinterSaleStrategyResult,
|
|
18
22
|
} from "./subgraph-queries";
|
|
19
23
|
import { SubgraphGetter } from "src/apis/subgraph-getter";
|
|
20
24
|
|
|
@@ -24,90 +28,133 @@ export const getApiNetworkConfigForChain = (chainId: number): NetworkConfig => {
|
|
|
24
28
|
}
|
|
25
29
|
return networkConfigByChain[chainId]!;
|
|
26
30
|
};
|
|
31
|
+
type ParsedSalesConfig = {
|
|
32
|
+
salesStrategy: OnchainSalesStrategies;
|
|
33
|
+
saleActive: boolean;
|
|
34
|
+
saleEnd: bigint | undefined;
|
|
35
|
+
secondaryMarketActive?: boolean;
|
|
36
|
+
};
|
|
27
37
|
|
|
28
|
-
function
|
|
29
|
-
|
|
38
|
+
function parseFixedPriceSalesConfig(
|
|
39
|
+
fixedPrice: FixedPriceSaleStrategyResult["fixedPrice"],
|
|
30
40
|
contractMintFee: bigint,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
blockTime: bigint,
|
|
42
|
+
): ParsedSalesConfig {
|
|
43
|
+
const saleEnd = BigInt(fixedPrice.saleEnd);
|
|
44
|
+
return {
|
|
45
|
+
salesStrategy: {
|
|
34
46
|
saleType: "fixedPrice",
|
|
35
|
-
...
|
|
36
|
-
maxTokensPerAddress: BigInt(
|
|
37
|
-
|
|
38
|
-
),
|
|
39
|
-
pricePerToken: BigInt(targetStrategy.fixedPrice.pricePerToken),
|
|
47
|
+
...fixedPrice,
|
|
48
|
+
maxTokensPerAddress: BigInt(fixedPrice.maxTokensPerAddress),
|
|
49
|
+
pricePerToken: BigInt(fixedPrice.pricePerToken),
|
|
40
50
|
mintFeePerQuantity: contractMintFee,
|
|
41
|
-
}
|
|
51
|
+
},
|
|
52
|
+
saleEnd,
|
|
53
|
+
saleActive:
|
|
54
|
+
BigInt(fixedPrice.saleStart) <= blockTime && BigInt(saleEnd) > blockTime,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
42
57
|
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
function parseERC20SalesConfig(
|
|
59
|
+
erc20Minter: ERC20SaleStrategyResult["erc20Minter"],
|
|
60
|
+
blockTime: bigint,
|
|
61
|
+
): ParsedSalesConfig {
|
|
62
|
+
const saleEnd = BigInt(erc20Minter.saleEnd);
|
|
63
|
+
return {
|
|
64
|
+
salesStrategy: {
|
|
45
65
|
saleType: "erc20",
|
|
46
|
-
...
|
|
47
|
-
maxTokensPerAddress: BigInt(
|
|
48
|
-
|
|
49
|
-
),
|
|
50
|
-
pricePerToken: BigInt(targetStrategy.erc20Minter.pricePerToken),
|
|
66
|
+
...erc20Minter,
|
|
67
|
+
maxTokensPerAddress: BigInt(erc20Minter.maxTokensPerAddress),
|
|
68
|
+
pricePerToken: BigInt(erc20Minter.pricePerToken),
|
|
51
69
|
mintFeePerQuantity: 0n,
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
},
|
|
71
|
+
saleEnd,
|
|
72
|
+
saleActive:
|
|
73
|
+
BigInt(erc20Minter.saleStart) <= blockTime && saleEnd > blockTime,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function parsePresaleSalesConfig(
|
|
78
|
+
presale: PresaleSalesStrategyResult["presale"],
|
|
79
|
+
contractMintFee: bigint,
|
|
80
|
+
blockTime: bigint,
|
|
81
|
+
): ParsedSalesConfig {
|
|
82
|
+
const saleEnd = BigInt(presale.presaleEnd);
|
|
83
|
+
return {
|
|
84
|
+
salesStrategy: {
|
|
56
85
|
saleType: "allowlist",
|
|
57
|
-
address:
|
|
58
|
-
merkleRoot:
|
|
59
|
-
saleStart:
|
|
60
|
-
saleEnd:
|
|
86
|
+
address: presale.address,
|
|
87
|
+
merkleRoot: presale.merkleRoot,
|
|
88
|
+
saleStart: presale.presaleStart,
|
|
89
|
+
saleEnd: presale.presaleEnd,
|
|
61
90
|
mintFeePerQuantity: contractMintFee,
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
91
|
+
},
|
|
92
|
+
saleEnd,
|
|
93
|
+
saleActive:
|
|
94
|
+
BigInt(presale.presaleStart) <= blockTime && saleEnd > blockTime,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseZoraTimedSalesConfig(
|
|
99
|
+
zoraTimedMinter: ZoraTimedMinterSaleStrategyResult["zoraTimedMinter"],
|
|
100
|
+
blockTime: bigint,
|
|
101
|
+
): ParsedSalesConfig {
|
|
102
|
+
const saleEnd = BigInt(zoraTimedMinter.saleEnd);
|
|
103
|
+
const hasSaleEnd = saleEnd > 0n;
|
|
104
|
+
return {
|
|
105
|
+
salesStrategy: {
|
|
66
106
|
saleType: "timed",
|
|
67
|
-
address:
|
|
68
|
-
mintFee: BigInt(
|
|
69
|
-
saleStart:
|
|
70
|
-
saleEnd:
|
|
71
|
-
erc20Z:
|
|
72
|
-
pool:
|
|
73
|
-
secondaryActivated:
|
|
74
|
-
mintFeePerQuantity: BigInt(
|
|
75
|
-
marketCountdown:
|
|
76
|
-
? BigInt(
|
|
107
|
+
address: zoraTimedMinter.address,
|
|
108
|
+
mintFee: BigInt(zoraTimedMinter.mintFee),
|
|
109
|
+
saleStart: zoraTimedMinter.saleStart,
|
|
110
|
+
saleEnd: zoraTimedMinter.saleEnd,
|
|
111
|
+
erc20Z: zoraTimedMinter.erc20Z.id,
|
|
112
|
+
pool: zoraTimedMinter.erc20Z.pool,
|
|
113
|
+
secondaryActivated: zoraTimedMinter.secondaryActivated,
|
|
114
|
+
mintFeePerQuantity: BigInt(zoraTimedMinter.mintFee),
|
|
115
|
+
marketCountdown: zoraTimedMinter.marketCountdown
|
|
116
|
+
? BigInt(zoraTimedMinter.marketCountdown)
|
|
77
117
|
: undefined,
|
|
78
|
-
minimumMarketEth:
|
|
79
|
-
? BigInt(
|
|
118
|
+
minimumMarketEth: zoraTimedMinter.minimumMarketEth
|
|
119
|
+
? BigInt(zoraTimedMinter.minimumMarketEth)
|
|
80
120
|
: undefined,
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (a.type === "FIXED_PRICE") return BigInt(a.fixedPrice.saleEnd);
|
|
89
|
-
if (a.type === "ERC_20_MINTER") return BigInt(a.erc20Minter.saleEnd);
|
|
90
|
-
if (a.type === "ZORA_TIMED") return BigInt(a.zoraTimedMinter.saleEnd);
|
|
91
|
-
return BigInt(a.presale.presaleEnd);
|
|
121
|
+
},
|
|
122
|
+
saleEnd: hasSaleEnd ? saleEnd : undefined,
|
|
123
|
+
secondaryMarketActive: zoraTimedMinter.secondaryActivated,
|
|
124
|
+
saleActive:
|
|
125
|
+
BigInt(zoraTimedMinter.saleStart) <= blockTime &&
|
|
126
|
+
(hasSaleEnd ? saleEnd > blockTime : true),
|
|
127
|
+
};
|
|
92
128
|
}
|
|
93
129
|
|
|
94
|
-
function
|
|
95
|
-
|
|
130
|
+
function parseSalesConfig(
|
|
131
|
+
targetStrategy: SalesStrategyResult,
|
|
132
|
+
contractMintFee: bigint,
|
|
96
133
|
blockTime: bigint,
|
|
97
|
-
):
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
134
|
+
): ParsedSalesConfig {
|
|
135
|
+
switch (targetStrategy.type) {
|
|
136
|
+
case "FIXED_PRICE":
|
|
137
|
+
return parseFixedPriceSalesConfig(
|
|
138
|
+
targetStrategy.fixedPrice,
|
|
139
|
+
contractMintFee,
|
|
140
|
+
blockTime,
|
|
141
|
+
);
|
|
142
|
+
case "ERC_20_MINTER":
|
|
143
|
+
return parseERC20SalesConfig(targetStrategy.erc20Minter, blockTime);
|
|
144
|
+
case "PRESALE":
|
|
145
|
+
return parsePresaleSalesConfig(
|
|
146
|
+
targetStrategy.presale,
|
|
147
|
+
contractMintFee,
|
|
148
|
+
blockTime,
|
|
149
|
+
);
|
|
150
|
+
case "ZORA_TIMED":
|
|
151
|
+
return parseZoraTimedSalesConfig(
|
|
152
|
+
targetStrategy.zoraTimedMinter,
|
|
153
|
+
blockTime,
|
|
154
|
+
);
|
|
155
|
+
default:
|
|
156
|
+
throw new Error("Unknown saleType");
|
|
109
157
|
}
|
|
110
|
-
return BigInt(strategy.presale.presaleEnd) > blockTime;
|
|
111
158
|
}
|
|
112
159
|
|
|
113
160
|
function getTargetStrategy({
|
|
@@ -115,39 +162,45 @@ function getTargetStrategy({
|
|
|
115
162
|
preferredSaleType,
|
|
116
163
|
token,
|
|
117
164
|
blockTime,
|
|
165
|
+
contractMintFee,
|
|
118
166
|
}: {
|
|
119
167
|
tokenId?: GenericTokenIdTypes;
|
|
120
168
|
preferredSaleType?: SaleType;
|
|
121
169
|
token: TokenQueryResult;
|
|
122
170
|
blockTime: bigint;
|
|
123
|
-
|
|
171
|
+
contractMintFee: bigint;
|
|
172
|
+
}): ParsedSalesConfig | undefined {
|
|
124
173
|
const allStrategies =
|
|
125
174
|
(typeof tokenId !== "undefined"
|
|
126
175
|
? token.salesStrategies
|
|
127
176
|
: token.contract.salesStrategies) || [];
|
|
128
177
|
|
|
129
|
-
const
|
|
130
|
-
|
|
178
|
+
const parsedStrategies = allStrategies.map((strategy) =>
|
|
179
|
+
parseSalesConfig(strategy, contractMintFee, blockTime),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const stillValidSalesStrategies = parsedStrategies.filter(
|
|
183
|
+
(strategy) => strategy.saleActive,
|
|
131
184
|
);
|
|
132
185
|
|
|
133
186
|
const saleStrategies = stillValidSalesStrategies.sort((a, b) =>
|
|
134
|
-
|
|
187
|
+
(a.saleEnd ?? 0n) > (b.saleEnd ?? 0n) ? 1 : -1,
|
|
135
188
|
);
|
|
136
189
|
|
|
137
|
-
let targetStrategy:
|
|
190
|
+
let targetStrategy: ParsedSalesConfig | undefined;
|
|
138
191
|
|
|
139
192
|
if (!preferredSaleType) {
|
|
140
193
|
return saleStrategies[0];
|
|
141
194
|
} else {
|
|
142
|
-
const mappedSaleType =
|
|
143
|
-
preferredSaleType === "erc20" ? "ERC_20_MINTER" : "FIXED_PRICE";
|
|
144
195
|
targetStrategy = saleStrategies.find(
|
|
145
|
-
(
|
|
196
|
+
({ salesStrategy }) => salesStrategy.saleType === preferredSaleType,
|
|
146
197
|
);
|
|
147
198
|
if (!targetStrategy) {
|
|
148
199
|
const targetStrategy = saleStrategies.find(
|
|
149
|
-
(
|
|
150
|
-
|
|
200
|
+
({ salesStrategy }) =>
|
|
201
|
+
salesStrategy.saleType === "timed" ||
|
|
202
|
+
salesStrategy.saleType === "fixedPrice" ||
|
|
203
|
+
salesStrategy.saleType === "erc20",
|
|
151
204
|
);
|
|
152
205
|
if (!targetStrategy) throw new Error("Cannot find valid sale strategy");
|
|
153
206
|
return targetStrategy;
|
|
@@ -249,7 +302,7 @@ export class SubgraphMintGetter
|
|
|
249
302
|
}
|
|
250
303
|
}
|
|
251
304
|
|
|
252
|
-
function
|
|
305
|
+
function getTargetStrategyAndMintFeeAndSaleActive({
|
|
253
306
|
token,
|
|
254
307
|
tokenId,
|
|
255
308
|
preferredSaleType,
|
|
@@ -267,13 +320,10 @@ function getTargetStrategyAndMintFee({
|
|
|
267
320
|
preferredSaleType: preferredSaleType,
|
|
268
321
|
token,
|
|
269
322
|
blockTime,
|
|
323
|
+
contractMintFee: defaultMintFee,
|
|
270
324
|
});
|
|
271
325
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const salesConfig = parseSalesConfig(targetStrategy, defaultMintFee);
|
|
275
|
-
|
|
276
|
-
return salesConfig;
|
|
326
|
+
return targetStrategy;
|
|
277
327
|
}
|
|
278
328
|
|
|
279
329
|
function parseTokenQueryResult({
|
|
@@ -288,8 +338,8 @@ function parseTokenQueryResult({
|
|
|
288
338
|
preferredSaleType?: SaleType;
|
|
289
339
|
defaultMintFee: bigint;
|
|
290
340
|
blockTime: bigint;
|
|
291
|
-
}):
|
|
292
|
-
const
|
|
341
|
+
}): GetMintableReturn {
|
|
342
|
+
const salesStrategyAndMintInfo = getTargetStrategyAndMintFeeAndSaleActive({
|
|
293
343
|
token,
|
|
294
344
|
tokenId,
|
|
295
345
|
preferredSaleType,
|
|
@@ -302,8 +352,14 @@ function parseTokenQueryResult({
|
|
|
302
352
|
});
|
|
303
353
|
|
|
304
354
|
return {
|
|
305
|
-
|
|
306
|
-
|
|
355
|
+
salesConfigAndTokenInfo: {
|
|
356
|
+
...tokenInfo,
|
|
357
|
+
salesConfig: salesStrategyAndMintInfo?.salesStrategy,
|
|
358
|
+
},
|
|
359
|
+
primaryMintActive: salesStrategyAndMintInfo?.saleActive ?? false,
|
|
360
|
+
primaryMintEnd: salesStrategyAndMintInfo?.saleEnd,
|
|
361
|
+
secondaryMarketActive:
|
|
362
|
+
salesStrategyAndMintInfo?.secondaryMarketActive ?? false,
|
|
307
363
|
};
|
|
308
364
|
}
|
|
309
365
|
|
|
@@ -311,7 +367,7 @@ function parseTokenInfo({
|
|
|
311
367
|
token,
|
|
312
368
|
}: {
|
|
313
369
|
token: TokenQueryResult;
|
|
314
|
-
}): OnchainMintable {
|
|
370
|
+
}): Omit<OnchainMintable, "primaryMintActive" | "primaryMintEnd"> {
|
|
315
371
|
return {
|
|
316
372
|
contract: {
|
|
317
373
|
address: token.contract.address,
|
package/src/mint/types.ts
CHANGED
|
@@ -226,17 +226,24 @@ export type SalesConfigAndTokenInfo =
|
|
|
226
226
|
| OnchainSalesConfigAndTokenInfo
|
|
227
227
|
| PremintMintable;
|
|
228
228
|
|
|
229
|
+
export type GetMintableReturn = {
|
|
230
|
+
salesConfigAndTokenInfo: OnchainSalesConfigAndTokenInfo;
|
|
231
|
+
secondaryMarketActive: boolean;
|
|
232
|
+
primaryMintActive: boolean;
|
|
233
|
+
primaryMintEnd?: bigint;
|
|
234
|
+
};
|
|
235
|
+
|
|
229
236
|
export interface IOnchainMintGetter {
|
|
230
237
|
getMintable(params: {
|
|
231
238
|
tokenAddress: Address;
|
|
232
239
|
tokenId?: GenericTokenIdTypes;
|
|
233
240
|
preferredSaleType?: SaleType;
|
|
234
241
|
blockTime: bigint;
|
|
235
|
-
}): Promise<
|
|
242
|
+
}): Promise<GetMintableReturn>;
|
|
236
243
|
|
|
237
244
|
getContractMintable(params: {
|
|
238
245
|
tokenAddress: Address;
|
|
239
|
-
}): Promise<
|
|
246
|
+
}): Promise<GetMintableReturn[]>;
|
|
240
247
|
|
|
241
248
|
getContractPremintTokenIds(params: {
|
|
242
249
|
tokenAddress: Address;
|
|
@@ -280,6 +287,17 @@ export type AsyncPrepareMint = (
|
|
|
280
287
|
export type MintableReturn = {
|
|
281
288
|
/** Token information */
|
|
282
289
|
token: SalesConfigAndTokenInfo;
|
|
283
|
-
/**
|
|
284
|
-
|
|
285
|
-
|
|
290
|
+
/** If the primary mint is active, the end time of the primary mint, if there is an end time */
|
|
291
|
+
primaryMintEnd?: bigint;
|
|
292
|
+
secondaryMarketActive: boolean;
|
|
293
|
+
} & (
|
|
294
|
+
| {
|
|
295
|
+
primaryMintActive: true;
|
|
296
|
+
/** Function that takes a quantity of items to mint and returns a prepared transaction and the costs to mint that quantity. If the primary mint is not active, it will be undefined. */
|
|
297
|
+
prepareMint: PrepareMint;
|
|
298
|
+
}
|
|
299
|
+
| {
|
|
300
|
+
primaryMintActive: false;
|
|
301
|
+
prepareMint?: undefined;
|
|
302
|
+
}
|
|
303
|
+
);
|
|
@@ -22,7 +22,7 @@ import { advanceToSaleAndAndLaunchMarket } from "src/secondary/secondary-client.
|
|
|
22
22
|
|
|
23
23
|
describe("rewardsClient", () => {
|
|
24
24
|
makeAnvilTest({
|
|
25
|
-
forkBlockNumber:
|
|
25
|
+
forkBlockNumber: 14653556,
|
|
26
26
|
forkUrl: forkUrls.zoraSepolia,
|
|
27
27
|
anvilChainId: zoraSepolia.id,
|
|
28
28
|
})(
|
|
@@ -117,7 +117,7 @@ describe("rewardsClient", () => {
|
|
|
117
117
|
30_000,
|
|
118
118
|
);
|
|
119
119
|
makeAnvilTest({
|
|
120
|
-
forkBlockNumber:
|
|
120
|
+
forkBlockNumber: 14653556,
|
|
121
121
|
forkUrl: forkUrls.zoraSepolia,
|
|
122
122
|
anvilChainId: zoraSepolia.id,
|
|
123
123
|
})(
|
|
@@ -80,7 +80,7 @@ export async function advanceToSaleAndAndLaunchMarket({
|
|
|
80
80
|
|
|
81
81
|
describe("secondary", () => {
|
|
82
82
|
makeAnvilTest({
|
|
83
|
-
forkBlockNumber:
|
|
83
|
+
forkBlockNumber: 14653556,
|
|
84
84
|
forkUrl: forkUrls.zoraSepolia,
|
|
85
85
|
anvilChainId: zoraSepolia.id,
|
|
86
86
|
})(
|
|
@@ -128,7 +128,7 @@ describe("secondary", () => {
|
|
|
128
128
|
);
|
|
129
129
|
|
|
130
130
|
makeAnvilTest({
|
|
131
|
-
forkBlockNumber:
|
|
131
|
+
forkBlockNumber: 14653556,
|
|
132
132
|
forkUrl: forkUrls.zoraSepolia,
|
|
133
133
|
anvilChainId: zoraSepolia.id,
|
|
134
134
|
})(
|
|
@@ -199,6 +199,13 @@ describe("secondary", () => {
|
|
|
199
199
|
account: collectorAccount,
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
+
const balanceBefore = await publicClient.readContract({
|
|
203
|
+
abi: zoraCreator1155ImplABI,
|
|
204
|
+
address: contractAddress,
|
|
205
|
+
functionName: "balanceOf",
|
|
206
|
+
args: [collectorAccount, newTokenId],
|
|
207
|
+
});
|
|
208
|
+
|
|
202
209
|
// now get the price ot buy on secondary
|
|
203
210
|
const quantityToBuy = 1000n;
|
|
204
211
|
|
|
@@ -236,7 +243,7 @@ describe("secondary", () => {
|
|
|
236
243
|
args: [collectorAccount, newTokenId],
|
|
237
244
|
});
|
|
238
245
|
|
|
239
|
-
expect(balance).toBe(quantityToBuy
|
|
246
|
+
expect(balance - balanceBefore).toBe(quantityToBuy);
|
|
240
247
|
|
|
241
248
|
// now sell 10_000n tokens
|
|
242
249
|
const quantityToSell = 10_000n;
|