@medialane/sdk 0.3.2 → 0.4.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.d.ts CHANGED
@@ -29,6 +29,12 @@ declare const SUPPORTED_NETWORKS: readonly ["mainnet", "sepolia"];
29
29
  type Network = (typeof SUPPORTED_NETWORKS)[number];
30
30
  declare const DEFAULT_RPC_URLS: Record<Network, string>;
31
31
 
32
+ interface RetryOptions {
33
+ maxAttempts?: number;
34
+ baseDelayMs?: number;
35
+ maxDelayMs?: number;
36
+ }
37
+
32
38
  declare const MedialaneConfigSchema: z.ZodObject<{
33
39
  network: z.ZodDefault<z.ZodEnum<["mainnet", "sepolia"]>>;
34
40
  rpcUrl: z.ZodOptional<z.ZodString>;
@@ -37,20 +43,43 @@ declare const MedialaneConfigSchema: z.ZodObject<{
37
43
  apiKey: z.ZodOptional<z.ZodString>;
38
44
  marketplaceContract: z.ZodOptional<z.ZodString>;
39
45
  collectionContract: z.ZodOptional<z.ZodString>;
46
+ retryOptions: z.ZodOptional<z.ZodObject<{
47
+ maxAttempts: z.ZodOptional<z.ZodNumber>;
48
+ baseDelayMs: z.ZodOptional<z.ZodNumber>;
49
+ maxDelayMs: z.ZodOptional<z.ZodNumber>;
50
+ }, "strip", z.ZodTypeAny, {
51
+ maxAttempts?: number | undefined;
52
+ baseDelayMs?: number | undefined;
53
+ maxDelayMs?: number | undefined;
54
+ }, {
55
+ maxAttempts?: number | undefined;
56
+ baseDelayMs?: number | undefined;
57
+ maxDelayMs?: number | undefined;
58
+ }>>;
40
59
  }, "strip", z.ZodTypeAny, {
41
60
  network: "mainnet" | "sepolia";
61
+ collectionContract?: string | undefined;
42
62
  rpcUrl?: string | undefined;
43
63
  backendUrl?: string | undefined;
44
64
  apiKey?: string | undefined;
45
65
  marketplaceContract?: string | undefined;
46
- collectionContract?: string | undefined;
66
+ retryOptions?: {
67
+ maxAttempts?: number | undefined;
68
+ baseDelayMs?: number | undefined;
69
+ maxDelayMs?: number | undefined;
70
+ } | undefined;
47
71
  }, {
72
+ collectionContract?: string | undefined;
48
73
  network?: "mainnet" | "sepolia" | undefined;
49
74
  rpcUrl?: string | undefined;
50
75
  backendUrl?: string | undefined;
51
76
  apiKey?: string | undefined;
52
77
  marketplaceContract?: string | undefined;
53
- collectionContract?: string | undefined;
78
+ retryOptions?: {
79
+ maxAttempts?: number | undefined;
80
+ baseDelayMs?: number | undefined;
81
+ maxDelayMs?: number | undefined;
82
+ } | undefined;
54
83
  }>;
55
84
  type MedialaneConfig = z.input<typeof MedialaneConfigSchema>;
56
85
  interface ResolvedConfig {
@@ -60,6 +89,7 @@ interface ResolvedConfig {
60
89
  apiKey: string | undefined;
61
90
  marketplaceContract: string;
62
91
  collectionContract: string;
92
+ retryOptions?: RetryOptions;
63
93
  }
64
94
  declare function resolveConfig(raw: MedialaneConfig): ResolvedConfig;
65
95
 
@@ -145,9 +175,12 @@ interface TxResult {
145
175
  txHash: string;
146
176
  }
147
177
 
178
+ type MedialaneErrorCode = "TOKEN_NOT_FOUND" | "COLLECTION_NOT_FOUND" | "ORDER_NOT_FOUND" | "INTENT_NOT_FOUND" | "INTENT_EXPIRED" | "RATE_LIMITED" | "NETWORK_NOT_SUPPORTED" | "APPROVAL_FAILED" | "TRANSACTION_FAILED" | "INVALID_PARAMS" | "UNAUTHORIZED" | "UNKNOWN";
179
+
148
180
  declare class MedialaneError extends Error {
181
+ readonly code: MedialaneErrorCode;
149
182
  readonly cause?: unknown | undefined;
150
- constructor(message: string, cause?: unknown | undefined);
183
+ constructor(message: string, code?: MedialaneErrorCode, cause?: unknown | undefined);
151
184
  }
152
185
 
153
186
  declare class MarketplaceModule {
@@ -165,6 +198,14 @@ declare class MarketplaceModule {
165
198
  buildCancellationTypedData(params: Record<string, unknown>, chainId: constants.StarknetChainId): TypedData;
166
199
  }
167
200
 
201
+ type CollectionSort = "recent" | "supply" | "floor" | "volume" | "name";
202
+ interface ApiCollectionsQuery {
203
+ page?: number;
204
+ limit?: number;
205
+ isKnown?: boolean;
206
+ sort?: CollectionSort;
207
+ owner?: string;
208
+ }
168
209
  type OrderStatus = "ACTIVE" | "FULFILLED" | "CANCELLED" | "EXPIRED";
169
210
  type SortOrder = "price_asc" | "price_desc" | "recent";
170
211
  type ActivityType = "transfer" | "sale" | "listing" | "offer" | "cancelled";
@@ -494,12 +535,14 @@ interface CreateWebhookParams {
494
535
 
495
536
  declare class MedialaneApiError extends Error {
496
537
  readonly status: number;
538
+ readonly code: MedialaneErrorCode;
497
539
  constructor(status: number, message: string);
498
540
  }
499
541
  declare class ApiClient {
500
542
  private readonly baseUrl;
501
543
  private readonly baseHeaders;
502
- constructor(baseUrl: string, apiKey?: string);
544
+ private readonly retryOptions;
545
+ constructor(baseUrl: string, apiKey?: string, retryOptions?: RetryOptions);
503
546
  private request;
504
547
  private get;
505
548
  private post;
@@ -512,7 +555,7 @@ declare class ApiClient {
512
555
  getToken(contract: string, tokenId: string, wait?: boolean): Promise<ApiResponse<ApiToken>>;
513
556
  getTokensByOwner(address: string, page?: number, limit?: number): Promise<ApiResponse<ApiToken[]>>;
514
557
  getTokenHistory(contract: string, tokenId: string, page?: number, limit?: number): Promise<ApiResponse<ApiActivity[]>>;
515
- getCollections(page?: number, limit?: number, isKnown?: boolean): Promise<ApiResponse<ApiCollection[]>>;
558
+ getCollections(page?: number, limit?: number, isKnown?: boolean, sort?: CollectionSort): Promise<ApiResponse<ApiCollection[]>>;
516
559
  getCollectionsByOwner(owner: string, page?: number, limit?: number): Promise<ApiResponse<ApiCollection[]>>;
517
560
  getCollection(contract: string): Promise<ApiResponse<ApiCollection>>;
518
561
  getCollectionTokens(contract: string, page?: number, limit?: number): Promise<ApiResponse<ApiToken[]>>;
@@ -989,4 +1032,4 @@ declare function buildFulfillmentTypedData(message: Record<string, unknown>, cha
989
1032
  */
990
1033
  declare function buildCancellationTypedData(message: Record<string, unknown>, chainId: constants.StarknetChainId): TypedData;
991
1034
 
992
- export { type ActivityType, type ApiActivitiesQuery, type ApiActivity, type ApiActivityPrice, ApiClient, type ApiCollection, type ApiIntent, type ApiIntentCreated, type ApiKeyStatus, type ApiMeta, type ApiMetadataSignedUrl, type ApiMetadataUpload, type ApiOrder, type ApiOrderConsideration, type ApiOrderOffer, type ApiOrderPrice, type ApiOrderTokenMeta, type ApiOrderTxHash, type ApiOrdersQuery, type ApiPortalKey, type ApiPortalKeyCreated, type ApiPortalMe, type ApiResponse, type ApiSearchCollectionResult, type ApiSearchResult, type ApiSearchTokenResult, type ApiToken, type ApiTokenMetadata, type ApiUsageDay, type ApiWebhookCreated, type ApiWebhookEndpoint, COLLECTION_CONTRACT_MAINNET, type CancelOrderIntentParams, type CancelOrderParams, type Cancelation, type CartItem, type ConsiderationItem, type CreateCollectionIntentParams, type CreateCollectionParams, type CreateListingIntentParams, type CreateListingParams, type CreateMintIntentParams, type CreateWebhookParams, DEFAULT_RPC_URLS, type FulfillOrderIntentParams, type FulfillOrderParams, type Fulfillment, IPMarketplaceABI, type IntentStatus, type IntentType, type IpAttribute, type IpNftMetadata, MARKETPLACE_CONTRACT_MAINNET, type MakeOfferIntentParams, type MakeOfferParams, MarketplaceModule, MedialaneApiError, MedialaneClient, type MedialaneConfig, MedialaneError, type MintParams, type Network, type OfferItem, type Order, type OrderParameters, type OrderStatus, type ResolvedConfig, SUPPORTED_NETWORKS, SUPPORTED_TOKENS, type SortOrder, type SupportedTokenSymbol, type TenantPlan, type TxResult, type WebhookEventType, type WebhookStatus, buildCancellationTypedData, buildFulfillmentTypedData, buildOrderTypedData, formatAmount, getTokenByAddress, getTokenBySymbol, normalizeAddress, parseAmount, resolveConfig, shortenAddress, stringifyBigInts, u256ToBigInt };
1035
+ export { type ActivityType, type ApiActivitiesQuery, type ApiActivity, type ApiActivityPrice, ApiClient, type ApiCollection, type ApiCollectionsQuery, type ApiIntent, type ApiIntentCreated, type ApiKeyStatus, type ApiMeta, type ApiMetadataSignedUrl, type ApiMetadataUpload, type ApiOrder, type ApiOrderConsideration, type ApiOrderOffer, type ApiOrderPrice, type ApiOrderTokenMeta, type ApiOrderTxHash, type ApiOrdersQuery, type ApiPortalKey, type ApiPortalKeyCreated, type ApiPortalMe, type ApiResponse, type ApiSearchCollectionResult, type ApiSearchResult, type ApiSearchTokenResult, type ApiToken, type ApiTokenMetadata, type ApiUsageDay, type ApiWebhookCreated, type ApiWebhookEndpoint, COLLECTION_CONTRACT_MAINNET, type CancelOrderIntentParams, type CancelOrderParams, type Cancelation, type CartItem, type CollectionSort, type ConsiderationItem, type CreateCollectionIntentParams, type CreateCollectionParams, type CreateListingIntentParams, type CreateListingParams, type CreateMintIntentParams, type CreateWebhookParams, DEFAULT_RPC_URLS, type FulfillOrderIntentParams, type FulfillOrderParams, type Fulfillment, IPMarketplaceABI, type IntentStatus, type IntentType, type IpAttribute, type IpNftMetadata, MARKETPLACE_CONTRACT_MAINNET, type MakeOfferIntentParams, type MakeOfferParams, MarketplaceModule, MedialaneApiError, MedialaneClient, type MedialaneConfig, MedialaneError, type MedialaneErrorCode, type MintParams, type Network, type OfferItem, type Order, type OrderParameters, type OrderStatus, type ResolvedConfig, type RetryOptions, SUPPORTED_NETWORKS, SUPPORTED_TOKENS, type SortOrder, type SupportedTokenSymbol, type TenantPlan, type TxResult, type WebhookEventType, type WebhookStatus, buildCancellationTypedData, buildFulfillmentTypedData, buildOrderTypedData, formatAmount, getTokenByAddress, getTokenBySymbol, normalizeAddress, parseAmount, resolveConfig, shortenAddress, stringifyBigInts, u256ToBigInt };
package/dist/index.js CHANGED
@@ -6,6 +6,8 @@ import { TypedDataRevision, shortString, cairo, Contract, constants, RpcProvider
6
6
  // src/constants.ts
7
7
  var MARKETPLACE_CONTRACT_MAINNET = "0x04299b51289aa700de4ce19cc77bcea8430bfd1aef04193efab09d60a3a7ee0f";
8
8
  var COLLECTION_CONTRACT_MAINNET = "0x05e73b7be06d82beeb390a0e0d655f2c9e7cf519658e04f05d9c690ccc41da03";
9
+ var MARKETPLACE_CONTRACT_SEPOLIA = "";
10
+ var COLLECTION_CONTRACT_SEPOLIA = "";
9
11
  var SUPPORTED_TOKENS = [
10
12
  {
11
13
  // Circle-native USDC on Starknet (canonical — preferred)
@@ -42,122 +44,6 @@ var DEFAULT_RPC_URLS = {
42
44
  sepolia: "https://rpc.starknet-sepolia.lava.build"
43
45
  };
44
46
 
45
- // src/config.ts
46
- var MedialaneConfigSchema = z.object({
47
- network: z.enum(SUPPORTED_NETWORKS).default("mainnet"),
48
- rpcUrl: z.string().url().optional(),
49
- backendUrl: z.string().url().optional(),
50
- /** API key for authenticated /v1/* backend endpoints */
51
- apiKey: z.string().optional(),
52
- marketplaceContract: z.string().optional(),
53
- collectionContract: z.string().optional()
54
- });
55
- function resolveConfig(raw) {
56
- const parsed = MedialaneConfigSchema.parse(raw);
57
- return {
58
- network: parsed.network,
59
- rpcUrl: parsed.rpcUrl ?? DEFAULT_RPC_URLS[parsed.network],
60
- backendUrl: parsed.backendUrl,
61
- apiKey: parsed.apiKey,
62
- marketplaceContract: parsed.marketplaceContract ?? MARKETPLACE_CONTRACT_MAINNET,
63
- collectionContract: parsed.collectionContract ?? COLLECTION_CONTRACT_MAINNET
64
- };
65
- }
66
- function buildOrderTypedData(message, chainId) {
67
- return {
68
- domain: {
69
- name: "Medialane",
70
- version: "1",
71
- chainId,
72
- revision: TypedDataRevision.ACTIVE
73
- },
74
- primaryType: "OrderParameters",
75
- types: {
76
- StarknetDomain: [
77
- { name: "name", type: "shortstring" },
78
- { name: "version", type: "shortstring" },
79
- { name: "chainId", type: "shortstring" },
80
- { name: "revision", type: "shortstring" }
81
- ],
82
- OrderParameters: [
83
- { name: "offerer", type: "ContractAddress" },
84
- { name: "offer", type: "OfferItem" },
85
- { name: "consideration", type: "ConsiderationItem" },
86
- { name: "start_time", type: "felt" },
87
- { name: "end_time", type: "felt" },
88
- { name: "salt", type: "felt" },
89
- { name: "nonce", type: "felt" }
90
- ],
91
- OfferItem: [
92
- { name: "item_type", type: "shortstring" },
93
- { name: "token", type: "ContractAddress" },
94
- { name: "identifier_or_criteria", type: "felt" },
95
- { name: "start_amount", type: "felt" },
96
- { name: "end_amount", type: "felt" }
97
- ],
98
- ConsiderationItem: [
99
- { name: "item_type", type: "shortstring" },
100
- { name: "token", type: "ContractAddress" },
101
- { name: "identifier_or_criteria", type: "felt" },
102
- { name: "start_amount", type: "felt" },
103
- { name: "end_amount", type: "felt" },
104
- { name: "recipient", type: "ContractAddress" }
105
- ]
106
- },
107
- message
108
- };
109
- }
110
- function buildFulfillmentTypedData(message, chainId) {
111
- return {
112
- domain: {
113
- name: "Medialane",
114
- version: "1",
115
- chainId,
116
- revision: TypedDataRevision.ACTIVE
117
- },
118
- primaryType: "OrderFulfillment",
119
- types: {
120
- StarknetDomain: [
121
- { name: "name", type: "shortstring" },
122
- { name: "version", type: "shortstring" },
123
- { name: "chainId", type: "shortstring" },
124
- { name: "revision", type: "shortstring" }
125
- ],
126
- OrderFulfillment: [
127
- { name: "order_hash", type: "felt" },
128
- { name: "fulfiller", type: "ContractAddress" },
129
- { name: "nonce", type: "felt" }
130
- ]
131
- },
132
- message
133
- };
134
- }
135
- function buildCancellationTypedData(message, chainId) {
136
- return {
137
- domain: {
138
- name: "Medialane",
139
- version: "1",
140
- chainId,
141
- revision: TypedDataRevision.ACTIVE
142
- },
143
- primaryType: "OrderCancellation",
144
- types: {
145
- StarknetDomain: [
146
- { name: "name", type: "shortstring" },
147
- { name: "version", type: "shortstring" },
148
- { name: "chainId", type: "shortstring" },
149
- { name: "revision", type: "shortstring" }
150
- ],
151
- OrderCancellation: [
152
- { name: "order_hash", type: "felt" },
153
- { name: "offerer", type: "ContractAddress" },
154
- { name: "nonce", type: "felt" }
155
- ]
156
- },
157
- message
158
- };
159
- }
160
-
161
47
  // src/abis.ts
162
48
  var IPMarketplaceABI = [
163
49
  {
@@ -510,11 +396,106 @@ function getTokenBySymbol(symbol) {
510
396
  const upper = symbol.toUpperCase();
511
397
  return SUPPORTED_TOKENS.find((t) => t.symbol === upper);
512
398
  }
399
+ function buildOrderTypedData(message, chainId) {
400
+ return {
401
+ domain: {
402
+ name: "Medialane",
403
+ version: "1",
404
+ chainId,
405
+ revision: TypedDataRevision.ACTIVE
406
+ },
407
+ primaryType: "OrderParameters",
408
+ types: {
409
+ StarknetDomain: [
410
+ { name: "name", type: "shortstring" },
411
+ { name: "version", type: "shortstring" },
412
+ { name: "chainId", type: "shortstring" },
413
+ { name: "revision", type: "shortstring" }
414
+ ],
415
+ OrderParameters: [
416
+ { name: "offerer", type: "ContractAddress" },
417
+ { name: "offer", type: "OfferItem" },
418
+ { name: "consideration", type: "ConsiderationItem" },
419
+ { name: "start_time", type: "felt" },
420
+ { name: "end_time", type: "felt" },
421
+ { name: "salt", type: "felt" },
422
+ { name: "nonce", type: "felt" }
423
+ ],
424
+ OfferItem: [
425
+ { name: "item_type", type: "shortstring" },
426
+ { name: "token", type: "ContractAddress" },
427
+ { name: "identifier_or_criteria", type: "felt" },
428
+ { name: "start_amount", type: "felt" },
429
+ { name: "end_amount", type: "felt" }
430
+ ],
431
+ ConsiderationItem: [
432
+ { name: "item_type", type: "shortstring" },
433
+ { name: "token", type: "ContractAddress" },
434
+ { name: "identifier_or_criteria", type: "felt" },
435
+ { name: "start_amount", type: "felt" },
436
+ { name: "end_amount", type: "felt" },
437
+ { name: "recipient", type: "ContractAddress" }
438
+ ]
439
+ },
440
+ message
441
+ };
442
+ }
443
+ function buildFulfillmentTypedData(message, chainId) {
444
+ return {
445
+ domain: {
446
+ name: "Medialane",
447
+ version: "1",
448
+ chainId,
449
+ revision: TypedDataRevision.ACTIVE
450
+ },
451
+ primaryType: "OrderFulfillment",
452
+ types: {
453
+ StarknetDomain: [
454
+ { name: "name", type: "shortstring" },
455
+ { name: "version", type: "shortstring" },
456
+ { name: "chainId", type: "shortstring" },
457
+ { name: "revision", type: "shortstring" }
458
+ ],
459
+ OrderFulfillment: [
460
+ { name: "order_hash", type: "felt" },
461
+ { name: "fulfiller", type: "ContractAddress" },
462
+ { name: "nonce", type: "felt" }
463
+ ]
464
+ },
465
+ message
466
+ };
467
+ }
468
+ function buildCancellationTypedData(message, chainId) {
469
+ return {
470
+ domain: {
471
+ name: "Medialane",
472
+ version: "1",
473
+ chainId,
474
+ revision: TypedDataRevision.ACTIVE
475
+ },
476
+ primaryType: "OrderCancellation",
477
+ types: {
478
+ StarknetDomain: [
479
+ { name: "name", type: "shortstring" },
480
+ { name: "version", type: "shortstring" },
481
+ { name: "chainId", type: "shortstring" },
482
+ { name: "revision", type: "shortstring" }
483
+ ],
484
+ OrderCancellation: [
485
+ { name: "order_hash", type: "felt" },
486
+ { name: "offerer", type: "ContractAddress" },
487
+ { name: "nonce", type: "felt" }
488
+ ]
489
+ },
490
+ message
491
+ };
492
+ }
513
493
 
514
494
  // src/marketplace/orders.ts
515
495
  var MedialaneError = class extends Error {
516
- constructor(message, cause) {
496
+ constructor(message, code = "UNKNOWN", cause) {
517
497
  super(message);
498
+ this.code = code;
518
499
  this.cause = cause;
519
500
  this.name = "MedialaneError";
520
501
  }
@@ -554,7 +535,7 @@ function resolveToken(currency) {
554
535
  const token = SUPPORTED_TOKENS.find(
555
536
  (t) => t.symbol === currency.toUpperCase() || t.address.toLowerCase() === currency.toLowerCase()
556
537
  );
557
- if (!token) throw new MedialaneError(`Unsupported currency: ${currency}`);
538
+ if (!token) throw new MedialaneError(`Unsupported currency: ${currency}`, "INVALID_PARAMS");
558
539
  return token;
559
540
  }
560
541
  async function createListing(account, params, config) {
@@ -638,7 +619,7 @@ async function createListing(account, params, config) {
638
619
  await provider.waitForTransaction(tx.transaction_hash);
639
620
  return { txHash: tx.transaction_hash };
640
621
  } catch (err) {
641
- throw new MedialaneError("Failed to create listing", err);
622
+ throw new MedialaneError("Failed to create listing", "TRANSACTION_FAILED", err);
642
623
  }
643
624
  }
644
625
  async function makeOffer(account, params, config) {
@@ -709,7 +690,7 @@ async function makeOffer(account, params, config) {
709
690
  await provider.waitForTransaction(tx.transaction_hash);
710
691
  return { txHash: tx.transaction_hash };
711
692
  } catch (err) {
712
- throw new MedialaneError("Failed to make offer", err);
693
+ throw new MedialaneError("Failed to make offer", "TRANSACTION_FAILED", err);
713
694
  }
714
695
  }
715
696
  async function fulfillOrder(account, params, config) {
@@ -737,7 +718,7 @@ async function fulfillOrder(account, params, config) {
737
718
  await provider.waitForTransaction(tx.transaction_hash);
738
719
  return { txHash: tx.transaction_hash };
739
720
  } catch (err) {
740
- throw new MedialaneError("Failed to fulfill order", err);
721
+ throw new MedialaneError("Failed to fulfill order", "TRANSACTION_FAILED", err);
741
722
  }
742
723
  }
743
724
  async function cancelOrder(account, params, config) {
@@ -765,7 +746,7 @@ async function cancelOrder(account, params, config) {
765
746
  await provider.waitForTransaction(tx.transaction_hash);
766
747
  return { txHash: tx.transaction_hash };
767
748
  } catch (err) {
768
- throw new MedialaneError("Failed to cancel order", err);
749
+ throw new MedialaneError("Failed to cancel order", "TRANSACTION_FAILED", err);
769
750
  }
770
751
  }
771
752
  function encodeByteArray(str) {
@@ -788,7 +769,7 @@ async function mint(account, params, config) {
788
769
  await provider.waitForTransaction(tx.transaction_hash);
789
770
  return { txHash: tx.transaction_hash };
790
771
  } catch (err) {
791
- throw new MedialaneError("Failed to mint NFT", err);
772
+ throw new MedialaneError("Failed to mint NFT", "TRANSACTION_FAILED", err);
792
773
  }
793
774
  }
794
775
  async function createCollection(account, params, config) {
@@ -805,11 +786,11 @@ async function createCollection(account, params, config) {
805
786
  await provider.waitForTransaction(tx.transaction_hash);
806
787
  return { txHash: tx.transaction_hash };
807
788
  } catch (err) {
808
- throw new MedialaneError("Failed to create collection", err);
789
+ throw new MedialaneError("Failed to create collection", "TRANSACTION_FAILED", err);
809
790
  }
810
791
  }
811
792
  async function checkoutCart(account, items, config) {
812
- if (items.length === 0) throw new MedialaneError("Cart is empty");
793
+ if (items.length === 0) throw new MedialaneError("Cart is empty", "INVALID_PARAMS");
813
794
  const { contract, provider } = makeContract(config);
814
795
  const tokenTotals = /* @__PURE__ */ new Map();
815
796
  for (const item of items) {
@@ -856,8 +837,47 @@ async function checkoutCart(account, items, config) {
856
837
  await provider.waitForTransaction(tx.transaction_hash);
857
838
  return { txHash: tx.transaction_hash };
858
839
  } catch (err) {
859
- throw new MedialaneError("Cart checkout failed", err);
840
+ throw new MedialaneError("Cart checkout failed", "TRANSACTION_FAILED", err);
841
+ }
842
+ }
843
+
844
+ // src/config.ts
845
+ var MedialaneConfigSchema = z.object({
846
+ network: z.enum(SUPPORTED_NETWORKS).default("mainnet"),
847
+ rpcUrl: z.string().url().optional(),
848
+ backendUrl: z.string().url().optional(),
849
+ /** API key for authenticated /v1/* backend endpoints */
850
+ apiKey: z.string().optional(),
851
+ marketplaceContract: z.string().optional(),
852
+ collectionContract: z.string().optional(),
853
+ retryOptions: z.object({
854
+ maxAttempts: z.number().int().min(1).max(10).optional(),
855
+ baseDelayMs: z.number().int().min(0).optional(),
856
+ maxDelayMs: z.number().int().min(0).optional()
857
+ }).optional()
858
+ });
859
+ function resolveConfig(raw) {
860
+ const parsed = MedialaneConfigSchema.parse(raw);
861
+ const isMainnet = parsed.network === "mainnet";
862
+ const defaultMarketplace = isMainnet ? MARKETPLACE_CONTRACT_MAINNET : MARKETPLACE_CONTRACT_SEPOLIA;
863
+ const defaultCollection = isMainnet ? COLLECTION_CONTRACT_MAINNET : COLLECTION_CONTRACT_SEPOLIA;
864
+ const marketplaceContract = parsed.marketplaceContract ?? defaultMarketplace;
865
+ const collectionContract = parsed.collectionContract ?? defaultCollection;
866
+ if (!marketplaceContract || !collectionContract) {
867
+ throw new MedialaneError(
868
+ `Sepolia network is not yet supported: marketplace and collection contract addresses are not configured. Pass 'marketplaceContract' and 'collectionContract' explicitly in your MedialaneClient config.`,
869
+ "NETWORK_NOT_SUPPORTED"
870
+ );
860
871
  }
872
+ return {
873
+ network: parsed.network,
874
+ rpcUrl: parsed.rpcUrl ?? DEFAULT_RPC_URLS[parsed.network],
875
+ backendUrl: parsed.backendUrl,
876
+ apiKey: parsed.apiKey,
877
+ marketplaceContract,
878
+ collectionContract,
879
+ retryOptions: parsed.retryOptions
880
+ };
861
881
  }
862
882
 
863
883
  // src/marketplace/index.ts
@@ -909,18 +929,60 @@ function shortenAddress(address, chars = 4) {
909
929
  return `${norm.slice(0, chars + 2)}...${norm.slice(-chars)}`;
910
930
  }
911
931
 
932
+ // src/utils/retry.ts
933
+ var DEFAULT_MAX_ATTEMPTS = 3;
934
+ var DEFAULT_BASE_DELAY_MS = 300;
935
+ var DEFAULT_MAX_DELAY_MS = 5e3;
936
+ function sleep(ms) {
937
+ return new Promise((resolve) => setTimeout(resolve, ms));
938
+ }
939
+ async function withRetry(fn, opts) {
940
+ const maxAttempts = opts?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
941
+ const baseDelayMs = opts?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
942
+ const maxDelayMs = opts?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
943
+ let lastError;
944
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
945
+ try {
946
+ return await fn();
947
+ } catch (err) {
948
+ lastError = err;
949
+ if (err instanceof MedialaneApiError && err.status < 500) {
950
+ throw err;
951
+ }
952
+ const isRetryable = err instanceof MedialaneApiError && err.status >= 500 || err instanceof TypeError;
953
+ if (!isRetryable || attempt === maxAttempts - 1) {
954
+ throw err;
955
+ }
956
+ const jitter = Math.random() * baseDelayMs;
957
+ const delay = Math.min(baseDelayMs * Math.pow(2, attempt) + jitter, maxDelayMs);
958
+ await sleep(delay);
959
+ }
960
+ }
961
+ throw lastError;
962
+ }
963
+
912
964
  // src/api/client.ts
965
+ function deriveErrorCode(status) {
966
+ if (status === 404) return "TOKEN_NOT_FOUND";
967
+ if (status === 429) return "RATE_LIMITED";
968
+ if (status === 410) return "INTENT_EXPIRED";
969
+ if (status === 401 || status === 403) return "UNAUTHORIZED";
970
+ if (status === 400) return "INVALID_PARAMS";
971
+ return "UNKNOWN";
972
+ }
913
973
  var MedialaneApiError = class extends Error {
914
974
  constructor(status, message) {
915
975
  super(message);
916
976
  this.status = status;
917
977
  this.name = "MedialaneApiError";
978
+ this.code = deriveErrorCode(status);
918
979
  }
919
980
  };
920
981
  var ApiClient = class {
921
- constructor(baseUrl, apiKey) {
982
+ constructor(baseUrl, apiKey, retryOptions) {
922
983
  this.baseUrl = baseUrl;
923
984
  this.baseHeaders = apiKey ? { "x-api-key": apiKey } : {};
985
+ this.retryOptions = retryOptions;
924
986
  }
925
987
  async request(path, init) {
926
988
  const url = `${this.baseUrl.replace(/\/$/, "")}${path}`;
@@ -928,19 +990,23 @@ var ApiClient = class {
928
990
  if (!(init?.body instanceof FormData)) {
929
991
  headers["Content-Type"] = "application/json";
930
992
  }
931
- const res = await fetch(url, {
932
- ...init,
933
- headers: { ...headers, ...init?.headers }
934
- });
935
- if (!res.ok) {
936
- let message = res.statusText;
937
- try {
938
- const body = await res.json();
939
- if (body.error) message = body.error;
940
- } catch {
993
+ const res = await withRetry(async () => {
994
+ const response = await fetch(url, {
995
+ ...init,
996
+ headers: { ...headers, ...init?.headers }
997
+ });
998
+ if (!response.ok) {
999
+ const text = await response.text().catch(() => response.statusText);
1000
+ let message = text;
1001
+ try {
1002
+ const body = JSON.parse(text);
1003
+ if (body.error) message = body.error;
1004
+ } catch {
1005
+ }
1006
+ throw new MedialaneApiError(response.status, message);
941
1007
  }
942
- throw new MedialaneApiError(res.status, message);
943
- }
1008
+ return response;
1009
+ }, this.retryOptions);
944
1010
  return res.json();
945
1011
  }
946
1012
  get(path) {
@@ -998,9 +1064,10 @@ var ApiClient = class {
998
1064
  );
999
1065
  }
1000
1066
  // ─── Collections ───────────────────────────────────────────────────────────
1001
- getCollections(page = 1, limit = 20, isKnown) {
1067
+ getCollections(page = 1, limit = 20, isKnown, sort) {
1002
1068
  const params = new URLSearchParams({ page: String(page), limit: String(limit) });
1003
1069
  if (isKnown !== void 0) params.set("isKnown", String(isKnown));
1070
+ if (sort) params.set("sort", sort);
1004
1071
  return this.get(`/v1/collections?${params}`);
1005
1072
  }
1006
1073
  getCollectionsByOwner(owner, page = 1, limit = 50) {
@@ -1125,7 +1192,7 @@ var MedialaneClient = class {
1125
1192
  }
1126
1193
  });
1127
1194
  } else {
1128
- this.api = new ApiClient(this.config.backendUrl, this.config.apiKey);
1195
+ this.api = new ApiClient(this.config.backendUrl, this.config.apiKey, this.config.retryOptions);
1129
1196
  }
1130
1197
  }
1131
1198
  get network() {