@medialane/sdk
Framework-agnostic TypeScript SDK for the Medialane API. Bundles a full REST client and on-chain marketplace helpers in one package.
Install
# bun bun add @medialane/sdk starknet # npm npm install @medialane/sdk starknet # yarn yarn add @medialane/sdk starknet
Peer dependency: starknet@^6
Configure
Create a MedialaneClient with your network and API key.
import { MedialaneClient } from "@medialane/sdk"
const client = new MedialaneClient({
network: "mainnet", // "mainnet" | "sepolia"
rpcUrl: "https://starknet-mainnet.g.alchemy.com/starknet/version/rpc/v0_7/YOUR_KEY",
backendUrl: "https://medialane-backend-production.up.railway.app",
apiKey: "ml_live_YOUR_KEY",
// marketplaceContract — optional, defaults to mainnet contract
// collectionContract — optional, defaults to mainnet collection registry
// Optional: configure retry for transient failures
retryOptions: {
maxAttempts: 3, // default
baseDelayMs: 300, // default
maxDelayMs: 5000, // default
},
})The apiKey is sent as x-api-key on every request. Get your key at /account.
Minting & Launchpad
The SDK provides two ways to mint assets: direct on-chain calls (requires signer) and backend-orchestrated intents.
Mint an asset into a collection
// 1. Direct on-chain (client.marketplace)
await client.marketplace.mint(account, {
collectionId: "42",
recipient: "0x0591...",
tokenUri: "ipfs://...",
})
// 2. Via backend intent (client.api)
// No SNIP-12 signing required for mint/create-collection intents
const { intentId, calls } = await client.api.createMintIntent({
owner: "0x0591...", // collection owner
collectionId: "42",
recipient: "0x0592...",
tokenUri: "ipfs://...",
})Register a new collection
// 1. Direct on-chain
await client.marketplace.createCollection(account, {
name: "My Collection",
symbol: "MYC",
baseUri: "ipfs://...",
})
// 2. Via backend intent
const { intentId, calls } = await client.api.createCollectionIntent({
owner: "0x0591...",
name: "My Collection",
symbol: "MYC",
baseUri: "ipfs://...",
})Marketplace (on-chain)
client.marketplace provides typed wrappers for direct contract calls via starknet.js.
// Get order details directly from the contract
const order = await client.marketplace.getOrderDetails("0x04f7a1...")
// Get the current nonce for signing
const nonce = await client.marketplace.getNonce("0x0591...")API Client (REST)
client.api mirrors the full REST API surface.
List open orders
const orders = await client.api.getOrders({ status: "ACTIVE", limit: 20 })
console.log(orders.data[0].orderHash, orders.data[0].price)Get a token with metadata
const token = await client.api.getToken("0x05e7...", "42")
console.log(token.data.metadata?.name)Get collections by owner
// Fetch collections owned by a wallet address
// Addresses are normalized automatically — pass any valid Starknet format
const result = await client.api.getCollectionsByOwner("0x0591...")
result.data.forEach((col) => {
console.log(col.name, col.collectionId) // collectionId = on-chain registry ID
})Create a listing intent
import { toSignatureArray } from "@medialane/sdk"
// 1. Create the intent — get typed data back
const intent = await client.api.createListingIntent({
nftContract: "0x05e7...",
tokenId: "42",
price: "500000",
currency: "USDC",
offerer: walletAddress,
endTime: Math.floor(Date.now() / 1000) + 86400 * 30,
})
// 2. Sign with starknet.js
import { Account } from "starknet"
const account = new Account(provider, walletAddress, privateKey)
const signature = await account.signMessage(intent.data.typedData)
// 3. Submit the signature
await client.api.submitIntentSignature(intent.data.id, toSignatureArray(signature))Search
const results = await client.api.search("genesis", 10)
results.data.tokens.forEach((t) => console.log(t.metadata?.name))
results.data.collections.forEach((c) => console.log(c.name))Portal — manage keys
// List your API keys
const keys = await client.api.getApiKeys()
// Create a new key
const newKey = await client.api.createApiKey("Agent Key")
console.log(newKey.data.key) // shown once — save it!
// Get usage
const usage = await client.api.getUsage()On-chain Comments
// Fetch permanent on-chain comments for a token
const result = await client.api.getTokenComments("0x05e7...", "42", { limit: 20 })
result.data.forEach((c) => {
console.log(c.author, c.content, c.postedAt)
})Counter-offers
// Seller creates a counter-offer in response to a buyer's bid
const intent = await client.api.createCounterOfferIntent(
{
sellerAddress: "0x0591...",
originalOrderHash: "0x04f7a1...",
counterPrice: "750000", // raw wei
durationSeconds: 86400, // 1 day
message: "Best I can do!",
},
siwsToken
)
// Buyer fetches counter-offers for their bid
const counters = await client.api.getCounterOffers({
originalOrderHash: "0x04f7a1...",
})
console.log(counters.data[0].price)
// Buyer accepts by fulfilling the counter-offer (it is a standard listing)
await client.api.createFulfillIntent({ fulfiller: buyerAddress, orderHash: counters.data[0].orderHash })Remix Licensing
import { OPEN_LICENSES } from "@medialane/sdk"
// Check if a license is open (auto-approved remix)
console.log(OPEN_LICENSES) // ["CC0", "CC BY", "CC BY-SA", "CC BY-NC"]
// Request permission to remix a token (custom offer, SIWS session JWT required)
const offer = await client.api.submitRemixOffer(
{
originalContract: "0x05e7...",
originalTokenId: "42",
licenseType: "CC BY-NC",
commercial: false,
derivatives: true,
royaltyPct: 10,
message: "Would love to remix this for my EP cover",
},
siwsToken
)
// Open-license tokens are auto-approved
const autoOffer = await client.api.submitAutoRemixOffer(
{ originalContract: "0x05e7...", originalTokenId: "7", licenseType: "CC0" },
siwsToken
)
// Creator approves a pending offer
await client.api.confirmRemixOffer(offer.data.id, {
approvedCollection: "0x06a3...",
remixContract: "0x06a3...",
remixTokenId: "1",
}, siwsToken)
// Creator rejects an offer
await client.api.rejectRemixOffer(offer.data.id, siwsToken)
// Owner records their own self-remix after minting
await client.api.confirmSelfRemix(
{
originalContract: "0x05e7...",
originalTokenId: "42",
remixContract: "0x06a3...",
remixTokenId: "1",
licenseType: "CC BY",
commercial: true,
derivatives: true,
},
siwsToken
)
// List incoming / outgoing offers
const incoming = await client.api.getRemixOffers({ role: "creator" }, siwsToken)
const outgoing = await client.api.getRemixOffers({ role: "requester" }, siwsToken)
// Get public remixes for a token (no auth needed)
const remixes = await client.api.getTokenRemixes("0x05e7...", "42")
remixes.data.forEach((r) => console.log(r.remixContract, r.remixTokenId, r.licenseType))CollectionSort — typed sort options
import type { CollectionSort } from "@medialane/sdk"
// "recent" | "supply" | "floor" | "volume" | "name"
const sort: CollectionSort = "floor"
await client.api.getCollections(1, 20, true, sort)POP Protocol (Proof of Participation)
POP collections are event-based claim drops — conferences, workshops, hackathons, bootcamps. Each collection has one claimable token per eligible wallet. Use client.services.pop for on-chain interactions and client.api for eligibility checks.
Check eligibility and claim
// Check if a wallet is eligible to claim from a POP collection
const status = await client.api.getPopEligibility(
"0x00b32c...", // POP collection address
"0x0591...", // wallet address
)
// status: { isEligible: boolean; hasClaimed: boolean; tokenId: string | null }
if (status.isEligible && !status.hasClaimed) {
// Claim on-chain (requires starknet.js AccountInterface)
const { txHash } = await client.services.pop.claim(account, "0x00b32c...")
console.log("Claimed:", txHash)
}Batch eligibility check
// Check up to 100 wallets in one request
const results = await client.api.getPopEligibilityBatch(
"0x00b32c...", // POP collection address
["0x0591...", "0x06a3..."],
)
// results: Array<{ wallet, isEligible, hasClaimed, tokenId }>
results.forEach((r) => console.log(r.wallet, r.isEligible))List POP collections
// Fetch all POP Protocol collections
const pops = await client.api.getPopCollections({ page: 1, limit: 20, sort: "recent" })
pops.data.forEach((col) => console.log(col.name, col.source)) // source: "POP_PROTOCOL"Admin — mint and allowlist
// Gift a token to a specific wallet (bypass eligibility check)
await client.services.pop.adminMint(account, {
collection: "0x00b32c...",
recipient: "0x0591...",
customUri: "ipfs://...", // optional override
})
// Add a single wallet to the allowlist
await client.services.pop.addToAllowlist(account, {
collection: "0x00b32c...",
address: "0x0591...",
})
// Add up to 200 wallets per tx
await client.services.pop.batchAddToAllowlist(account, {
collection: "0x00b32c...",
addresses: ["0x0591...", "0x06a3...", /* ... */],
})Deploy a new POP collection
import type { CreatePopCollectionParams } from "@medialane/sdk"
const params: CreatePopCollectionParams = {
name: "ETHDenver 2026",
symbol: "ETHDEN26",
baseUri: "ipfs://...",
claimEndTime: Math.floor(Date.now() / 1000) + 86400 * 7, // 7 days
eventType: "Conference", // "Conference" | "Bootcamp" | "Workshop" | "Hackathon" | "Meetup" | "Course" | "Other"
}
const { txHash } = await client.services.pop.createCollection(account, params)
console.log("Deployed:", txHash)Collection Drop
Collection Drops are public minting campaigns with configurable claim conditions — price, supply cap, time window, and per-wallet limits. Use client.services.drop for on-chain interactions and client.api for status queries.
Claim (public mint)
// Check mint status for a wallet before claiming
const status = await client.api.getDropMintStatus(
"0x03587f...", // Drop collection address
"0x0591...", // wallet address
)
// status: { mintedByWallet: number; totalMinted: number }
// Claim 1 token (default)
const { txHash } = await client.services.drop.claim(account, "0x03587f...")
// Claim multiple tokens
await client.services.drop.claim(account, "0x03587f...", 3)List Drop collections
const drops = await client.api.getDropCollections({ page: 1, limit: 20, sort: "recent" })
drops.data.forEach((col) => console.log(col.name, col.source)) // source: "COLLECTION_DROP"Deploy a new Drop
import type { CreateDropParams, ClaimConditions } from "@medialane/sdk"
const conditions: ClaimConditions = {
startTime: Math.floor(Date.now() / 1000), // open now
endTime: Math.floor(Date.now() / 1000) + 86400 * 30, // closes in 30 days
price: BigInt("1000000"), // 1 USDC (6 decimals). 0 = free mint
paymentToken: "0x033068f6...", // USDC contract
maxQuantityPerWallet: BigInt(5), // max 5 per wallet. 0 = unlimited
}
const params: CreateDropParams = {
name: "Genesis Drop",
symbol: "GEN",
baseUri: "ipfs://...",
maxSupply: BigInt(1000),
initialConditions: conditions,
}
const { txHash } = await client.services.drop.createDrop(account, params)
console.log("Drop deployed:", txHash)Manage an active Drop
// Update claim conditions (price, time window, wallet limits)
await client.services.drop.setClaimConditions(account, {
collection: "0x03587f...",
conditions: { startTime: 0, endTime: 0, price: 0n, paymentToken: "0x0", maxQuantityPerWallet: 0n },
})
// Pause or unpause minting
await client.services.drop.setPaused(account, { collection: "0x03587f...", paused: true })
// Enable allowlist gate
await client.services.drop.setAllowlistEnabled(account, { collection: "0x03587f...", enabled: true })
await client.services.drop.batchAddToAllowlist(account, {
collection: "0x03587f...",
addresses: ["0x0591...", "0x06a3..."],
})
// Withdraw ERC-20 proceeds
await client.services.drop.withdrawPayments(account, { collection: "0x03587f..." })Error Handling
The SDK throws MedialaneError for marketplace issues and MedialaneApiError for REST API failures. Both carry a typed .code field from the MedialaneErrorCode union.
import { MedialaneError, MedialaneApiError } from "@medialane/sdk"
try {
await client.marketplace.mint(account, params)
} catch (err) {
if (err instanceof MedialaneError) {
console.error(err.code, err.message) // e.g. "TRANSACTION_FAILED"
}
if (err instanceof MedialaneApiError) {
console.error(err.code, err.status, err.message) // e.g. "TOKEN_NOT_FOUND", 404
}
}Error Codes
All errors expose a MedialaneErrorCode typed union:
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"
TOKEN_NOT_FOUND404 response or missing tokenCOLLECTION_NOT_FOUND404 on collection lookupORDER_NOT_FOUND404 on order lookupINTENT_NOT_FOUND404 on intent lookupINTENT_EXPIRED410 response — intent TTL exceededRATE_LIMITED429 response — too many requestsNETWORK_NOT_SUPPORTEDSepolia selected with no contract addressesAPPROVAL_FAILEDNFT approval missing before listingTRANSACTION_FAILEDOn-chain call revertedINVALID_PARAMS400 response — bad request parametersUNAUTHORIZED401/403 — missing or invalid API keyUNKNOWNUnexpected errorsNote: 4xx errors are not retried automatically. Only transient network and 5xx errors trigger the retry logic configured via retryOptions.
Full API reference — all REST endpoints, parameters, and response schemas are documented in the API Reference.
Use Case Examples
Common patterns you can build with the SDK:
Fetch a creator's portfolio
const portfolio = await client.api.getTokensByOwner({
owner: "0x05f9...",
limit: 20,
});
// portfolio.data → array of token objects with metadata, license terms, remixCountList open-license assets for remix
import { OPEN_LICENSES } from "@medialane/sdk";
const openAssets = await client.api.getTokens({
licenseType: OPEN_LICENSES, // ["CC0", "CC BY", "CC BY-SA", "CC BY-NC"]
});Submit a trade intent (no private key exposure)
const intent = await client.api.createListingIntent({
contractAddress: "0x04a...",
tokenId: "42",
price: "0.05", // in ETH
currency: "ETH",
duration: 86400, // seconds
});
// sign off-chain, submit on-chain
const sig = await account.signMessage(intent.typedData);
await client.api.submitOrder({ ...intent, signature: sig });Stream on-chain activity
const activity = await client.api.getActivities({
eventType: "TRANSFER", // TRANSFER | ORDER_CREATED | ORDER_FULFILLED | ORDER_CANCELLED
contractAddress: "0x04a...",
limit: 50,
});Built with the SDK
Both Medialane consumer apps use the same SDK you're integrating:
medialane.io
Creator Launchpad
Collections, Orders, Minting, Remix Licensing, POP, Collection Drop, On-chain Comments. Invisible wallet UX via ChipiPay.
dapp.medialane.io
Permissionless dApp
Activities, Trade Intents, Asset Metadata. Direct starknet.js reads — no backend dependency for browsing.
Full SDK documentation
Complete method reference, type definitions, and advanced usage are on docs.medialane.io/docs/sdk.