@medialane/sdk 0.85.9 → 0.85.11
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 +81 -80
- package/dist/index.cjs +177 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +172 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Framework-agnostic TypeScript SDK for the Medialane IP marketplace on Starknet**
|
|
6
6
|
|
|
7
|
-
The Medialane SDK provides a unified interface for interacting with the Medialane marketplace
|
|
7
|
+
The Medialane SDK provides a unified interface for interacting with the Medialane marketplace: both **on-chain operations** (create listings, make offers, fulfill orders, mint IP assets) and **REST API access** (search tokens, manage orders, upload metadata to IPFS). Built for [medialane.io](https://medialane.io), [starknet.medialane.io](https://starknet.medialane.io), [portal.medialane.io](https://portal.medialane.io), and `media-wallet`.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -30,9 +30,9 @@ The Medialane SDK provides a unified interface for interacting with the Medialan
|
|
|
30
30
|
- ERC-1155 multi-holder ownership via `token.balances`
|
|
31
31
|
|
|
32
32
|
**IP Metadata Types**
|
|
33
|
-
- `IpAttribute
|
|
34
|
-
- `IpNftMetadata
|
|
35
|
-
- `ApiTokenMetadata
|
|
33
|
+
- `IpAttribute`: typed OpenSea ERC-721 attribute
|
|
34
|
+
- `IpNftMetadata`: full IPFS metadata shape with licensing fields
|
|
35
|
+
- `ApiTokenMetadata`: indexed token metadata with all licensing attributes
|
|
36
36
|
- Berne Convention-compatible licensing data model
|
|
37
37
|
|
|
38
38
|
**Developer-Friendly**
|
|
@@ -75,11 +75,11 @@ const client = new MedialaneClient({
|
|
|
75
75
|
|
|
76
76
|
## Marketplace Operations (On-Chain)
|
|
77
77
|
|
|
78
|
-
All methods require a `starknet.js` `AccountInterface`. SNIP-12 signing and `waitForTransaction` are handled automatically. Fulfilment is **unsigned
|
|
78
|
+
All methods require a `starknet.js` `AccountInterface`. SNIP-12 signing and `waitForTransaction` are handled automatically. Fulfilment is **unsigned**: the caller is the fulfiller, so there is no `fulfiller`/`offerer` field to pass; cancellation still signs, but without a nonce (a per-offerer `counter` replaces it, see `incrementCounter`).
|
|
79
79
|
|
|
80
80
|
Two marketplace modules are available:
|
|
81
|
-
- `client.marketplace
|
|
82
|
-
- `client.marketplace1155
|
|
81
|
+
- `client.marketplace`: ERC-721 marketplace (`Medialane721`)
|
|
82
|
+
- `client.marketplace1155`: ERC-1155 marketplace (`Medialane1155`)
|
|
83
83
|
|
|
84
84
|
### Create a Listing (ERC-721)
|
|
85
85
|
|
|
@@ -141,7 +141,7 @@ const result = await client.marketplace.cancelOrder(account, {
|
|
|
141
141
|
### Bulk-Cancel (Invalidate All Open Orders)
|
|
142
142
|
|
|
143
143
|
```typescript
|
|
144
|
-
// Bumps the caller's counter
|
|
144
|
+
// Bumps the caller's counter: every previously-registered order becomes unfulfillable.
|
|
145
145
|
await client.marketplace.incrementCounter(account);
|
|
146
146
|
```
|
|
147
147
|
|
|
@@ -170,7 +170,7 @@ const result = await client.marketplace.createCollection(account, {
|
|
|
170
170
|
|
|
171
171
|
## ERC-1155 Marketplace (Medialane1155)
|
|
172
172
|
|
|
173
|
-
For IP assets from ERC-1155 collections (e.g. IP-Programmable-ERC1155-Collections). Contract address:
|
|
173
|
+
For IP assets from ERC-1155 collections (e.g. IP-Programmable-ERC1155-Collections). Contract address: read `getCoordinates("STARKNET").marketplace1155` from `src/chains.ts`, the single source of truth across redeploys.
|
|
174
174
|
|
|
175
175
|
### Create an ERC-1155 Listing
|
|
176
176
|
|
|
@@ -213,7 +213,7 @@ const result = await client.marketplace1155.cancelOrder(account, {
|
|
|
213
213
|
### SNIP-12 Typed Data Builders (custodial-wallet / custom flows)
|
|
214
214
|
|
|
215
215
|
Listing/offer and cancellation are signed; fulfilment is an **unsigned** call (the buyer is
|
|
216
|
-
the fulfiller, since v0.26.0)
|
|
216
|
+
the fulfiller, since v0.26.0): there is no fulfillment typed-data builder.
|
|
217
217
|
|
|
218
218
|
```typescript
|
|
219
219
|
import { build1155OrderTypedData, build1155CancellationTypedData } from "@medialane/sdk";
|
|
@@ -252,7 +252,7 @@ const history = await client.api.getTokenHistory(contract, tokenId);
|
|
|
252
252
|
|
|
253
253
|
### ERC-1155 Ownership
|
|
254
254
|
|
|
255
|
-
For ERC-1155 tokens, a single token ID can be held by many wallets simultaneously
|
|
255
|
+
For ERC-1155 tokens, a single token ID can be held by many wallets simultaneously; read ownership from `token.balances`:
|
|
256
256
|
|
|
257
257
|
```typescript
|
|
258
258
|
import type { ApiTokenBalance } from "@medialane/sdk";
|
|
@@ -274,12 +274,12 @@ token.balances?.forEach((b: ApiTokenBalance) => {
|
|
|
274
274
|
});
|
|
275
275
|
```
|
|
276
276
|
|
|
277
|
-
`token.owner` is deprecated and always `null` post-migration. `token.balances` is only populated on single-token fetches (`getToken`)
|
|
277
|
+
`token.owner` is deprecated and always `null` post-migration. `token.balances` is only populated on single-token fetches (`getToken`): it is `null` on list responses.
|
|
278
278
|
|
|
279
279
|
### Query Collections
|
|
280
280
|
|
|
281
281
|
```typescript
|
|
282
|
-
// All collections
|
|
282
|
+
// All collections: newest first by default
|
|
283
283
|
const collections = await client.api.getCollections();
|
|
284
284
|
|
|
285
285
|
// With sort and pagination
|
|
@@ -295,9 +295,9 @@ const tokens = await client.api.getCollectionTokens(contract);
|
|
|
295
295
|
|
|
296
296
|
```typescript
|
|
297
297
|
const results = await client.api.search("landscape painting", 10);
|
|
298
|
-
// results.data.tokens
|
|
299
|
-
// results.data.collections
|
|
300
|
-
// results.data.creators
|
|
298
|
+
// results.data.tokens: matching tokens
|
|
299
|
+
// results.data.collections: matching collections
|
|
300
|
+
// results.data.creators: matching creator profiles (v0.4.5)
|
|
301
301
|
```
|
|
302
302
|
|
|
303
303
|
### Activities
|
|
@@ -351,7 +351,7 @@ const signature = await account.signMessage(intent.data.typedData);
|
|
|
351
351
|
await client.api.submitIntentSignature(intent.data.id, toSignatureArray(signature));
|
|
352
352
|
```
|
|
353
353
|
|
|
354
|
-
Mint and collection intents are pre-signed
|
|
354
|
+
Mint and collection intents are pre-signed: no signature step needed:
|
|
355
355
|
|
|
356
356
|
```typescript
|
|
357
357
|
const mintIntent = await client.api.createMintIntent({
|
|
@@ -393,7 +393,7 @@ const metadata: IpNftMetadata = {
|
|
|
393
393
|
],
|
|
394
394
|
};
|
|
395
395
|
|
|
396
|
-
// Token from the API
|
|
396
|
+
// Token from the API: includes indexed licensing fields for fast access
|
|
397
397
|
const token = await client.api.getToken(contract, tokenId);
|
|
398
398
|
token.data.metadata.licenseType; // "CC BY-NC-SA"
|
|
399
399
|
token.data.metadata.commercialUse; // "No"
|
|
@@ -473,8 +473,8 @@ try {
|
|
|
473
473
|
|---|---|---|---|
|
|
474
474
|
| `chain` | `Chain` (`"STARKNET" \| "ETHEREUM" \| "SOLANA" \| "BASE" \| "BITCOIN"`) | `"STARKNET"` | The chain this client is scoped to. Coordinates resolve from the `coordinates[chain]` registry (`chains.ts`). Replaces `network` (v0.37.0). |
|
|
475
475
|
| `rpcUrl` | `string` | the chain's registry `rpcUrl` | JSON-RPC URL override |
|
|
476
|
-
| `backendUrl` | `string` |
|
|
477
|
-
| `apiKey` | `string` |
|
|
476
|
+
| `backendUrl` | `string` | (none) | Medialane API base URL (required for `.api.*`) |
|
|
477
|
+
| `apiKey` | `string` | (none) | API key from [Medialane Portal](https://portal.medialane.io) |
|
|
478
478
|
| `marketplace721Contract` | `string` | Mainnet default | ERC-721 marketplace protocol override |
|
|
479
479
|
| `marketplaceContract` | `string` | Mainnet default | Legacy alias for `marketplace721Contract` |
|
|
480
480
|
| `marketplace1155Contract` | `string` | Mainnet default | ERC-1155 marketplace protocol override |
|
|
@@ -511,9 +511,9 @@ bun run typecheck # tsc --noEmit
|
|
|
511
511
|
```
|
|
512
512
|
|
|
513
513
|
Built with:
|
|
514
|
-
- **tsup
|
|
515
|
-
- **TypeScript
|
|
516
|
-
- **Zod
|
|
514
|
+
- **tsup**: dual ESM/CJS bundling
|
|
515
|
+
- **TypeScript**: full type safety
|
|
516
|
+
- **Zod**: runtime config validation
|
|
517
517
|
- Peer dep: `starknet >= 6.0.0`
|
|
518
518
|
|
|
519
519
|
---
|
|
@@ -522,125 +522,125 @@ Built with:
|
|
|
522
522
|
|
|
523
523
|
> Full history in [CHANGELOG.md](./CHANGELOG.md). Highlights below.
|
|
524
524
|
|
|
525
|
-
### v0.37.0
|
|
525
|
+
### v0.37.0: multichain readiness (BREAKING)
|
|
526
526
|
- **Chain is a first-class axis.** New `chains.ts` `coordinates[chain]` registry is the single source of per-chain service coordinates (`CHAINS`, `getCoordinates`, `DEFAULT_CHAIN`, `Chain`, `ChainCoordinates`); the flat `*_MAINNET` constants derive from it.
|
|
527
|
-
- **`MedialaneConfig.chain` replaces `network
|
|
528
|
-
- **`ServiceDefinition.onchain` is per-chain
|
|
529
|
-
- **`normalizeAddress(chain, address)
|
|
530
|
-
- **Removed** `SUPPORTED_NETWORKS`, `DEFAULT_RPC_URL`, `Network` (mainnet-only
|
|
527
|
+
- **`MedialaneConfig.chain` replaces `network`**: the client is chain-scoped; `client.network` getter → `client.chain`.
|
|
528
|
+
- **`ServiceDefinition.onchain` is per-chain**: `Partial<Record<Chain, …>>`; read `service.onchain?.STARKNET?.factoryAddress`.
|
|
529
|
+
- **`normalizeAddress(chain, address)`**: per-chain codec (Starknet pad / EVM EIP-55 / Solana base58; Bitcoin not yet implemented).
|
|
530
|
+
- **Removed** `SUPPORTED_NETWORKS`, `DEFAULT_RPC_URL`, `Network` (mainnet-only: coordinates key by chain alone). `getChainId(config)` throws for non-Starknet.
|
|
531
531
|
|
|
532
532
|
### v0.6.7
|
|
533
|
-
- **`CollectionRegistryABI`** exported from `@medialane/sdk
|
|
533
|
+
- **`CollectionRegistryABI`** exported from `@medialane/sdk`: minimal ABI covering `list_user_collections` and `get_collection` on the collection registry contract. Eliminates duplicated inline ABI definitions in consuming apps.
|
|
534
534
|
|
|
535
535
|
### v0.6.6
|
|
536
536
|
- **`COLLECTION_CONTRACT_MAINNET`** updated to audited v2 contract address `0x05c49ee5d3208a2c2e150fdd0c247d1195ed9ab54fa2d5dea7a633f39e4b205b`
|
|
537
537
|
|
|
538
538
|
### v0.6.5
|
|
539
|
-
- **ERC-1155 support
|
|
540
|
-
- **`ApiTokenBalance`** type
|
|
541
|
-
- **`ApiToken.owner`** deprecated
|
|
542
|
-
- **`ApiCollection.standard
|
|
543
|
-
- **`totalSupply` fix
|
|
539
|
+
- **ERC-1155 support**: `ApiToken.balances: ApiTokenBalance[] | null` replaces the single `owner` field for ownership checks
|
|
540
|
+
- **`ApiTokenBalance`** type: `{ owner: string; amount: string }`: each entry represents one holder and their quantity
|
|
541
|
+
- **`ApiToken.owner`** deprecated: always `null` after the ERC-1155 migration; use `balances` instead
|
|
542
|
+
- **`ApiCollection.standard`**: `"ERC721" | "ERC1155" | "UNKNOWN"` detected via ERC-165 `supportsInterface`
|
|
543
|
+
- **`totalSupply` fix**: ERC-1155 collections now report `SUM(holder amounts)` for an accurate circulating total
|
|
544
544
|
|
|
545
545
|
### v0.6.1
|
|
546
|
-
- **Collection Drop
|
|
547
|
-
- **`client.api.getDropCollections(opts?)
|
|
548
|
-
- **`client.api.getDropMintStatus(collection, wallet)
|
|
546
|
+
- **Collection Drop**: new `DropService` (`client.services.drop`) with full on-chain drop management: `claim`, `adminMint`, `setClaimConditions`, `setAllowlistEnabled`, `addToAllowlist`, `batchAddToAllowlist`, `setPaused`, `withdrawPayments`, `createDrop`
|
|
547
|
+
- **`client.api.getDropCollections(opts?)`**: list all `COLLECTION_DROP` collections
|
|
548
|
+
- **`client.api.getDropMintStatus(collection, wallet)`**: returns `{ mintedByWallet, totalMinted }`
|
|
549
549
|
- **`DropMintStatus`**, **`ClaimConditions`**, **`CreateDropParams`** types exported
|
|
550
550
|
- **`DropCollectionABI`** and **`DropFactoryABI`** exported from `@medialane/sdk`
|
|
551
551
|
- **`DROP_FACTORY_CONTRACT_MAINNET`** and **`DROP_COLLECTION_CLASS_HASH_MAINNET`** constants exported
|
|
552
552
|
- **`CollectionSource`** union extended with `"COLLECTION_DROP"`
|
|
553
553
|
|
|
554
554
|
### v0.6.0
|
|
555
|
-
- **POP Protocol
|
|
555
|
+
- **POP Protocol**: `PopService` (`client.services.pop`): `claim`, `adminMint`, `addToAllowlist`, `batchAddToAllowlist`, `removeFromAllowlist`, `setTokenUri`, `setPaused`, `createCollection`
|
|
556
556
|
- **`client.api.getPopCollections(opts?)`** and **`client.api.getPopEligibility(collection, wallet)`**
|
|
557
557
|
- **`POPCollectionABI`** and **`POPFactoryABI`** exported
|
|
558
558
|
- **`POP_FACTORY_CONTRACT_MAINNET`** and **`POP_COLLECTION_CLASS_HASH_MAINNET`** constants exported
|
|
559
559
|
|
|
560
560
|
### v0.5.7
|
|
561
|
-
- **`ApiCollectionProfile.hasGatedContent: boolean
|
|
562
|
-
- **`ApiCollectionProfile.gatedContentTitle: string | null
|
|
561
|
+
- **`ApiCollectionProfile.hasGatedContent: boolean`**: whether the collection has token-gated content configured
|
|
562
|
+
- **`ApiCollectionProfile.gatedContentTitle: string | null`**: public title of gated content (shown to all users; URL is accessible to holders only via the backend gated-content endpoint)
|
|
563
563
|
|
|
564
564
|
### v0.5.5
|
|
565
|
-
- **`extendRemixOffer(id, days, siwsToken)
|
|
566
|
-
- **`ApiRemixOfferPrice`** type
|
|
565
|
+
- **`extendRemixOffer(id, days, siwsToken)`**: requester extends expiry of a PENDING/AUTO_PENDING remix offer by 1–30 days (`POST /v1/remix-offers/:id/extend`)
|
|
566
|
+
- **`ApiRemixOfferPrice`** type: `{ raw, formatted, currency, decimals }` replaces flat `proposedPrice`/`proposedCurrency` fields on `ApiRemixOffer.price` (visible to participants only)
|
|
567
567
|
|
|
568
568
|
### v0.5.4
|
|
569
|
-
- **`ApiRemixOffer.price`** shape introduced
|
|
569
|
+
- **`ApiRemixOffer.price`** shape introduced: backend now serializes price as a structured object (`raw`, `formatted`, `currency`, `decimals`), replacing raw wei strings
|
|
570
570
|
|
|
571
571
|
### v0.5.3
|
|
572
|
-
- **`getTokenComments(contract, tokenId, opts?)
|
|
573
|
-
- **`ApiComment`** type
|
|
572
|
+
- **`getTokenComments(contract, tokenId, opts?)`**: fetch on-chain NFT comments for a token (`GET /v1/tokens/:contract/:tokenId/comments`)
|
|
573
|
+
- **`ApiComment`** type: `{ id, author, content, txHash, blockNumber, blockTimestamp, isHidden, createdAt }`
|
|
574
574
|
|
|
575
575
|
### v0.5.0
|
|
576
|
-
- **Counter-offer support
|
|
576
|
+
- **Counter-offer support**: `createCounterOfferIntent(params, siwsToken)`, `getCounterOffers(query)`, `ApiCounterOffersQuery`, `CreateCounterOfferIntentParams`
|
|
577
577
|
- **`OrderStatus`** extended with `"COUNTER_OFFERED"`; **`IntentType`** with `"COUNTER_OFFER"`
|
|
578
578
|
- **`ApiOrder`** extended: `parentOrderHash?: string | null`, `counterOfferMessage?: string | null`
|
|
579
|
-
- **Remix licensing
|
|
580
|
-
- `submitRemixOffer(params, siwsToken)
|
|
581
|
-
- `submitAutoRemixOffer(params, siwsToken)
|
|
582
|
-
- `confirmSelfRemix(params, siwsToken)
|
|
583
|
-
- `getRemixOffers(query, siwsToken)
|
|
584
|
-
- `getRemixOffer(id, siwsToken?)
|
|
585
|
-
- `confirmRemixOffer(id, params, siwsToken)
|
|
586
|
-
- `rejectRemixOffer(id, siwsToken)
|
|
587
|
-
- `getTokenRemixes(contract, tokenId, opts?)
|
|
588
|
-
- **New types
|
|
579
|
+
- **Remix licensing**: full set of remix offer methods and types:
|
|
580
|
+
- `submitRemixOffer(params, siwsToken)`: custom offer
|
|
581
|
+
- `submitAutoRemixOffer(params, siwsToken)`: auto offer for open-license tokens
|
|
582
|
+
- `confirmSelfRemix(params, siwsToken)`: record owner self-remix
|
|
583
|
+
- `getRemixOffers(query, siwsToken)`: list by role
|
|
584
|
+
- `getRemixOffer(id, siwsToken?)`: single offer
|
|
585
|
+
- `confirmRemixOffer(id, params, siwsToken)`: creator approves
|
|
586
|
+
- `rejectRemixOffer(id, siwsToken)`: creator rejects
|
|
587
|
+
- `getTokenRemixes(contract, tokenId, opts?)`: public remix list
|
|
588
|
+
- **New types**: `RemixOfferStatus`, `ApiRemixOffer`, `ApiPublicRemix`, `OPEN_LICENSES`, `OpenLicense`, `CreateRemixOfferParams`, `AutoRemixOfferParams`, `ConfirmSelfRemixParams`, `ConfirmRemixOfferParams`, `ApiRemixOffersQuery`
|
|
589
589
|
|
|
590
590
|
### v0.4.8
|
|
591
591
|
- **`ApiComment`** type + **`getTokenComments`** (patch release, backported into v0.5.3)
|
|
592
592
|
|
|
593
593
|
### v0.4.7
|
|
594
|
-
- **`IPType`** union type exported
|
|
594
|
+
- **`IPType`** union type exported: `"Audio" | "Art" | "Documents" | "NFT" | "Video" | "Photography" | "Patents" | "Posts" | "Publications" | "RWA" | "Software" | "Custom"`
|
|
595
595
|
|
|
596
596
|
### v0.4.6
|
|
597
597
|
- **`ApiUserWallet`** type + `upsertMyWallet(siwsToken)` / `getMyWallet(siwsToken)` for wallet registration fallback (`POST/GET /v1/users/me`)
|
|
598
598
|
|
|
599
599
|
### v0.4.5
|
|
600
|
-
- **`ApiSearchCreatorResult`** type + `ApiSearchResult.creators
|
|
600
|
+
- **`ApiSearchCreatorResult`** type + `ApiSearchResult.creators`: creator profiles now included in search results
|
|
601
601
|
|
|
602
602
|
### v0.4.4
|
|
603
|
-
- **`ApiCreatorListResult`** + `getCreators(opts?)
|
|
603
|
+
- **`ApiCreatorListResult`** + `getCreators(opts?)`: list creators with search/pagination via `GET /v1/creators`
|
|
604
604
|
|
|
605
605
|
### v0.4.3
|
|
606
|
-
- **`ApiCreatorProfile.username`** field + `getCreatorByUsername(username)
|
|
606
|
+
- **`ApiCreatorProfile.username`** field + `getCreatorByUsername(username)`: resolve username slug to creator profile
|
|
607
607
|
|
|
608
608
|
### v0.4.2
|
|
609
609
|
- **WBTC** added to `SUPPORTED_TOKENS` (`0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac`, 8 decimals)
|
|
610
|
-
- **`listable` field** on every `SUPPORTED_TOKENS` entry
|
|
611
|
-
- **`getListableTokens()
|
|
612
|
-
- **ETH** promoted to `listable: true
|
|
613
|
-
- **USDC.e removed
|
|
610
|
+
- **`listable` field** on every `SUPPORTED_TOKENS` entry: controls whether a token appears in listing/offer dialogs vs filter-only
|
|
611
|
+
- **`getListableTokens()`**: returns tokens filtered to `listable: true`; exported from package root
|
|
612
|
+
- **ETH** promoted to `listable: true`: now available in listing and offer dialogs
|
|
613
|
+
- **USDC.e removed**: bridged USDC (`0x053c91...`) removed entirely; only Circle-native USDC remains, to avoid user confusion
|
|
614
614
|
|
|
615
615
|
### v0.4.1
|
|
616
|
-
- **Collection claims
|
|
617
|
-
- **Collection profiles
|
|
618
|
-
- **Creator profiles
|
|
619
|
-
- **New types
|
|
616
|
+
- **Collection claims**: `claimCollection(contractAddress, walletAddress, siwsToken)` for on-chain ownership verification; `requestCollectionClaim({ contractAddress, walletAddress?, email, notes? })` for manual review
|
|
617
|
+
- **Collection profiles**: `getCollectionProfile(contractAddress)` and `updateCollectionProfile(contractAddress, data, siwsToken)` for enriched display metadata (displayName, description, image, bannerImage, social links)
|
|
618
|
+
- **Creator profiles**: `getCreatorProfile(walletAddress)` and `updateCreatorProfile(walletAddress, data, siwsToken)` for creator display metadata
|
|
619
|
+
- **New types**: `ApiCollectionClaim`, `ApiAdminCollectionClaim`, `ApiCollectionProfile`, `ApiCreatorProfile`
|
|
620
620
|
- **`ApiCollection`** extended with `source` (`"MEDIALANE_REGISTRY" | "EXTERNAL" | "PARTNERSHIP" | "IP_TICKET" | "IP_CLUB" | "GAME"`) and `claimedBy: string | null`
|
|
621
621
|
- `profile?: ApiCollectionProfile | null` optionally embedded on `ApiCollection` when `?include=profile`
|
|
622
622
|
|
|
623
623
|
### v0.4.0
|
|
624
|
-
- **Typed error codes
|
|
625
|
-
- **Automatic retry
|
|
624
|
+
- **Typed error codes**: `MedialaneError` and `MedialaneApiError` now expose a `.code: MedialaneErrorCode` property (`"TOKEN_NOT_FOUND"` | `"RATE_LIMITED"` | `"INTENT_EXPIRED"` | `"UNAUTHORIZED"` | `"INVALID_PARAMS"` | `"NETWORK_NOT_SUPPORTED"` | `"UNKNOWN"`)
|
|
625
|
+
- **Automatic retry**: all API requests retry up to 3 times with exponential backoff (300ms base, 5s cap) on transient failures. Configure via `retryOptions` in `MedialaneConfig`
|
|
626
626
|
- **`RetryOptions`** type exported from index
|
|
627
627
|
- **`CollectionSort`** named union type exported (`"recent" | "supply" | "floor" | "volume" | "name"`)
|
|
628
|
-
- **Sepolia guard
|
|
628
|
+
- **Sepolia guard**: constructing a client with `network: "sepolia"` and no explicit contract addresses now throws `NETWORK_NOT_SUPPORTED` immediately
|
|
629
629
|
|
|
630
630
|
### v0.3.3
|
|
631
|
-
- `getCollections(page?, limit?, isKnown?, sort?)
|
|
632
|
-
- Default sort changed from `totalSupply DESC` to `createdAt DESC` (newest first)
|
|
631
|
+
- `getCollections(page?, limit?, isKnown?, sort?)`: added `sort` parameter: `"recent"` (default) | `"supply"` | `"floor"` | `"volume"` | `"name"`
|
|
632
|
+
- Default sort changed from `totalSupply DESC` to `createdAt DESC` (newest first): matches backend default
|
|
633
633
|
|
|
634
634
|
### v0.3.1
|
|
635
|
-
- `ApiCollection.collectionId: string | null
|
|
635
|
+
- `ApiCollection.collectionId: string | null`: on-chain registry numeric ID (decimal string). Required for `createMintIntent`. Populated for collections indexed after 2026-03-09.
|
|
636
636
|
|
|
637
637
|
### v0.3.0
|
|
638
|
-
- `normalizeAddress()` applied internally before all API calls
|
|
639
|
-
- `ApiCollection.owner: string | null
|
|
640
|
-
- `getCollectionsByOwner(owner)
|
|
638
|
+
- `normalizeAddress()` applied internally before all API calls: callers no longer need to normalize Starknet addresses
|
|
639
|
+
- `ApiCollection.owner: string | null`: populated from intent typedData or on-chain `owner()` call
|
|
640
|
+
- `getCollectionsByOwner(owner)`: fetch collections by wallet address via `GET /v1/collections?owner=`
|
|
641
641
|
|
|
642
642
|
### v0.2.6
|
|
643
|
-
- `ApiOrder.token: ApiOrderTokenMeta | null
|
|
643
|
+
- `ApiOrder.token: ApiOrderTokenMeta | null`: token name/image/description embedded on orders (batchTokenMeta); no per-row `getToken` calls needed
|
|
644
644
|
|
|
645
645
|
### v0.2.0
|
|
646
646
|
- `IpAttribute` and `IpNftMetadata` interfaces for IP metadata
|
|
@@ -649,14 +649,15 @@ Built with:
|
|
|
649
649
|
- Added `USDC.e` (bridged USDC via Starkgate) to `SUPPORTED_TOKENS`
|
|
650
650
|
|
|
651
651
|
### v0.1.0
|
|
652
|
-
- Initial release
|
|
652
|
+
- Initial release: orders, tokens, collections, activities, intents, metadata, portal
|
|
653
653
|
|
|
654
654
|
---
|
|
655
655
|
|
|
656
656
|
## Links
|
|
657
657
|
|
|
658
658
|
- **Marketplace**: [medialane.io](https://medialane.io)
|
|
659
|
-
- **
|
|
659
|
+
- **Starknet App**: [starknet.medialane.io](https://starknet.medialane.io)
|
|
660
|
+
- **Developer Portal**: [portal.medialane.io](https://portal.medialane.io)
|
|
660
661
|
- **npm**: [npmjs.com/package/@medialane/sdk](https://www.npmjs.com/package/@medialane/sdk)
|
|
661
662
|
- **GitHub**: [github.com/medialane-io](https://github.com/medialane-io)
|
|
662
663
|
|
package/dist/index.cjs
CHANGED
|
@@ -1281,6 +1281,151 @@ function createFailoverFetch(urls, options = {}) {
|
|
|
1281
1281
|
return failover;
|
|
1282
1282
|
}
|
|
1283
1283
|
|
|
1284
|
+
// src/server/rpc-proxy.ts
|
|
1285
|
+
var DEFAULT_STARKNET_RPC_METHODS = [
|
|
1286
|
+
"starknet_call",
|
|
1287
|
+
"starknet_addInvokeTransaction",
|
|
1288
|
+
"starknet_getTransactionReceipt",
|
|
1289
|
+
"starknet_getTransactionStatus",
|
|
1290
|
+
"starknet_getTransactionByHash",
|
|
1291
|
+
"starknet_getTransaction",
|
|
1292
|
+
"starknet_getBlockWithReceipts",
|
|
1293
|
+
"starknet_estimateFee",
|
|
1294
|
+
"starknet_getNonce",
|
|
1295
|
+
"starknet_simulateTransactions",
|
|
1296
|
+
"starknet_specVersion",
|
|
1297
|
+
"starknet_chainId",
|
|
1298
|
+
"starknet_blockNumber",
|
|
1299
|
+
"starknet_blockHashAndNumber",
|
|
1300
|
+
"starknet_getClassAt",
|
|
1301
|
+
"starknet_getClass",
|
|
1302
|
+
"starknet_getClassHashAt",
|
|
1303
|
+
"starknet_getStorageAt",
|
|
1304
|
+
"starknet_getBlockWithTxHashes",
|
|
1305
|
+
"starknet_getBlockWithTxs",
|
|
1306
|
+
"starknet_getEvents"
|
|
1307
|
+
];
|
|
1308
|
+
function isAllowedMethod(body, allowed) {
|
|
1309
|
+
if (Array.isArray(body)) {
|
|
1310
|
+
return body.every((item) => isAllowedMethod(item, allowed));
|
|
1311
|
+
}
|
|
1312
|
+
if (body && typeof body === "object") {
|
|
1313
|
+
const method = body.method;
|
|
1314
|
+
return typeof method === "string" && allowed.has(method);
|
|
1315
|
+
}
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
function extractMethod(body) {
|
|
1319
|
+
if (Array.isArray(body)) return "batch";
|
|
1320
|
+
if (body && typeof body === "object") {
|
|
1321
|
+
const method = body.method;
|
|
1322
|
+
if (typeof method === "string") return method;
|
|
1323
|
+
}
|
|
1324
|
+
return "unknown";
|
|
1325
|
+
}
|
|
1326
|
+
function isSameOrigin(req) {
|
|
1327
|
+
const origin = req.headers.get("origin");
|
|
1328
|
+
if (!origin) return true;
|
|
1329
|
+
const host = req.headers.get("host");
|
|
1330
|
+
try {
|
|
1331
|
+
return new URL(origin).host === host;
|
|
1332
|
+
} catch {
|
|
1333
|
+
return false;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
function rpcError(code, message, status = 200, id = null) {
|
|
1337
|
+
return Response.json({ jsonrpc: "2.0", error: { code, message }, id }, { status });
|
|
1338
|
+
}
|
|
1339
|
+
async function billRpcCall(backendUrl, apiKey, method) {
|
|
1340
|
+
if (!apiKey) {
|
|
1341
|
+
console.error("[rpc-proxy] no API key configured \u2014 refusing to bill/forward");
|
|
1342
|
+
return false;
|
|
1343
|
+
}
|
|
1344
|
+
try {
|
|
1345
|
+
const res = await fetch(`${backendUrl.replace(/\/$/, "")}/v1/rpc/meter`, {
|
|
1346
|
+
method: "POST",
|
|
1347
|
+
headers: { "Content-Type": "application/json", "x-api-key": apiKey },
|
|
1348
|
+
body: JSON.stringify({ method })
|
|
1349
|
+
});
|
|
1350
|
+
return res.ok;
|
|
1351
|
+
} catch (err) {
|
|
1352
|
+
console.error("[rpc-proxy] billing call failed", { err: err instanceof Error ? err.message : String(err) });
|
|
1353
|
+
return false;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
function createRpcProxyHandler(config) {
|
|
1357
|
+
const allowed = new Set(config.allowedMethods ?? DEFAULT_STARKNET_RPC_METHODS);
|
|
1358
|
+
return async function handleRpcProxy(req) {
|
|
1359
|
+
if (!isSameOrigin(req)) {
|
|
1360
|
+
return rpcError(-32600, "Cross-origin requests are not allowed", 403);
|
|
1361
|
+
}
|
|
1362
|
+
const ip = req.headers.get("x-forwarded-for")?.split(",")[0].trim() ?? "unknown";
|
|
1363
|
+
if (!config.checkRateLimit(ip)) {
|
|
1364
|
+
return rpcError(-32005, "Too many requests", 429);
|
|
1365
|
+
}
|
|
1366
|
+
let body;
|
|
1367
|
+
try {
|
|
1368
|
+
body = await req.json();
|
|
1369
|
+
} catch {
|
|
1370
|
+
return rpcError(-32700, "Parse error");
|
|
1371
|
+
}
|
|
1372
|
+
if (!isAllowedMethod(body, allowed)) {
|
|
1373
|
+
const method2 = !Array.isArray(body) && body && typeof body === "object" ? String(body.method ?? "<unknown>") : "<batch or invalid>";
|
|
1374
|
+
return rpcError(-32601, `Method not allowed: ${method2}`);
|
|
1375
|
+
}
|
|
1376
|
+
const method = extractMethod(body);
|
|
1377
|
+
if (!await billRpcCall(config.backendUrl, config.apiKey, method)) {
|
|
1378
|
+
return rpcError(-32003, "Insufficient credits or billing unavailable \u2014 RPC call not forwarded", 402);
|
|
1379
|
+
}
|
|
1380
|
+
let lastError = "No RPC upstream configured";
|
|
1381
|
+
for (const rpcUrl of config.rpcUrls) {
|
|
1382
|
+
try {
|
|
1383
|
+
const response = await fetch(rpcUrl, {
|
|
1384
|
+
method: "POST",
|
|
1385
|
+
headers: { "Content-Type": "application/json" },
|
|
1386
|
+
body: JSON.stringify(body)
|
|
1387
|
+
});
|
|
1388
|
+
const text = await response.text();
|
|
1389
|
+
const upstream = rpcUrl.split("/")[2];
|
|
1390
|
+
if (!text) {
|
|
1391
|
+
lastError = `Upstream RPC returned empty body (HTTP ${response.status})`;
|
|
1392
|
+
console.warn("[rpc-proxy] upstream returned empty body", { status: response.status, upstream });
|
|
1393
|
+
continue;
|
|
1394
|
+
}
|
|
1395
|
+
try {
|
|
1396
|
+
const data = JSON.parse(text);
|
|
1397
|
+
if (isTransientRpcError({ status: response.status, body: data })) {
|
|
1398
|
+
const errObj = data.error;
|
|
1399
|
+
lastError = `Upstream RPC returned transient JSON-RPC error: ${String(errObj?.message ?? "(no message)")}`;
|
|
1400
|
+
console.warn("[rpc-proxy] upstream returned transient JSON-RPC error", {
|
|
1401
|
+
upstream,
|
|
1402
|
+
code: errObj?.code,
|
|
1403
|
+
message: errObj?.message
|
|
1404
|
+
});
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
return Response.json(data, { status: 200 });
|
|
1408
|
+
} catch {
|
|
1409
|
+
lastError = `Upstream RPC returned non-JSON (HTTP ${response.status})`;
|
|
1410
|
+
console.warn("[rpc-proxy] upstream returned non-JSON", {
|
|
1411
|
+
status: response.status,
|
|
1412
|
+
upstream,
|
|
1413
|
+
bodyPreview: text.slice(0, 200)
|
|
1414
|
+
});
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
} catch (err) {
|
|
1418
|
+
lastError = `Upstream RPC unreachable: ${err instanceof Error ? err.message : "unknown error"}`;
|
|
1419
|
+
console.warn("[rpc-proxy] upstream fetch failed", {
|
|
1420
|
+
upstream: rpcUrl.split("/")[2],
|
|
1421
|
+
err: err instanceof Error ? err.message : String(err)
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return rpcError(-32603, lastError);
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1284
1429
|
// src/metadata.ts
|
|
1285
1430
|
var RESERVED_TRAITS = /* @__PURE__ */ new Set([
|
|
1286
1431
|
"Creator",
|
|
@@ -1335,11 +1480,40 @@ function buildAssetMetadata(input) {
|
|
|
1335
1480
|
};
|
|
1336
1481
|
}
|
|
1337
1482
|
|
|
1483
|
+
// src/utils/ipfs-gateway.ts
|
|
1484
|
+
var CID_PATH_PATTERN = /^(Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[a-z2-7]{58,})(\/[\w.\-/]*)?$/;
|
|
1485
|
+
function isValidIpfsCidPath(cidPath) {
|
|
1486
|
+
if (!CID_PATH_PATTERN.test(cidPath)) return false;
|
|
1487
|
+
if (cidPath.split("/").includes("..")) return false;
|
|
1488
|
+
return true;
|
|
1489
|
+
}
|
|
1490
|
+
var IPFS_SAFE_CONTENT_TYPE_PREFIXES = [
|
|
1491
|
+
"image/jpeg",
|
|
1492
|
+
"image/png",
|
|
1493
|
+
"image/gif",
|
|
1494
|
+
"image/webp",
|
|
1495
|
+
"image/avif",
|
|
1496
|
+
"image/svg+xml",
|
|
1497
|
+
"video/",
|
|
1498
|
+
"audio/",
|
|
1499
|
+
"model/",
|
|
1500
|
+
"font/",
|
|
1501
|
+
"application/json",
|
|
1502
|
+
"application/octet-stream"
|
|
1503
|
+
];
|
|
1504
|
+
function resolveSafeImageContentType(contentType) {
|
|
1505
|
+
return IPFS_SAFE_CONTENT_TYPE_PREFIXES.some((p) => contentType.startsWith(p)) ? contentType : "application/octet-stream";
|
|
1506
|
+
}
|
|
1507
|
+
var MAX_IPFS_GATEWAY_RESPONSE_BYTES = 25 * 1024 * 1024;
|
|
1508
|
+
|
|
1338
1509
|
exports.ApiClient = ApiClient;
|
|
1339
1510
|
exports.CHAINS = CHAINS;
|
|
1340
1511
|
exports.DEFAULT_CHAIN = DEFAULT_CHAIN;
|
|
1341
1512
|
exports.DEFAULT_CURRENCY = DEFAULT_CURRENCY;
|
|
1513
|
+
exports.DEFAULT_STARKNET_RPC_METHODS = DEFAULT_STARKNET_RPC_METHODS;
|
|
1342
1514
|
exports.FeeConfigSchema = FeeConfigSchema;
|
|
1515
|
+
exports.IPFS_SAFE_CONTENT_TYPE_PREFIXES = IPFS_SAFE_CONTENT_TYPE_PREFIXES;
|
|
1516
|
+
exports.MAX_IPFS_GATEWAY_RESPONSE_BYTES = MAX_IPFS_GATEWAY_RESPONSE_BYTES;
|
|
1343
1517
|
exports.MedialaneApiError = MedialaneApiError;
|
|
1344
1518
|
exports.OPEN_LICENSES = OPEN_LICENSES;
|
|
1345
1519
|
exports.PUBLIC_RPC_FALLBACKS = PUBLIC_RPC_FALLBACKS;
|
|
@@ -1384,6 +1558,7 @@ exports.chainSlug = chainSlug;
|
|
|
1384
1558
|
exports.coinHref = coinHref;
|
|
1385
1559
|
exports.collectionHref = collectionHref;
|
|
1386
1560
|
exports.createFailoverFetch = createFailoverFetch;
|
|
1561
|
+
exports.createRpcProxyHandler = createRpcProxyHandler;
|
|
1387
1562
|
exports.encodeU256 = encodeU256;
|
|
1388
1563
|
exports.formatAmount = formatAmount;
|
|
1389
1564
|
exports.getCoordinates = getCoordinates;
|
|
@@ -1396,12 +1571,14 @@ exports.getTokenBySymbol = getTokenBySymbol;
|
|
|
1396
1571
|
exports.hasCapability = hasCapability;
|
|
1397
1572
|
exports.isServiceId = isServiceId;
|
|
1398
1573
|
exports.isTransientRpcError = isTransientRpcError;
|
|
1574
|
+
exports.isValidIpfsCidPath = isValidIpfsCidPath;
|
|
1399
1575
|
exports.listServices = listServices;
|
|
1400
1576
|
exports.normalizeAddress = normalizeAddress;
|
|
1401
1577
|
exports.normalizeHash = normalizeHash;
|
|
1402
1578
|
exports.parseAmount = parseAmount;
|
|
1403
1579
|
exports.resolveConfig = resolveConfig;
|
|
1404
1580
|
exports.resolveFeeConfig = resolveFeeConfig;
|
|
1581
|
+
exports.resolveSafeImageContentType = resolveSafeImageContentType;
|
|
1405
1582
|
exports.shortenAddress = shortenAddress;
|
|
1406
1583
|
exports.stringifyBigInts = stringifyBigInts;
|
|
1407
1584
|
exports.u256ToBigInt = u256ToBigInt;
|