@medialane/sdk 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,1154 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+ var starknet = require('starknet');
5
+
6
+ // src/config.ts
7
+
8
+ // src/constants.ts
9
+ var MARKETPLACE_CONTRACT_MAINNET = "0x059deafbbafbf7051c315cf75a94b03c5547892bc0c6dfa36d7ac7290d4cc33a";
10
+ var COLLECTION_CONTRACT_MAINNET = "0x05e73b7be06d82beeb390a0e0d655f2c9e7cf519658e04f05d9c690ccc41da03";
11
+ var SUPPORTED_TOKENS = [
12
+ {
13
+ // Circle-native USDC on Starknet (canonical)
14
+ symbol: "USDC",
15
+ address: "0x033068f6539f8e6e6b131e6b2b814e6c34a5224bc66947c47dab9dfee93b35fb",
16
+ decimals: 6
17
+ },
18
+ {
19
+ symbol: "USDT",
20
+ address: "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8",
21
+ decimals: 6
22
+ },
23
+ {
24
+ symbol: "ETH",
25
+ address: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
26
+ decimals: 18
27
+ },
28
+ {
29
+ symbol: "STRK",
30
+ address: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
31
+ decimals: 18
32
+ }
33
+ ];
34
+ var DEFAULT_CURRENCY = "USDC";
35
+ var SUPPORTED_NETWORKS = ["mainnet", "sepolia"];
36
+ var DEFAULT_RPC_URLS = {
37
+ mainnet: "https://starknet-mainnet.public.blastapi.io",
38
+ sepolia: "https://starknet-sepolia.public.blastapi.io"
39
+ };
40
+
41
+ // src/config.ts
42
+ var MedialaneConfigSchema = zod.z.object({
43
+ network: zod.z.enum(SUPPORTED_NETWORKS).default("mainnet"),
44
+ rpcUrl: zod.z.string().url().optional(),
45
+ backendUrl: zod.z.string().url().optional(),
46
+ /** API key for authenticated /v1/* backend endpoints */
47
+ apiKey: zod.z.string().optional(),
48
+ marketplaceContract: zod.z.string().optional(),
49
+ collectionContract: zod.z.string().optional()
50
+ });
51
+ function resolveConfig(raw) {
52
+ const parsed = MedialaneConfigSchema.parse(raw);
53
+ return {
54
+ network: parsed.network,
55
+ rpcUrl: parsed.rpcUrl ?? DEFAULT_RPC_URLS[parsed.network],
56
+ backendUrl: parsed.backendUrl,
57
+ apiKey: parsed.apiKey,
58
+ marketplaceContract: parsed.marketplaceContract ?? MARKETPLACE_CONTRACT_MAINNET,
59
+ collectionContract: parsed.collectionContract ?? COLLECTION_CONTRACT_MAINNET
60
+ };
61
+ }
62
+ function buildOrderTypedData(message, chainId) {
63
+ return {
64
+ domain: {
65
+ name: "Medialane",
66
+ version: "1",
67
+ chainId,
68
+ revision: starknet.TypedDataRevision.ACTIVE
69
+ },
70
+ primaryType: "OrderParameters",
71
+ types: {
72
+ StarknetDomain: [
73
+ { name: "name", type: "shortstring" },
74
+ { name: "version", type: "shortstring" },
75
+ { name: "chainId", type: "shortstring" },
76
+ { name: "revision", type: "shortstring" }
77
+ ],
78
+ OrderParameters: [
79
+ { name: "offerer", type: "ContractAddress" },
80
+ { name: "offer", type: "OfferItem" },
81
+ { name: "consideration", type: "ConsiderationItem" },
82
+ { name: "start_time", type: "felt" },
83
+ { name: "end_time", type: "felt" },
84
+ { name: "salt", type: "felt" },
85
+ { name: "nonce", type: "felt" }
86
+ ],
87
+ OfferItem: [
88
+ { name: "item_type", type: "shortstring" },
89
+ { name: "token", type: "ContractAddress" },
90
+ { name: "identifier_or_criteria", type: "felt" },
91
+ { name: "start_amount", type: "felt" },
92
+ { name: "end_amount", type: "felt" }
93
+ ],
94
+ ConsiderationItem: [
95
+ { name: "item_type", type: "shortstring" },
96
+ { name: "token", type: "ContractAddress" },
97
+ { name: "identifier_or_criteria", type: "felt" },
98
+ { name: "start_amount", type: "felt" },
99
+ { name: "end_amount", type: "felt" },
100
+ { name: "recipient", type: "ContractAddress" }
101
+ ]
102
+ },
103
+ message
104
+ };
105
+ }
106
+ function buildFulfillmentTypedData(message, chainId) {
107
+ return {
108
+ domain: {
109
+ name: "Medialane",
110
+ version: "1",
111
+ chainId,
112
+ revision: starknet.TypedDataRevision.ACTIVE
113
+ },
114
+ primaryType: "OrderFulfillment",
115
+ types: {
116
+ StarknetDomain: [
117
+ { name: "name", type: "shortstring" },
118
+ { name: "version", type: "shortstring" },
119
+ { name: "chainId", type: "shortstring" },
120
+ { name: "revision", type: "shortstring" }
121
+ ],
122
+ OrderFulfillment: [
123
+ { name: "order_hash", type: "felt" },
124
+ { name: "fulfiller", type: "ContractAddress" },
125
+ { name: "nonce", type: "felt" }
126
+ ]
127
+ },
128
+ message
129
+ };
130
+ }
131
+ function buildCancellationTypedData(message, chainId) {
132
+ return {
133
+ domain: {
134
+ name: "Medialane",
135
+ version: "1",
136
+ chainId,
137
+ revision: starknet.TypedDataRevision.ACTIVE
138
+ },
139
+ primaryType: "OrderCancellation",
140
+ types: {
141
+ StarknetDomain: [
142
+ { name: "name", type: "shortstring" },
143
+ { name: "version", type: "shortstring" },
144
+ { name: "chainId", type: "shortstring" },
145
+ { name: "revision", type: "shortstring" }
146
+ ],
147
+ OrderCancellation: [
148
+ { name: "order_hash", type: "felt" },
149
+ { name: "offerer", type: "ContractAddress" },
150
+ { name: "nonce", type: "felt" }
151
+ ]
152
+ },
153
+ message
154
+ };
155
+ }
156
+
157
+ // src/abis.ts
158
+ var IPMarketplaceABI = [
159
+ {
160
+ type: "impl",
161
+ name: "UpgradeableImpl",
162
+ interface_name: "openzeppelin_upgrades::interface::IUpgradeable"
163
+ },
164
+ {
165
+ type: "interface",
166
+ name: "openzeppelin_upgrades::interface::IUpgradeable",
167
+ items: [
168
+ {
169
+ type: "function",
170
+ name: "upgrade",
171
+ inputs: [{ name: "new_class_hash", type: "core::starknet::class_hash::ClassHash" }],
172
+ outputs: [],
173
+ state_mutability: "external"
174
+ }
175
+ ]
176
+ },
177
+ {
178
+ type: "impl",
179
+ name: "MedialaneImpl",
180
+ interface_name: "mediolano_core::core::interface::IMedialane"
181
+ },
182
+ {
183
+ type: "struct",
184
+ name: "mediolano_core::core::types::OfferItem",
185
+ members: [
186
+ { name: "item_type", type: "core::felt252" },
187
+ { name: "token", type: "core::starknet::contract_address::ContractAddress" },
188
+ { name: "identifier_or_criteria", type: "core::felt252" },
189
+ { name: "start_amount", type: "core::felt252" },
190
+ { name: "end_amount", type: "core::felt252" }
191
+ ]
192
+ },
193
+ {
194
+ type: "struct",
195
+ name: "mediolano_core::core::types::ConsiderationItem",
196
+ members: [
197
+ { name: "item_type", type: "core::felt252" },
198
+ { name: "token", type: "core::starknet::contract_address::ContractAddress" },
199
+ { name: "identifier_or_criteria", type: "core::felt252" },
200
+ { name: "start_amount", type: "core::felt252" },
201
+ { name: "end_amount", type: "core::felt252" },
202
+ { name: "recipient", type: "core::starknet::contract_address::ContractAddress" }
203
+ ]
204
+ },
205
+ {
206
+ type: "struct",
207
+ name: "mediolano_core::core::types::OrderParameters",
208
+ members: [
209
+ { name: "offerer", type: "core::starknet::contract_address::ContractAddress" },
210
+ { name: "offer", type: "mediolano_core::core::types::OfferItem" },
211
+ { name: "consideration", type: "mediolano_core::core::types::ConsiderationItem" },
212
+ { name: "start_time", type: "core::felt252" },
213
+ { name: "end_time", type: "core::felt252" },
214
+ { name: "salt", type: "core::felt252" },
215
+ { name: "nonce", type: "core::felt252" }
216
+ ]
217
+ },
218
+ {
219
+ type: "struct",
220
+ name: "mediolano_core::core::types::Order",
221
+ members: [
222
+ { name: "parameters", type: "mediolano_core::core::types::OrderParameters" },
223
+ { name: "signature", type: "core::array::Array::<core::felt252>" }
224
+ ]
225
+ },
226
+ {
227
+ type: "struct",
228
+ name: "mediolano_core::core::types::OrderFulfillment",
229
+ members: [
230
+ { name: "order_hash", type: "core::felt252" },
231
+ { name: "fulfiller", type: "core::starknet::contract_address::ContractAddress" },
232
+ { name: "nonce", type: "core::felt252" }
233
+ ]
234
+ },
235
+ {
236
+ type: "struct",
237
+ name: "mediolano_core::core::types::FulfillmentRequest",
238
+ members: [
239
+ { name: "fulfillment", type: "mediolano_core::core::types::OrderFulfillment" },
240
+ { name: "signature", type: "core::array::Array::<core::felt252>" }
241
+ ]
242
+ },
243
+ {
244
+ type: "struct",
245
+ name: "mediolano_core::core::types::OrderCancellation",
246
+ members: [
247
+ { name: "order_hash", type: "core::felt252" },
248
+ { name: "offerer", type: "core::starknet::contract_address::ContractAddress" },
249
+ { name: "nonce", type: "core::felt252" }
250
+ ]
251
+ },
252
+ {
253
+ type: "struct",
254
+ name: "mediolano_core::core::types::CancelRequest",
255
+ members: [
256
+ { name: "cancelation", type: "mediolano_core::core::types::OrderCancellation" },
257
+ { name: "signature", type: "core::array::Array::<core::felt252>" }
258
+ ]
259
+ },
260
+ {
261
+ type: "enum",
262
+ name: "mediolano_core::core::types::OrderStatus",
263
+ variants: [
264
+ { name: "None", type: "()" },
265
+ { name: "Created", type: "()" },
266
+ { name: "Filled", type: "()" },
267
+ { name: "Cancelled", type: "()" }
268
+ ]
269
+ },
270
+ {
271
+ type: "enum",
272
+ name: "core::option::Option::<core::starknet::contract_address::ContractAddress>",
273
+ variants: [
274
+ { name: "Some", type: "core::starknet::contract_address::ContractAddress" },
275
+ { name: "None", type: "()" }
276
+ ]
277
+ },
278
+ {
279
+ type: "struct",
280
+ name: "mediolano_core::core::types::OrderDetails",
281
+ members: [
282
+ { name: "offerer", type: "core::starknet::contract_address::ContractAddress" },
283
+ { name: "offer", type: "mediolano_core::core::types::OfferItem" },
284
+ { name: "consideration", type: "mediolano_core::core::types::ConsiderationItem" },
285
+ { name: "start_time", type: "core::integer::u64" },
286
+ { name: "end_time", type: "core::integer::u64" },
287
+ { name: "order_status", type: "mediolano_core::core::types::OrderStatus" },
288
+ {
289
+ name: "fulfiller",
290
+ type: "core::option::Option::<core::starknet::contract_address::ContractAddress>"
291
+ }
292
+ ]
293
+ },
294
+ {
295
+ type: "interface",
296
+ name: "mediolano_core::core::interface::IMedialane",
297
+ items: [
298
+ {
299
+ type: "function",
300
+ name: "register_order",
301
+ inputs: [{ name: "order", type: "mediolano_core::core::types::Order" }],
302
+ outputs: [],
303
+ state_mutability: "external"
304
+ },
305
+ {
306
+ type: "function",
307
+ name: "fulfill_order",
308
+ inputs: [
309
+ {
310
+ name: "fulfillment_request",
311
+ type: "mediolano_core::core::types::FulfillmentRequest"
312
+ }
313
+ ],
314
+ outputs: [],
315
+ state_mutability: "external"
316
+ },
317
+ {
318
+ type: "function",
319
+ name: "cancel_order",
320
+ inputs: [{ name: "cancel_request", type: "mediolano_core::core::types::CancelRequest" }],
321
+ outputs: [],
322
+ state_mutability: "external"
323
+ },
324
+ {
325
+ type: "function",
326
+ name: "get_order_details",
327
+ inputs: [{ name: "order_hash", type: "core::felt252" }],
328
+ outputs: [{ type: "mediolano_core::core::types::OrderDetails" }],
329
+ state_mutability: "view"
330
+ },
331
+ {
332
+ type: "function",
333
+ name: "get_order_hash",
334
+ inputs: [
335
+ { name: "parameters", type: "mediolano_core::core::types::OrderParameters" },
336
+ { name: "signer", type: "core::starknet::contract_address::ContractAddress" }
337
+ ],
338
+ outputs: [{ type: "core::felt252" }],
339
+ state_mutability: "view"
340
+ }
341
+ ]
342
+ },
343
+ {
344
+ type: "impl",
345
+ name: "NoncesImpl",
346
+ interface_name: "openzeppelin_utils::cryptography::interface::INonces"
347
+ },
348
+ {
349
+ type: "interface",
350
+ name: "openzeppelin_utils::cryptography::interface::INonces",
351
+ items: [
352
+ {
353
+ type: "function",
354
+ name: "nonces",
355
+ inputs: [
356
+ { name: "owner", type: "core::starknet::contract_address::ContractAddress" }
357
+ ],
358
+ outputs: [{ type: "core::felt252" }],
359
+ state_mutability: "view"
360
+ }
361
+ ]
362
+ },
363
+ {
364
+ type: "impl",
365
+ name: "SRC5Impl",
366
+ interface_name: "openzeppelin_introspection::interface::ISRC5"
367
+ },
368
+ {
369
+ type: "enum",
370
+ name: "core::bool",
371
+ variants: [
372
+ { name: "False", type: "()" },
373
+ { name: "True", type: "()" }
374
+ ]
375
+ },
376
+ {
377
+ type: "interface",
378
+ name: "openzeppelin_introspection::interface::ISRC5",
379
+ items: [
380
+ {
381
+ type: "function",
382
+ name: "supports_interface",
383
+ inputs: [{ name: "interface_id", type: "core::felt252" }],
384
+ outputs: [{ type: "core::bool" }],
385
+ state_mutability: "view"
386
+ }
387
+ ]
388
+ },
389
+ {
390
+ type: "constructor",
391
+ name: "constructor",
392
+ inputs: [
393
+ { name: "manager", type: "core::starknet::contract_address::ContractAddress" },
394
+ { name: "native_token_address", type: "core::starknet::contract_address::ContractAddress" }
395
+ ]
396
+ },
397
+ {
398
+ type: "event",
399
+ name: "mediolano_core::core::events::OrderCreated",
400
+ kind: "struct",
401
+ members: [
402
+ { name: "order_hash", type: "core::felt252", kind: "key" },
403
+ {
404
+ name: "offerer",
405
+ type: "core::starknet::contract_address::ContractAddress",
406
+ kind: "key"
407
+ }
408
+ ]
409
+ },
410
+ {
411
+ type: "event",
412
+ name: "mediolano_core::core::events::OrderFulfilled",
413
+ kind: "struct",
414
+ members: [
415
+ { name: "order_hash", type: "core::felt252", kind: "key" },
416
+ {
417
+ name: "offerer",
418
+ type: "core::starknet::contract_address::ContractAddress",
419
+ kind: "key"
420
+ },
421
+ {
422
+ name: "fulfiller",
423
+ type: "core::starknet::contract_address::ContractAddress",
424
+ kind: "key"
425
+ }
426
+ ]
427
+ },
428
+ {
429
+ type: "event",
430
+ name: "mediolano_core::core::events::OrderCancelled",
431
+ kind: "struct",
432
+ members: [
433
+ { name: "order_hash", type: "core::felt252", kind: "key" },
434
+ {
435
+ name: "offerer",
436
+ type: "core::starknet::contract_address::ContractAddress",
437
+ kind: "key"
438
+ }
439
+ ]
440
+ },
441
+ {
442
+ type: "event",
443
+ name: "mediolano_core::core::medialane::Medialane::Event",
444
+ kind: "enum",
445
+ variants: [
446
+ {
447
+ name: "OrderCreated",
448
+ type: "mediolano_core::core::events::OrderCreated",
449
+ kind: "nested"
450
+ },
451
+ {
452
+ name: "OrderFulfilled",
453
+ type: "mediolano_core::core::events::OrderFulfilled",
454
+ kind: "nested"
455
+ },
456
+ {
457
+ name: "OrderCancelled",
458
+ type: "mediolano_core::core::events::OrderCancelled",
459
+ kind: "nested"
460
+ }
461
+ ]
462
+ }
463
+ ];
464
+
465
+ // src/utils/bigint.ts
466
+ function stringifyBigInts(obj) {
467
+ if (typeof obj === "bigint") {
468
+ return obj.toString();
469
+ }
470
+ if (Array.isArray(obj)) {
471
+ return obj.map(stringifyBigInts);
472
+ }
473
+ if (obj !== null && typeof obj === "object") {
474
+ return Object.fromEntries(
475
+ Object.entries(obj).map(([key, value]) => [
476
+ key,
477
+ stringifyBigInts(value)
478
+ ])
479
+ );
480
+ }
481
+ return obj;
482
+ }
483
+ function u256ToBigInt(low, high) {
484
+ return BigInt(low) + (BigInt(high) << 128n);
485
+ }
486
+
487
+ // src/utils/token.ts
488
+ function parseAmount(human, decimals) {
489
+ const [whole, frac = ""] = human.split(".");
490
+ const fracPadded = frac.padEnd(decimals, "0").slice(0, decimals);
491
+ return (BigInt(whole) * BigInt(10) ** BigInt(decimals) + BigInt(fracPadded)).toString();
492
+ }
493
+ function formatAmount(raw, decimals) {
494
+ const value = BigInt(raw);
495
+ const factor = BigInt(Math.pow(10, decimals));
496
+ const whole = value / factor;
497
+ const remainder = value % factor;
498
+ const fractional = remainder.toString().padStart(decimals, "0");
499
+ return `${whole}.${fractional}`;
500
+ }
501
+ function getTokenByAddress(address) {
502
+ const lower = address.toLowerCase();
503
+ return SUPPORTED_TOKENS.find((t) => t.address.toLowerCase() === lower);
504
+ }
505
+ function getTokenBySymbol(symbol) {
506
+ const upper = symbol.toUpperCase();
507
+ return SUPPORTED_TOKENS.find((t) => t.symbol === upper);
508
+ }
509
+
510
+ // src/marketplace/orders.ts
511
+ var MedialaneError = class extends Error {
512
+ constructor(message, cause) {
513
+ super(message);
514
+ this.cause = cause;
515
+ this.name = "MedialaneError";
516
+ }
517
+ };
518
+ function toSignatureArray(sig) {
519
+ if (Array.isArray(sig)) return sig;
520
+ const s = sig;
521
+ return [s.r.toString(), s.s.toString()];
522
+ }
523
+ function getChainId(config) {
524
+ return config.network === "mainnet" ? starknet.constants.StarknetChainId.SN_MAIN : starknet.constants.StarknetChainId.SN_SEPOLIA;
525
+ }
526
+ var _contractCache = /* @__PURE__ */ new WeakMap();
527
+ var _providerCache = /* @__PURE__ */ new WeakMap();
528
+ function getProvider(config) {
529
+ let provider = _providerCache.get(config);
530
+ if (!provider) {
531
+ provider = new starknet.RpcProvider({ nodeUrl: config.rpcUrl });
532
+ _providerCache.set(config, provider);
533
+ }
534
+ return provider;
535
+ }
536
+ function makeContract(config) {
537
+ const cached = _contractCache.get(config);
538
+ if (cached) return cached;
539
+ const provider = getProvider(config);
540
+ const contract = new starknet.Contract(
541
+ IPMarketplaceABI,
542
+ config.marketplaceContract,
543
+ provider
544
+ );
545
+ const result = { contract, provider };
546
+ _contractCache.set(config, result);
547
+ return result;
548
+ }
549
+ function resolveToken(currency) {
550
+ const token = SUPPORTED_TOKENS.find(
551
+ (t) => t.symbol === currency.toUpperCase() || t.address.toLowerCase() === currency.toLowerCase()
552
+ );
553
+ if (!token) throw new MedialaneError(`Unsupported currency: ${currency}`);
554
+ return token;
555
+ }
556
+ async function createListing(account, params, config) {
557
+ const { nftContract, tokenId, price, currency = DEFAULT_CURRENCY, durationSeconds } = params;
558
+ const { contract, provider } = makeContract(config);
559
+ const token = resolveToken(currency);
560
+ const priceWei = parseAmount(price, token.decimals);
561
+ const now = Math.floor(Date.now() / 1e3);
562
+ const startTime = now + 300;
563
+ const endTime = now + durationSeconds;
564
+ const saltBytes = new Uint8Array(4);
565
+ crypto.getRandomValues(saltBytes);
566
+ const salt = new DataView(saltBytes.buffer).getUint32(0).toString();
567
+ const currentNonce = await contract.nonces(account.address);
568
+ const orderParams = {
569
+ offerer: account.address,
570
+ offer: {
571
+ item_type: "ERC721",
572
+ token: nftContract,
573
+ identifier_or_criteria: tokenId,
574
+ start_amount: "1",
575
+ end_amount: "1"
576
+ },
577
+ consideration: {
578
+ item_type: "ERC20",
579
+ token: token.address,
580
+ identifier_or_criteria: "0",
581
+ start_amount: priceWei,
582
+ end_amount: priceWei,
583
+ recipient: account.address
584
+ },
585
+ start_time: startTime.toString(),
586
+ end_time: endTime.toString(),
587
+ salt,
588
+ nonce: currentNonce.toString()
589
+ };
590
+ const chainId = getChainId(config);
591
+ const typedData = stringifyBigInts(buildOrderTypedData(orderParams, chainId));
592
+ const signature = await account.signMessage(typedData);
593
+ const signatureArray = toSignatureArray(signature);
594
+ const registerPayload = stringifyBigInts({
595
+ parameters: {
596
+ ...orderParams,
597
+ offer: {
598
+ ...orderParams.offer,
599
+ item_type: starknet.shortString.encodeShortString(orderParams.offer.item_type)
600
+ },
601
+ consideration: {
602
+ ...orderParams.consideration,
603
+ item_type: starknet.shortString.encodeShortString(orderParams.consideration.item_type)
604
+ }
605
+ },
606
+ signature: signatureArray
607
+ });
608
+ const tokenIdUint256 = starknet.cairo.uint256(tokenId);
609
+ let isAlreadyApproved = false;
610
+ try {
611
+ const result = await provider.callContract({
612
+ contractAddress: nftContract,
613
+ entrypoint: "get_approved",
614
+ calldata: [tokenIdUint256.low.toString(), tokenIdUint256.high.toString()]
615
+ });
616
+ isAlreadyApproved = BigInt(result[0]).toString() === BigInt(config.marketplaceContract).toString();
617
+ } catch {
618
+ }
619
+ const registerCall = contract.populate("register_order", [registerPayload]);
620
+ const calls = isAlreadyApproved ? [registerCall] : [
621
+ {
622
+ contractAddress: nftContract,
623
+ entrypoint: "approve",
624
+ calldata: [
625
+ config.marketplaceContract,
626
+ tokenIdUint256.low.toString(),
627
+ tokenIdUint256.high.toString()
628
+ ]
629
+ },
630
+ registerCall
631
+ ];
632
+ try {
633
+ const tx = await account.execute(calls);
634
+ await provider.waitForTransaction(tx.transaction_hash);
635
+ return { txHash: tx.transaction_hash };
636
+ } catch (err) {
637
+ throw new MedialaneError("Failed to create listing", err);
638
+ }
639
+ }
640
+ async function makeOffer(account, params, config) {
641
+ const { nftContract, tokenId, price, currency = DEFAULT_CURRENCY, durationSeconds } = params;
642
+ const { contract, provider } = makeContract(config);
643
+ const token = resolveToken(currency);
644
+ const priceWei = parseAmount(price, token.decimals);
645
+ const now = Math.floor(Date.now() / 1e3);
646
+ const startTime = now + 300;
647
+ const endTime = now + durationSeconds;
648
+ const saltBytes = new Uint8Array(4);
649
+ crypto.getRandomValues(saltBytes);
650
+ const salt = new DataView(saltBytes.buffer).getUint32(0).toString();
651
+ const currentNonce = await contract.nonces(account.address);
652
+ const orderParams = {
653
+ offerer: account.address,
654
+ offer: {
655
+ item_type: "ERC20",
656
+ token: token.address,
657
+ identifier_or_criteria: "0",
658
+ start_amount: priceWei,
659
+ end_amount: priceWei
660
+ },
661
+ consideration: {
662
+ item_type: "ERC721",
663
+ token: nftContract,
664
+ identifier_or_criteria: tokenId,
665
+ start_amount: "1",
666
+ end_amount: "1",
667
+ recipient: account.address
668
+ },
669
+ start_time: startTime.toString(),
670
+ end_time: endTime.toString(),
671
+ salt,
672
+ nonce: currentNonce.toString()
673
+ };
674
+ const chainId = getChainId(config);
675
+ const typedData = stringifyBigInts(buildOrderTypedData(orderParams, chainId));
676
+ const signature = await account.signMessage(typedData);
677
+ const signatureArray = toSignatureArray(signature);
678
+ const registerPayload = stringifyBigInts({
679
+ parameters: {
680
+ ...orderParams,
681
+ offer: {
682
+ ...orderParams.offer,
683
+ item_type: starknet.shortString.encodeShortString(orderParams.offer.item_type)
684
+ },
685
+ consideration: {
686
+ ...orderParams.consideration,
687
+ item_type: starknet.shortString.encodeShortString(orderParams.consideration.item_type)
688
+ }
689
+ },
690
+ signature: signatureArray
691
+ });
692
+ const amountUint256 = starknet.cairo.uint256(priceWei);
693
+ const approveCall = {
694
+ contractAddress: token.address,
695
+ entrypoint: "approve",
696
+ calldata: [
697
+ config.marketplaceContract,
698
+ amountUint256.low.toString(),
699
+ amountUint256.high.toString()
700
+ ]
701
+ };
702
+ const registerCall = contract.populate("register_order", [registerPayload]);
703
+ try {
704
+ const tx = await account.execute([approveCall, registerCall]);
705
+ await provider.waitForTransaction(tx.transaction_hash);
706
+ return { txHash: tx.transaction_hash };
707
+ } catch (err) {
708
+ throw new MedialaneError("Failed to make offer", err);
709
+ }
710
+ }
711
+ async function fulfillOrder(account, params, config) {
712
+ const { orderHash } = params;
713
+ const { contract, provider } = makeContract(config);
714
+ const currentNonce = await contract.nonces(account.address);
715
+ const chainId = getChainId(config);
716
+ const fulfillmentParams = {
717
+ order_hash: orderHash,
718
+ fulfiller: account.address,
719
+ nonce: currentNonce.toString()
720
+ };
721
+ const typedData = stringifyBigInts(
722
+ buildFulfillmentTypedData(fulfillmentParams, chainId)
723
+ );
724
+ const signature = await account.signMessage(typedData);
725
+ const signatureArray = toSignatureArray(signature);
726
+ const fulfillPayload = stringifyBigInts({
727
+ fulfillment: fulfillmentParams,
728
+ signature: signatureArray
729
+ });
730
+ const call = contract.populate("fulfill_order", [fulfillPayload]);
731
+ try {
732
+ const tx = await account.execute(call);
733
+ await provider.waitForTransaction(tx.transaction_hash);
734
+ return { txHash: tx.transaction_hash };
735
+ } catch (err) {
736
+ throw new MedialaneError("Failed to fulfill order", err);
737
+ }
738
+ }
739
+ async function cancelOrder(account, params, config) {
740
+ const { orderHash } = params;
741
+ const { contract, provider } = makeContract(config);
742
+ const currentNonce = await contract.nonces(account.address);
743
+ const chainId = getChainId(config);
744
+ const cancelParams = {
745
+ order_hash: orderHash,
746
+ offerer: account.address,
747
+ nonce: currentNonce.toString()
748
+ };
749
+ const typedData = stringifyBigInts(
750
+ buildCancellationTypedData(cancelParams, chainId)
751
+ );
752
+ const signature = await account.signMessage(typedData);
753
+ const signatureArray = toSignatureArray(signature);
754
+ const cancelRequest = stringifyBigInts({
755
+ cancelation: cancelParams,
756
+ signature: signatureArray
757
+ });
758
+ const call = contract.populate("cancel_order", [cancelRequest]);
759
+ try {
760
+ const tx = await account.execute(call);
761
+ await provider.waitForTransaction(tx.transaction_hash);
762
+ return { txHash: tx.transaction_hash };
763
+ } catch (err) {
764
+ throw new MedialaneError("Failed to cancel order", err);
765
+ }
766
+ }
767
+ function encodeByteArray(str) {
768
+ const ba = starknet.byteArray.byteArrayFromString(str);
769
+ return [
770
+ ba.data.length.toString(),
771
+ ...ba.data.map((d) => starknet.num.toHex(d)),
772
+ starknet.num.toHex(ba.pending_word),
773
+ ba.pending_word_len.toString()
774
+ ];
775
+ }
776
+ async function mint(account, params, config) {
777
+ const { collectionId, recipient, tokenUri, collectionContract } = params;
778
+ const provider = getProvider(config);
779
+ const contractAddress = collectionContract ?? config.collectionContract;
780
+ const id = starknet.cairo.uint256(collectionId);
781
+ const calldata = [id.low.toString(), id.high.toString(), recipient, ...encodeByteArray(tokenUri)];
782
+ try {
783
+ const tx = await account.execute([{ contractAddress, entrypoint: "mint", calldata }]);
784
+ await provider.waitForTransaction(tx.transaction_hash);
785
+ return { txHash: tx.transaction_hash };
786
+ } catch (err) {
787
+ throw new MedialaneError("Failed to mint NFT", err);
788
+ }
789
+ }
790
+ async function createCollection(account, params, config) {
791
+ const { name, symbol, baseUri, collectionContract } = params;
792
+ const provider = getProvider(config);
793
+ const contractAddress = collectionContract ?? config.collectionContract;
794
+ const calldata = [
795
+ ...encodeByteArray(name),
796
+ ...encodeByteArray(symbol),
797
+ ...encodeByteArray(baseUri)
798
+ ];
799
+ try {
800
+ const tx = await account.execute([{ contractAddress, entrypoint: "create_collection", calldata }]);
801
+ await provider.waitForTransaction(tx.transaction_hash);
802
+ return { txHash: tx.transaction_hash };
803
+ } catch (err) {
804
+ throw new MedialaneError("Failed to create collection", err);
805
+ }
806
+ }
807
+ async function checkoutCart(account, items, config) {
808
+ if (items.length === 0) throw new MedialaneError("Cart is empty");
809
+ const { contract, provider } = makeContract(config);
810
+ const tokenTotals = /* @__PURE__ */ new Map();
811
+ for (const item of items) {
812
+ const prev = tokenTotals.get(item.considerationToken) ?? 0n;
813
+ tokenTotals.set(item.considerationToken, prev + BigInt(item.considerationAmount));
814
+ }
815
+ const approveCalls = Array.from(tokenTotals.entries()).map(([tokenAddr, totalWei]) => {
816
+ const amount = starknet.cairo.uint256(totalWei.toString());
817
+ return {
818
+ contractAddress: tokenAddr,
819
+ entrypoint: "approve",
820
+ calldata: [
821
+ config.marketplaceContract,
822
+ amount.low.toString(),
823
+ amount.high.toString()
824
+ ]
825
+ };
826
+ });
827
+ const currentNonce = await contract.nonces(account.address);
828
+ const baseNonce = BigInt(currentNonce.toString());
829
+ const chainId = getChainId(config);
830
+ const fulfillCalls = [];
831
+ for (let i = 0; i < items.length; i++) {
832
+ const item = items[i];
833
+ const nonce = (baseNonce + BigInt(i)).toString();
834
+ const fulfillmentParams = {
835
+ order_hash: item.orderHash,
836
+ fulfiller: account.address,
837
+ nonce
838
+ };
839
+ const typedData = stringifyBigInts(
840
+ buildFulfillmentTypedData(fulfillmentParams, chainId)
841
+ );
842
+ const signature = await account.signMessage(typedData);
843
+ const signatureArray = toSignatureArray(signature);
844
+ const fulfillPayload = stringifyBigInts({
845
+ fulfillment: fulfillmentParams,
846
+ signature: signatureArray
847
+ });
848
+ fulfillCalls.push(contract.populate("fulfill_order", [fulfillPayload]));
849
+ }
850
+ try {
851
+ const tx = await account.execute([...approveCalls, ...fulfillCalls]);
852
+ await provider.waitForTransaction(tx.transaction_hash);
853
+ return { txHash: tx.transaction_hash };
854
+ } catch (err) {
855
+ throw new MedialaneError("Cart checkout failed", err);
856
+ }
857
+ }
858
+
859
+ // src/marketplace/index.ts
860
+ var MarketplaceModule = class {
861
+ constructor(config) {
862
+ this.config = config;
863
+ }
864
+ // ─── Writes ───────────────────────────────────────────────────────────────
865
+ createListing(account, params) {
866
+ return createListing(account, params, this.config);
867
+ }
868
+ makeOffer(account, params) {
869
+ return makeOffer(account, params, this.config);
870
+ }
871
+ fulfillOrder(account, params) {
872
+ return fulfillOrder(account, params, this.config);
873
+ }
874
+ cancelOrder(account, params) {
875
+ return cancelOrder(account, params, this.config);
876
+ }
877
+ checkoutCart(account, items) {
878
+ return checkoutCart(account, items, this.config);
879
+ }
880
+ mint(account, params) {
881
+ return mint(account, params, this.config);
882
+ }
883
+ createCollection(account, params) {
884
+ return createCollection(account, params, this.config);
885
+ }
886
+ // ─── Typed data builders (for ChipiPay / custom signing flows) ───────────
887
+ buildListingTypedData(params, chainId) {
888
+ return buildOrderTypedData(params, chainId);
889
+ }
890
+ buildFulfillmentTypedData(params, chainId) {
891
+ return buildFulfillmentTypedData(params, chainId);
892
+ }
893
+ buildCancellationTypedData(params, chainId) {
894
+ return buildCancellationTypedData(params, chainId);
895
+ }
896
+ };
897
+
898
+ // src/api/client.ts
899
+ var MedialaneApiError = class extends Error {
900
+ constructor(status, message) {
901
+ super(message);
902
+ this.status = status;
903
+ this.name = "MedialaneApiError";
904
+ }
905
+ };
906
+ var ApiClient = class {
907
+ constructor(baseUrl, apiKey) {
908
+ this.baseUrl = baseUrl;
909
+ this.baseHeaders = apiKey ? { "x-api-key": apiKey } : {};
910
+ }
911
+ async request(path, init) {
912
+ const url = `${this.baseUrl.replace(/\/$/, "")}${path}`;
913
+ const headers = { ...this.baseHeaders };
914
+ if (!(init?.body instanceof FormData)) {
915
+ headers["Content-Type"] = "application/json";
916
+ }
917
+ const res = await fetch(url, {
918
+ ...init,
919
+ headers: { ...headers, ...init?.headers }
920
+ });
921
+ if (!res.ok) {
922
+ let message = res.statusText;
923
+ try {
924
+ const body = await res.json();
925
+ if (body.error) message = body.error;
926
+ } catch {
927
+ }
928
+ throw new MedialaneApiError(res.status, message);
929
+ }
930
+ return res.json();
931
+ }
932
+ get(path) {
933
+ return this.request(path, { method: "GET" });
934
+ }
935
+ post(path, body) {
936
+ return this.request(path, { method: "POST", body: JSON.stringify(body) });
937
+ }
938
+ patch(path, body) {
939
+ return this.request(path, { method: "PATCH", body: JSON.stringify(body) });
940
+ }
941
+ del(path) {
942
+ return this.request(path, { method: "DELETE" });
943
+ }
944
+ // ─── Orders ────────────────────────────────────────────────────────────────
945
+ getOrders(query = {}) {
946
+ const params = new URLSearchParams();
947
+ if (query.status) params.set("status", query.status);
948
+ if (query.collection) params.set("collection", query.collection);
949
+ if (query.currency) params.set("currency", query.currency);
950
+ if (query.sort) params.set("sort", query.sort);
951
+ if (query.page !== void 0) params.set("page", String(query.page));
952
+ if (query.limit !== void 0) params.set("limit", String(query.limit));
953
+ if (query.offerer) params.set("offerer", query.offerer);
954
+ const qs = params.toString();
955
+ return this.get(`/v1/orders${qs ? `?${qs}` : ""}`);
956
+ }
957
+ getOrder(orderHash) {
958
+ return this.get(`/v1/orders/${orderHash}`);
959
+ }
960
+ getActiveOrdersForToken(contract, tokenId) {
961
+ return this.get(`/v1/orders/token/${contract}/${tokenId}`);
962
+ }
963
+ getOrdersByUser(address, page = 1, limit = 20) {
964
+ return this.get(
965
+ `/v1/orders/user/${address}?page=${page}&limit=${limit}`
966
+ );
967
+ }
968
+ // ─── Tokens ────────────────────────────────────────────────────────────────
969
+ getToken(contract, tokenId, wait = false) {
970
+ return this.get(
971
+ `/v1/tokens/${contract}/${tokenId}${wait ? "?wait=true" : ""}`
972
+ );
973
+ }
974
+ getTokensByOwner(address, page = 1, limit = 20) {
975
+ return this.get(
976
+ `/v1/tokens/owned/${address}?page=${page}&limit=${limit}`
977
+ );
978
+ }
979
+ getTokenHistory(contract, tokenId, page = 1, limit = 20) {
980
+ return this.get(
981
+ `/v1/tokens/${contract}/${tokenId}/history?page=${page}&limit=${limit}`
982
+ );
983
+ }
984
+ // ─── Collections ───────────────────────────────────────────────────────────
985
+ getCollections(page = 1, limit = 20) {
986
+ return this.get(`/v1/collections?page=${page}&limit=${limit}`);
987
+ }
988
+ getCollection(contract) {
989
+ return this.get(`/v1/collections/${contract}`);
990
+ }
991
+ getCollectionTokens(contract, page = 1, limit = 20) {
992
+ return this.get(
993
+ `/v1/collections/${contract}/tokens?page=${page}&limit=${limit}`
994
+ );
995
+ }
996
+ // ─── Activities ────────────────────────────────────────────────────────────
997
+ getActivities(query = {}) {
998
+ const params = new URLSearchParams();
999
+ if (query.type) params.set("type", query.type);
1000
+ if (query.page !== void 0) params.set("page", String(query.page));
1001
+ if (query.limit !== void 0) params.set("limit", String(query.limit));
1002
+ const qs = params.toString();
1003
+ return this.get(`/v1/activities${qs ? `?${qs}` : ""}`);
1004
+ }
1005
+ getActivitiesByAddress(address, page = 1, limit = 20) {
1006
+ return this.get(
1007
+ `/v1/activities/${address}?page=${page}&limit=${limit}`
1008
+ );
1009
+ }
1010
+ // ─── Search ────────────────────────────────────────────────────────────────
1011
+ search(q, limit = 10) {
1012
+ const params = new URLSearchParams({ q, limit: String(limit) });
1013
+ return this.get(
1014
+ `/v1/search?${params.toString()}`
1015
+ );
1016
+ }
1017
+ // ─── Intents ───────────────────────────────────────────────────────────────
1018
+ createListingIntent(params) {
1019
+ return this.post("/v1/intents/listing", params);
1020
+ }
1021
+ createOfferIntent(params) {
1022
+ return this.post("/v1/intents/offer", params);
1023
+ }
1024
+ createFulfillIntent(params) {
1025
+ return this.post("/v1/intents/fulfill", params);
1026
+ }
1027
+ createCancelIntent(params) {
1028
+ return this.post("/v1/intents/cancel", params);
1029
+ }
1030
+ getIntent(id) {
1031
+ return this.get(`/v1/intents/${id}`);
1032
+ }
1033
+ submitIntentSignature(id, signature) {
1034
+ return this.patch(`/v1/intents/${id}/signature`, { signature });
1035
+ }
1036
+ createMintIntent(params) {
1037
+ return this.post("/v1/intents/mint", params);
1038
+ }
1039
+ createCollectionIntent(params) {
1040
+ return this.post("/v1/intents/create-collection", params);
1041
+ }
1042
+ // ─── Metadata ──────────────────────────────────────────────────────────────
1043
+ getMetadataSignedUrl() {
1044
+ return this.get("/v1/metadata/signed-url");
1045
+ }
1046
+ uploadMetadata(metadata) {
1047
+ return this.post("/v1/metadata/upload", metadata);
1048
+ }
1049
+ resolveMetadata(uri) {
1050
+ const params = new URLSearchParams({ uri });
1051
+ return this.get(`/v1/metadata/resolve?${params.toString()}`);
1052
+ }
1053
+ uploadFile(file) {
1054
+ const formData = new FormData();
1055
+ formData.append("file", file);
1056
+ return this.request("/v1/metadata/upload-file", {
1057
+ method: "POST",
1058
+ body: formData
1059
+ });
1060
+ }
1061
+ // ─── Portal (tenant self-service) ──────────────────────────────────────────
1062
+ getMe() {
1063
+ return this.get("/v1/portal/me");
1064
+ }
1065
+ getApiKeys() {
1066
+ return this.get("/v1/portal/keys");
1067
+ }
1068
+ createApiKey(label) {
1069
+ return this.post("/v1/portal/keys", label ? { label } : {});
1070
+ }
1071
+ deleteApiKey(id) {
1072
+ return this.del(`/v1/portal/keys/${id}`);
1073
+ }
1074
+ getUsage() {
1075
+ return this.get("/v1/portal/usage");
1076
+ }
1077
+ getWebhooks() {
1078
+ return this.get("/v1/portal/webhooks");
1079
+ }
1080
+ createWebhook(params) {
1081
+ return this.post("/v1/portal/webhooks", params);
1082
+ }
1083
+ deleteWebhook(id) {
1084
+ return this.del(
1085
+ `/v1/portal/webhooks/${id}`
1086
+ );
1087
+ }
1088
+ };
1089
+
1090
+ // src/client.ts
1091
+ var MedialaneClient = class {
1092
+ constructor(rawConfig = {}) {
1093
+ this.config = resolveConfig(rawConfig);
1094
+ this.marketplace = new MarketplaceModule(this.config);
1095
+ if (!this.config.backendUrl) {
1096
+ this.api = new Proxy({}, {
1097
+ get(_target, prop) {
1098
+ return () => {
1099
+ throw new Error(
1100
+ `backendUrl not configured. Pass backendUrl to MedialaneClient to use .api.${String(prop)}()`
1101
+ );
1102
+ };
1103
+ }
1104
+ });
1105
+ } else {
1106
+ this.api = new ApiClient(this.config.backendUrl, this.config.apiKey);
1107
+ }
1108
+ }
1109
+ get network() {
1110
+ return this.config.network;
1111
+ }
1112
+ get rpcUrl() {
1113
+ return this.config.rpcUrl;
1114
+ }
1115
+ get marketplaceContract() {
1116
+ return this.config.marketplaceContract;
1117
+ }
1118
+ };
1119
+
1120
+ // src/utils/address.ts
1121
+ function normalizeAddress(address) {
1122
+ const hex = address.replace(/^0x/, "").toLowerCase();
1123
+ return "0x" + hex.padStart(64, "0");
1124
+ }
1125
+ function shortenAddress(address, chars = 4) {
1126
+ const norm = normalizeAddress(address);
1127
+ return `${norm.slice(0, chars + 2)}...${norm.slice(-chars)}`;
1128
+ }
1129
+
1130
+ exports.ApiClient = ApiClient;
1131
+ exports.COLLECTION_CONTRACT_MAINNET = COLLECTION_CONTRACT_MAINNET;
1132
+ exports.DEFAULT_RPC_URLS = DEFAULT_RPC_URLS;
1133
+ exports.IPMarketplaceABI = IPMarketplaceABI;
1134
+ exports.MARKETPLACE_CONTRACT_MAINNET = MARKETPLACE_CONTRACT_MAINNET;
1135
+ exports.MarketplaceModule = MarketplaceModule;
1136
+ exports.MedialaneApiError = MedialaneApiError;
1137
+ exports.MedialaneClient = MedialaneClient;
1138
+ exports.MedialaneError = MedialaneError;
1139
+ exports.SUPPORTED_NETWORKS = SUPPORTED_NETWORKS;
1140
+ exports.SUPPORTED_TOKENS = SUPPORTED_TOKENS;
1141
+ exports.buildCancellationTypedData = buildCancellationTypedData;
1142
+ exports.buildFulfillmentTypedData = buildFulfillmentTypedData;
1143
+ exports.buildOrderTypedData = buildOrderTypedData;
1144
+ exports.formatAmount = formatAmount;
1145
+ exports.getTokenByAddress = getTokenByAddress;
1146
+ exports.getTokenBySymbol = getTokenBySymbol;
1147
+ exports.normalizeAddress = normalizeAddress;
1148
+ exports.parseAmount = parseAmount;
1149
+ exports.resolveConfig = resolveConfig;
1150
+ exports.shortenAddress = shortenAddress;
1151
+ exports.stringifyBigInts = stringifyBigInts;
1152
+ exports.u256ToBigInt = u256ToBigInt;
1153
+ //# sourceMappingURL=index.cjs.map
1154
+ //# sourceMappingURL=index.cjs.map