@medialane/sdk 0.3.2 → 0.4.1
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/README.md +11 -0
- package/dist/index.cjs +281 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +126 -6
- package/dist/index.d.ts +126 -6
- package/dist/index.js +281 -141
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,10 +837,49 @@ 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);
|
|
860
841
|
}
|
|
861
842
|
}
|
|
862
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
|
+
);
|
|
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
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
863
883
|
// src/marketplace/index.ts
|
|
864
884
|
var MarketplaceModule = class {
|
|
865
885
|
constructor(config) {
|
|
@@ -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
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -1107,6 +1174,79 @@ var ApiClient = class {
|
|
|
1107
1174
|
`/v1/portal/webhooks/${id}`
|
|
1108
1175
|
);
|
|
1109
1176
|
}
|
|
1177
|
+
// ─── Collection Claims ──────────────────────────────────────────────────────
|
|
1178
|
+
/**
|
|
1179
|
+
* Path 1: On-chain auto claim. Sends both x-api-key (tenant auth) and
|
|
1180
|
+
* Authorization: Bearer (Clerk JWT) simultaneously.
|
|
1181
|
+
*/
|
|
1182
|
+
async claimCollection(contractAddress, walletAddress, clerkToken) {
|
|
1183
|
+
const url = `${this.baseUrl.replace(/\/$/, "")}/v1/collections/claim`;
|
|
1184
|
+
const res = await fetch(url, {
|
|
1185
|
+
method: "POST",
|
|
1186
|
+
headers: {
|
|
1187
|
+
"x-api-key": this.baseHeaders["x-api-key"] ?? "",
|
|
1188
|
+
"Content-Type": "application/json",
|
|
1189
|
+
"Authorization": `Bearer ${clerkToken}`
|
|
1190
|
+
},
|
|
1191
|
+
body: JSON.stringify({ contractAddress, walletAddress })
|
|
1192
|
+
});
|
|
1193
|
+
return res.json();
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Path 3: Manual off-chain claim request (email-based).
|
|
1197
|
+
*/
|
|
1198
|
+
requestCollectionClaim(params) {
|
|
1199
|
+
return this.request("/v1/collections/claim/request", {
|
|
1200
|
+
method: "POST",
|
|
1201
|
+
body: JSON.stringify(params)
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
// ─── Collection Profiles ────────────────────────────────────────────────────
|
|
1205
|
+
async getCollectionProfile(contractAddress) {
|
|
1206
|
+
const url = `${this.baseUrl.replace(/\/$/, "")}/v1/collections/${normalizeAddress(contractAddress)}/profile`;
|
|
1207
|
+
const res = await fetch(url, { headers: this.baseHeaders });
|
|
1208
|
+
if (res.status === 404) return null;
|
|
1209
|
+
return res.json();
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Update collection profile. Requires Clerk JWT for ownership check.
|
|
1213
|
+
*/
|
|
1214
|
+
async updateCollectionProfile(contractAddress, data, clerkToken) {
|
|
1215
|
+
const url = `${this.baseUrl.replace(/\/$/, "")}/v1/collections/${normalizeAddress(contractAddress)}/profile`;
|
|
1216
|
+
const res = await fetch(url, {
|
|
1217
|
+
method: "PATCH",
|
|
1218
|
+
headers: {
|
|
1219
|
+
"x-api-key": this.baseHeaders["x-api-key"] ?? "",
|
|
1220
|
+
"Content-Type": "application/json",
|
|
1221
|
+
"Authorization": `Bearer ${clerkToken}`
|
|
1222
|
+
},
|
|
1223
|
+
body: JSON.stringify(data)
|
|
1224
|
+
});
|
|
1225
|
+
return res.json();
|
|
1226
|
+
}
|
|
1227
|
+
// ─── Creator Profiles ───────────────────────────────────────────────────────
|
|
1228
|
+
async getCreatorProfile(walletAddress) {
|
|
1229
|
+
const url = `${this.baseUrl.replace(/\/$/, "")}/v1/creators/${normalizeAddress(walletAddress)}/profile`;
|
|
1230
|
+
const res = await fetch(url, { headers: this.baseHeaders });
|
|
1231
|
+
if (res.status === 404) return null;
|
|
1232
|
+
return res.json();
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Update creator profile. Requires Clerk JWT; wallet must match authenticated user.
|
|
1236
|
+
*/
|
|
1237
|
+
async updateCreatorProfile(walletAddress, data, clerkToken) {
|
|
1238
|
+
const url = `${this.baseUrl.replace(/\/$/, "")}/v1/creators/${normalizeAddress(walletAddress)}/profile`;
|
|
1239
|
+
const res = await fetch(url, {
|
|
1240
|
+
method: "PATCH",
|
|
1241
|
+
headers: {
|
|
1242
|
+
"x-api-key": this.baseHeaders["x-api-key"] ?? "",
|
|
1243
|
+
"Content-Type": "application/json",
|
|
1244
|
+
"Authorization": `Bearer ${clerkToken}`
|
|
1245
|
+
},
|
|
1246
|
+
body: JSON.stringify(data)
|
|
1247
|
+
});
|
|
1248
|
+
return res.json();
|
|
1249
|
+
}
|
|
1110
1250
|
};
|
|
1111
1251
|
|
|
1112
1252
|
// src/client.ts
|
|
@@ -1125,7 +1265,7 @@ var MedialaneClient = class {
|
|
|
1125
1265
|
}
|
|
1126
1266
|
});
|
|
1127
1267
|
} else {
|
|
1128
|
-
this.api = new ApiClient(this.config.backendUrl, this.config.apiKey);
|
|
1268
|
+
this.api = new ApiClient(this.config.backendUrl, this.config.apiKey, this.config.retryOptions);
|
|
1129
1269
|
}
|
|
1130
1270
|
}
|
|
1131
1271
|
get network() {
|