@medialane/sdk
v0.4.8
Published
Framework-agnostic TypeScript SDK for Medialane.io — the IP marketplace on Starknet
Readme
@medialane/sdk
Framework-agnostic TypeScript SDK for the Medialane IP marketplace on Starknet
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 and Medialane.xyz.
Features
On-Chain Operations
- Create listings (ERC-721 for sale)
- Make offers (bid with ERC-20)
- Fulfill orders (purchase NFTs)
- Cancel active orders
- Atomic multi-item cart checkout
- Built-in approval checking
- SNIP-12 typed data signing
- Mint IP NFTs into any collection
- Deploy new ERC-721 collections
REST API Client
- Query orders, tokens, collections, and activities
- Full-text search across the marketplace
- Intent-based transaction orchestration
- Upload metadata and files to IPFS (Pinata)
- Tenant portal: API keys, webhooks, usage
IP Metadata Types
IpAttribute— typed OpenSea ERC-721 attributeIpNftMetadata— full IPFS metadata shape with licensing fieldsApiTokenMetadata— indexed token metadata with all licensing attributes- Berne Convention-compatible licensing data model
Developer-Friendly
- Framework-agnostic TypeScript
- Dual ESM + CJS builds
- Zod schema config validation
- Full type safety
- Peer dependency:
starknet >= 6.0.0
Installation
npm install @medialane/sdk starknet
# or
bun add @medialane/sdk starknet
# or
yarn add @medialane/sdk starknetQuick Start
Initialize the Client
import { MedialaneClient } from "@medialane/sdk";
const client = new MedialaneClient({
network: "mainnet", // "mainnet" | "sepolia"
rpcUrl: "https://rpc.starknet.lava.build", // optional; defaults to Lava
backendUrl: "https://medialane-backend-production.up.railway.app", // required for .api methods
apiKey: "ml_live_...", // from Medialane Portal
});Marketplace Operations (On-Chain)
All methods require a starknet.js AccountInterface. Nonce management, SNIP-12 signing, and waitForTransaction are handled automatically.
Create a Listing
import { Account } from "starknet";
const result = await client.marketplace.createListing(account, {
nftContract: "0x05e73b7...",
tokenId: 42n,
currency: "USDC",
price: "1000000", // 1 USDC (6 decimals)
endTime: Math.floor(Date.now() / 1000) + 86400 * 30, // 30 days
});
console.log("Listed:", result.txHash);Make an Offer
const result = await client.marketplace.makeOffer(account, {
nftContract: "0x05e73b7...",
tokenId: 42n,
currency: "USDC",
price: "500000", // 0.5 USDC
endTime: Math.floor(Date.now() / 1000) + 86400 * 7,
});Fulfill an Order
const result = await client.marketplace.fulfillOrder(account, {
orderHash: "0x...",
fulfiller: account.address,
});Cart Checkout (Multiple Items)
const result = await client.marketplace.checkoutCart(account, [
{ orderHash: "0x...", fulfiller: account.address },
{ orderHash: "0x...", fulfiller: account.address },
]);Cancel an Order
const result = await client.marketplace.cancelOrder(account, {
orderHash: "0x...",
offerer: account.address,
});Mint an IP Asset
const result = await client.marketplace.mint(account, {
collectionId: "1", // collection ID on the registry
recipient: account.address,
tokenUri: "ipfs://...", // IPFS URI of the metadata JSON
});Deploy a Collection
const result = await client.marketplace.createCollection(account, {
name: "My Creative Works",
symbol: "MCW",
baseUri: "",
});REST API
Query Orders
const orders = await client.api.getOrders({
status: "ACTIVE",
sort: "price_asc",
currency: "0x033068...", // USDC address
page: 1,
limit: 20,
});
const order = await client.api.getOrder("0x...");
const tokenOrders = await client.api.getActiveOrdersForToken(contract, tokenId);
const userOrders = await client.api.getOrdersByUser(address);Query Tokens
const token = await client.api.getToken(contract, tokenId);
const tokens = await client.api.getTokensByOwner(address);
const history = await client.api.getTokenHistory(contract, tokenId);Query Collections
// All collections — newest first by default
const collections = await client.api.getCollections();
// With sort and pagination
const byVolume = await client.api.getCollections(1, 20, undefined, "volume");
const verified = await client.api.getCollections(1, 18, true, "recent");
// Sort options: "recent" | "supply" | "floor" | "volume" | "name"
const collection = await client.api.getCollection(contract);
const tokens = await client.api.getCollectionTokens(contract);Search
const results = await client.api.search("landscape painting", 10);
// results.data.tokens — matching tokens
// results.data.collections — matching collections
// results.data.creators — matching creator profiles (v0.4.5)Activities
const feed = await client.api.getActivities({ type: "sale", page: 1 });
const userFeed = await client.api.getActivitiesByAddress(address);Upload Metadata to IPFS
// Upload a file
const fileResult = await client.api.uploadFile(imageFile);
// fileResult.data.url → "ipfs://..."
// Upload metadata JSON
const metaResult = await client.api.uploadMetadata({
name: "My Work",
description: "...",
image: "ipfs://...",
external_url: "https://medialane.io",
attributes: [
{ trait_type: "License", value: "CC BY-NC" },
{ trait_type: "Commercial Use", value: "No" },
// ...
],
});
// metaResult.data.url → "ipfs://..."Intents (Advanced)
The intent system handles the SNIP-12 signing flow for marketplace operations:
// 1. Create intent (gets typedData to sign)
const intent = await client.api.createListingIntent({
offerer: address,
nftContract: "0x...",
tokenId: "42",
currency: "0x033068...",
price: "1000000",
endTime: Math.floor(Date.now() / 1000) + 86400 * 30,
});
// 2. Sign typedData
const signature = await account.signMessage(intent.data.typedData);
// 3. Submit signature
await client.api.submitIntentSignature(intent.data.id, toSignatureArray(signature));Mint and collection intents are pre-signed — no signature step needed:
const mintIntent = await client.api.createMintIntent({
owner: ownerAddress,
collectionId: "1",
recipient: recipientAddress,
tokenUri: "ipfs://...",
});
// mintIntent.data.calls → ready to executeIP Metadata Types
import type { IpAttribute, IpNftMetadata, ApiTokenMetadata } from "@medialane/sdk";
// Single OpenSea ERC-721 attribute
const attr: IpAttribute = { trait_type: "License", value: "CC BY-NC-SA" };
// Full IPFS metadata shape for a Medialane IP NFT
const metadata: IpNftMetadata = {
name: "My Track",
description: "Original music",
image: "ipfs://...",
external_url: "https://medialane.io",
attributes: [
{ trait_type: "IP Type", value: "Audio" },
{ trait_type: "License", value: "CC BY-NC-SA" },
{ trait_type: "Commercial Use", value: "No" },
{ trait_type: "Derivatives", value: "Share-Alike" },
{ trait_type: "Attribution", value: "Required" },
{ trait_type: "Territory", value: "Worldwide" },
{ trait_type: "AI Policy", value: "Not Allowed" },
{ trait_type: "Royalty", value: "10%" },
{ trait_type: "Standard", value: "Berne Convention" },
{ trait_type: "Registration", value: "2026-03-06" },
],
};
// Token from the API — includes indexed licensing fields for fast access
const token = await client.api.getToken(contract, tokenId);
token.data.metadata.licenseType; // "CC BY-NC-SA"
token.data.metadata.commercialUse; // "No"
token.data.metadata.derivatives; // "Share-Alike"
token.data.metadata.attributes; // IpAttribute[] | nullSupported Tokens
| Symbol | Address | Decimals | Listable |
|--------|---------|----------|----------|
| USDC | 0x033068f6539f8e6e6b131e6b2b814e6c34a5224bc66947c47dab9dfee93b35fb | 6 | ✓ |
| USDT | 0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8 | 6 | ✓ |
| ETH | 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 | 18 | ✓ |
| STRK | 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d | 18 | ✓ |
| WBTC | 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac | 8 | ✓ |
import { getTokenBySymbol, getTokenByAddress, getListableTokens, SUPPORTED_TOKENS } from "@medialane/sdk";
const usdc = getTokenBySymbol("USDC");
const token = getTokenByAddress("0x033068...");Utilities
import {
normalizeAddress, // Pad to 64-char 0x-prefixed lowercase hex
shortenAddress, // → "0x1234...5678"
parseAmount, // Human-readable → smallest unit BigInt ("1.5", 6) → 1500000n
formatAmount, // Smallest unit → human-readable ("1500000", 6) → "1.5"
stringifyBigInts, // Recursively convert BigInt → string (for JSON)
u256ToBigInt, // u256 { low, high } → BigInt
getListableTokens, // ReadonlyArray<SupportedToken> filtered to listable: true (for dialogs)
} from "@medialane/sdk";Error Handling
import { MedialaneError, MedialaneApiError } from "@medialane/sdk";
// On-chain errors (marketplace module)
try {
await client.marketplace.createListing(account, params);
} catch (err) {
if (err instanceof MedialaneError) {
console.error("On-chain error:", err.message, err.cause);
}
}
// REST API errors
try {
await client.api.getOrders();
} catch (err) {
if (err instanceof MedialaneApiError) {
console.error(`API ${err.status}:`, err.message);
}
}Configuration Reference
| Option | Type | Default | Description |
|---|---|---|---|
| network | "mainnet" \| "sepolia" | "mainnet" | Starknet network |
| rpcUrl | string | Lava public endpoint | JSON-RPC URL |
| backendUrl | string | — | Medialane API base URL (required for .api.*) |
| apiKey | string | — | API key from Medialane Portal |
| marketplaceContract | string | Mainnet default | Marketplace contract override |
| collectionContract | string | Mainnet default | Collection registry override |
Advanced: SNIP-12 Typed Data Builders
For integrations that handle signing externally (e.g. ChipiPay, Cartridge Controller):
import {
buildOrderTypedData,
buildFulfillmentTypedData,
buildCancellationTypedData,
} from "@medialane/sdk";
const typedData = buildOrderTypedData(orderParams, chainId);
const signature = await account.signMessage(typedData);
await client.api.submitIntentSignature(intentId, signatureArray);Development
bun run build # Compile to dist/ (ESM + CJS dual output)
bun run dev # Watch mode
bun run typecheck # tsc --noEmitBuilt with:
- tsup — dual ESM/CJS bundling
- TypeScript — full type safety
- Zod — runtime config validation
- Peer dep:
starknet >= 6.0.0
Changelog
v0.4.7
IPTypeunion type exported —"Audio" | "Art" | "Documents" | "NFT" | "Video" | "Photography" | "Patents" | "Posts" | "Publications" | "RWA" | "Software" | "Custom"
v0.4.6
ApiUserWallettype +upsertMyWallet(clerkToken)/getMyWallet(clerkToken)for ChipiPay wallet registration fallback (POST/GET /v1/users/me)
v0.4.5
ApiSearchCreatorResulttype +ApiSearchResult.creators— creator profiles now included in search results
v0.4.4
ApiCreatorListResult+getCreators(opts?)— list creators with search/pagination viaGET /v1/creators
v0.4.3
ApiCreatorProfile.usernamefield +getCreatorByUsername(username)— resolve username slug to creator profile
v0.4.2
- WBTC added to
SUPPORTED_TOKENS(0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac, 8 decimals) listablefield on everySUPPORTED_TOKENSentry — controls whether a token appears in listing/offer dialogs vs filter-onlygetListableTokens()— returns tokens filtered tolistable: true; exported from package root- ETH promoted to
listable: true— now available in listing and offer dialogs - USDC.e removed — bridged USDC (
0x053c91...) removed entirely; only Circle-native USDC remains, to avoid user confusion
v0.4.1
- Collection claims —
claimCollection(contractAddress, walletAddress, clerkToken)for on-chain ownership verification;requestCollectionClaim({ contractAddress, walletAddress?, email, notes? })for manual review - Collection profiles —
getCollectionProfile(contractAddress)andupdateCollectionProfile(contractAddress, data, clerkToken)for enriched display metadata (displayName, description, image, bannerImage, social links) - Creator profiles —
getCreatorProfile(walletAddress)andupdateCreatorProfile(walletAddress, data, clerkToken)for creator display metadata - New types —
ApiCollectionClaim,ApiAdminCollectionClaim,ApiCollectionProfile,ApiCreatorProfile ApiCollectionextended withsource("MEDIALANE_REGISTRY" | "EXTERNAL" | "PARTNERSHIP" | "IP_TICKET" | "IP_CLUB" | "GAME") andclaimedBy: string | nullprofile?: ApiCollectionProfile | nulloptionally embedded onApiCollectionwhen?include=profile
v0.4.0
- Typed error codes —
MedialaneErrorandMedialaneApiErrornow expose a.code: MedialaneErrorCodeproperty ("TOKEN_NOT_FOUND"|"RATE_LIMITED"|"INTENT_EXPIRED"|"UNAUTHORIZED"|"INVALID_PARAMS"|"NETWORK_NOT_SUPPORTED"|"UNKNOWN") - Automatic retry — all API requests retry up to 3 times with exponential backoff (300ms base, 5s cap); 4xx errors are not retried. Configure via
retryOptionsinMedialaneConfig RetryOptionstype exported from indexCollectionSortnamed union type exported ("recent" | "supply" | "floor" | "volume" | "name")- Sepolia guard — constructing a client with
network: "sepolia"and no explicit contract addresses now throwsNETWORK_NOT_SUPPORTEDimmediately
v0.3.3
getCollections(page?, limit?, isKnown?, sort?)— addedsortparameter:"recent"(default) |"supply"|"floor"|"volume"|"name"- Default sort changed from
totalSupply DESCtocreatedAt DESC(newest first) — matches backend default
v0.3.1
ApiCollection.collectionId: string | null— on-chain registry numeric ID (decimal string). Required forcreateMintIntent. Populated for collections indexed after 2026-03-09.
v0.3.0
normalizeAddress()applied internally before all API calls — callers no longer need to normalize Starknet addressesApiCollection.owner: string | null— populated from intent typedData or on-chainowner()callgetCollectionsByOwner(owner)— fetch collections by wallet address viaGET /v1/collections?owner=
v0.2.6
ApiOrder.token: ApiOrderTokenMeta | null— token name/image/description embedded on orders (batchTokenMeta); no per-rowgetTokencalls needed
v0.2.0
IpAttributeandIpNftMetadatainterfaces for IP metadataApiTokenMetadata.attributestyped asIpAttribute[] | null(wasunknown)ApiTokenMetadataextended withderivatives,attribution,territory,aiPolicy,royalty,registration,standard- Added
USDC.e(bridged USDC via Starkgate) toSUPPORTED_TOKENS
v0.1.0
- Initial release — orders, tokens, collections, activities, intents, metadata, portal
Links
- Marketplace: medialane.io
- Developer Portal: medialane.xyz
- npm: npmjs.com/package/@medialane/sdk
- GitHub: github.com/medialane-io
