| name | Mediolano Protocol Skills |
| description | AI agent skills for zero-fee IP tokenization on Starknet using the Mediolano Protocol |
| version | 1.0.0 |
| author | Mediolano |
Mediolano Protocol SDK Integration
Mediolano is a permissionless intellectual property (IP) tokenization platform on Starknet, providing zero-fee IP registration, collection management, and NFT operations.
Quick Start
The SDK is integrated within the Next.js application at src/sdk:
import { getSDK, createSDK } from '@/sdk';
const sdk = getSDK();
const sdk = createSDK({
rpcUrl: process.env.NEXT_PUBLIC_RPC_URL,
collectionContractAddress: process.env.NEXT_PUBLIC_COLLECTION_CONTRACT_ADDRESS as `0x${string}`,
});
const collections = await sdk.collections.getAllCollections();
const asset = await sdk.assets.getAsset('0xNftAddress', tokenId);
const status = await sdk.getStatus();
Account Setup
Wallet Account (Frontend/dApps)
import { useAccount, useSendTransaction } from '@starknet-react/core';
const { address } = useAccount();
const { sendAsync } = useSendTransaction({ calls: [] });
const result = await sendAsync([mintCall]);
Direct Account (Scripts/Backend)
import { Account, RpcProvider } from 'starknet';
const provider = new RpcProvider({ nodeUrl: process.env.NEXT_PUBLIC_RPC_URL });
const account = new Account(
provider,
process.env.STARKNET_ACCOUNT_ADDRESS,
process.env.STARKNET_PRIVATE_KEY
);
const result = await account.execute([{
contractAddress: mintCall.contractAddress,
entrypoint: mintCall.entrypoint,
calldata: mintCall.calldata,
}]);
Collection Operations
Read Collections
const collections = await sdk.collections.getAllCollections();
const collection = await sdk.collections.getCollection('1');
const userCollections = await sdk.collections.getUserCollections('0x...');
const result = await sdk.collections.getCollectionsPaginated(
{ page: 1, pageSize: 12 },
{ type: 'art', isActive: true }
);
const stats = await sdk.collections.getCollectionStats('1');
const isValid = await sdk.collections.isValidCollection('1');
const isOwner = await sdk.collections.isCollectionOwner('1', '0xAddress');
Create Collection
import { PinataSDK } from 'pinata';
const pinata = new PinataSDK({ pinataJwt: process.env.PINATA_JWT });
const metadata = {
name: 'My Collection',
description: 'Collection description',
image: 'ipfs://QmCoverImage',
type: 'art',
};
const response = await pinata.upload.json(metadata);
const baseUri = `ipfs://${response.IpfsHash}`;
const createCall = sdk.collections.buildCreateCollectionCall({
name: 'My Collection',
symbol: 'MYCOL',
baseUri,
});
await sendAsync([createCall]);
Mint Assets
const mintCall = sdk.collections.buildMintCall({
collectionId: '1',
recipient: '0x...' as `0x${string}`,
tokenUri: 'ipfs://QmAssetMetadata',
});
const batchMintCall = sdk.collections.buildBatchMintCall({
collectionId: '1',
recipients: ['0x...' as `0x${string}`, '0x...' as `0x${string}`],
tokenUris: ['ipfs://Qm1', 'ipfs://Qm2'],
});
See references/collection-guide.md for complete documentation.
Asset Operations
Read Assets
const asset = await sdk.assets.getAsset('0xNftAddress', tokenId);
const assets = await sdk.assets.getCollectionAssets('0xNftAddress');
const userAssets = await sdk.assets.getUserAssets('0xNftAddress', '0xUserAddress');
const result = await sdk.assets.getCollectionAssetsPaginated(
'0xNftAddress',
{ page: 1, pageSize: 12 }
);
const tokens = await sdk.assets.getUserTokensPerCollection('1', '0xUserAddress');
const isValid = await sdk.assets.isValidToken('tokenIdentifier');
NFT Standard Read Operations
const owner = await sdk.assets.getTokenOwner('0xNftAddress', tokenId);
const uri = await sdk.assets.getTokenUri('0xNftAddress', tokenId);
const supply = await sdk.assets.getTotalSupply('0xNftAddress');
const balance = await sdk.assets.getBalance('0xNftAddress', '0xOwner');
const tokenAtIndex = await sdk.assets.getTokenOfOwnerByIndex('0xNftAddress', '0xOwner', 0);
Transfer Assets
const transferCall = sdk.assets.buildTransferCall({
from: '0xCurrentOwner' as `0x${string}`,
to: '0xNewOwner' as `0x${string}`,
token: 'tokenIdentifier',
});
const batchTransferCall = sdk.assets.buildBatchTransferCall({
from: '0xCurrentOwner' as `0x${string}`,
to: '0xNewOwner' as `0x${string}`,
tokens: ['token1', 'token2', 'token3'],
});
Burn Assets
const burnCall = sdk.assets.buildBurnCall({ token: 'tokenIdentifier' });
const batchBurnCall = sdk.assets.buildBatchBurnCall({ tokens: ['token1', 'token2'] });
See references/asset-guide.md for complete documentation.
API Services
Internal API Routes (/api/sdk/)
These are Next.js API routes powered by the SDK, running on Edge runtime with 30s caching:
| Endpoint | Method | Description | Query Params |
|---|
/api/sdk/collections | GET | List collections | owner, type, search, isActive, page, pageSize |
/api/sdk/collections/[id] | GET | Get single collection | - |
/api/sdk/collections/[id]/stats | GET | Get collection stats | - |
/api/sdk/assets | GET | List assets | collection (required), owner, type, search, page, pageSize |
/api/sdk/assets/[nftAddress]/[tokenId] | GET | Get single asset | - |
/api/sdk/status | GET | SDK health check | - |
const res = await fetch('/api/sdk/collections?page=1&pageSize=10&type=art');
const { items, total, hasMore } = await res.json();
const collection = await fetch('/api/sdk/collections/1').then(r => r.json());
IPFS Upload APIs
| Endpoint | Method | Description |
|---|
/api/pinata | GET | Get Pinata signed upload URL |
/api/uploadmeta | POST | Upload metadata JSON to IPFS |
/api/forms-ipfs | POST | Upload form data with file to IPFS |
External Indexer API
Base URL: https://mediolano-api-service.onrender.com/api
OpenAPI Docs: Swagger UI | OpenAPI JSON
Assets
| Endpoint | Method | Description |
|---|
/assets | GET | List all assets |
/assets/{id} | GET | Get asset by ID |
/assets/owner/{owner} | GET | Get assets by owner address |
Collections
| Endpoint | Method | Description |
|---|
/collections | GET | List collections |
/collections/{id} | GET | Get collection by ID |
/collections/creator/{creator} | GET | Get collections by creator |
Query params: indexerSource (MEDIALANO-DAPP, MEDIALANO-MIPP), creator, search, limit, offset, sortBy, sortOrder
Transfers
| Endpoint | Method | Description |
|---|
/transfers | GET | List transfers |
/transfers/token/{tokenId} | GET | Get transfers for token |
/transfers/from/{from} | GET | Get transfers from address |
/transfers/to/{to} | GET | Get transfers to address |
Stats
| Endpoint | Method | Description |
|---|
/stats | GET | Global statistics |
/stats/indexer | GET | Stats by indexer source |
/stats/collection/{collectionId} | GET | Collection stats |
/stats/owner/{owner} | GET | Owner stats |
/stats/trending | GET | Trending collections |
Reports (Community Moderation)
| Endpoint | Method | Description |
|---|
/reports | GET | List reports |
/reports/submit | POST | Submit a report |
/reports/{id} | GET | Get report by ID |
/reports/{id}/status | PATCH | Update report status |
const BASE_URL = 'https://mediolano-api-service.onrender.com/api';
const trending = await fetch(`${BASE_URL}/stats/trending?limit=10`).then(r => r.json());
const collections = await fetch(`${BASE_URL}/collections?limit=20&offset=0&sortBy=createdAtBlock&sortOrder=desc`).then(r => r.json());
const transfers = await fetch(`${BASE_URL}/transfers/token/${tokenId}`).then(r => r.json());
IP Types
| Type | Description |
|---|
art | Visual artwork, illustrations, photography |
audio | Music, podcasts, sound effects |
video | Films, animations, tutorials |
document | Books, articles, research papers |
patent | Technical inventions, designs |
publication | Journals, magazines |
software | Code, applications, libraries |
rwa | Real world assets with digital twins |
Metadata Schemas
Mediolano follows OpenSea/ERC-721 metadata standards for marketplace interoperability. All IP-specific metadata (type, license, registration, etc.) is stored in the attributes array.
Asset Metadata (IPFS) - OpenSea Standard
{
"name": "My Digital Artwork",
"description": "A vibrant digital illustration exploring themes of nature",
"image": "ipfs://QmAssetImage",
"external_url": "https://mediolano.app/asset/123",
"attributes": [
{ "trait_type": "Type", "value": "art" },
{ "trait_type": "Author", "value": "Creator Name" },
{ "trait_type": "License", "value": "CC-BY-4.0" },
{ "trait_type": "License-Details", "value": "Attribution required" },
{ "trait_type": "Commercial", "value": "Yes" },
{ "trait_type": "Modifications", "value": "Allowed with attribution" },
{ "trait_type": "Attribution", "value": "Required" },
{ "trait_type": "Format", "value": "PNG" },
{ "trait_type": "Dimensions", "value": "1920x1080" },
{ "trait_type": "Created", "value": "2024" },
{ "trait_type": "Language", "value": "English" },
{ "trait_type": "Tags", "value": "digital, abstract, colorful" },
{ "trait_type": "Registration", "value": "2024-01-15" },
{ "trait_type": "Status", "value": "Registered" },
{ "trait_type": "Scope", "value": "Worldwide" },
{ "trait_type": "Duration", "value": "Perpetual" },
{ "trait_type": "Version", "value": "1.0" },
{ "trait_type": "Network", "value": "Starknet" },
{ "trait_type": "Contract Address", "value": "0x..." }
]
}
IP Type-Specific Attributes
Different asset types have additional specialized attributes:
| Type | Additional Attributes |
|---|
audio | Artist, Album, Genre, Composer, Band, Publisher |
video | Director, Producer, Duration, Resolution |
document | Author, Pages, Publisher, ISBN |
software | Repository, Language, Framework, Dependencies |
patent | Filing Date, Patent Number, Claims, Inventors |
publication | Journal, Volume, Issue, DOI |
rwa | Physical Location, Certification, Appraisal |
Collection Metadata (IPFS)
{
"name": "My IP Collection",
"description": "A curated collection of digital artworks",
"image": "ipfs://QmCollectionCover",
"external_url": "https://mediolano.app/collections/123",
"attributes": [
{ "trait_type": "Type", "value": "art" },
{ "trait_type": "Visibility", "value": "public" },
{ "trait_type": "Category", "value": "Digital Art" }
]
}
Querying Attributes (SDK)
const asset = await sdk.assets.getAsset(nftAddress, tokenId);
const licenseAttr = asset.attributes?.find(a => a.trait_type === "License");
const license = licenseAttr?.value || "Unknown";
const typeAttr = asset.attributes?.find(a => a.trait_type === "Type");
const type = typeAttr?.value || "Unknown";
const isRemix = asset.attributes?.some(
a => a.trait_type === "Type" && a.value === "Remix"
);
See references/tokenization-guide.md for complete schemas.
License Types
| License | Commercial | Derivatives |
|---|
| CC0 | ✅ | ✅ |
| CC-BY | ✅ | ✅ |
| CC-BY-NC | ❌ | ✅ |
| CC-BY-ND | ✅ | ❌ |
| MIT | ✅ | ✅ |
| Apache-2.0 | ✅ | ✅ |
| All-Rights-Reserved | ❌ | ❌ |
Utilities
import {
normalizeAddress,
decimalToHex,
isZeroAddress,
processIPFSUrl,
extractCID,
fetchIPFSMetadata,
processMetadataImage,
} from '@/sdk/utils';
normalizeAddress('0x123...')
decimalToHex('123456')
isZeroAddress('0x0')
processIPFSUrl('ipfs://Qm...', gateway)
extractCID('ipfs://Qm...')
await fetchIPFSMetadata(cid, gateway)
Configuration
Required Environment Variables
NEXT_PUBLIC_RPC_URL=https://starknet-mainnet.g.alchemy.com/v2/KEY
NEXT_PUBLIC_COLLECTION_CONTRACT_ADDRESS=0x...
NEXT_PUBLIC_STARKNET_NETWORK=mainnet
NEXT_PUBLIC_GATEWAY_URL=https://gateway.pinata.cloud
PINATA_JWT=eyJhbGc...
SDK Options
import { createSDK } from '@/sdk';
const sdk = createSDK({
rpcUrl: 'https://...',
network: 'mainnet',
collectionContractAddress: '0x...' as `0x${string}`,
timeout: 30000,
maxRetries: 3,
retryDelayMs: 1000,
ipfsGateway: 'https://gateway.pinata.cloud',
cacheOptions: {
ttl: 30000,
maxEntries: 500
},
debug: true,
});
sdk.clearCache();
sdk.invalidateContractCache('0xContractAddress');
See references/configuration.md for complete setup.
Fees
| Fee Type | Amount |
|---|
| Protocol Fee | $0 |
| Minting Fee | $0 |
| Transfer Fee | $0 |
| Gas Fee | ~0.001 STRK |
Error Handling
| Error | Cause | Solution |
|---|
| "Collection X does not exist" | Invalid collection ID | Verify collection ID |
| "Collection name is required" | Empty name parameter | Provide non-empty name |
| "Base URI is required" | Missing IPFS URI | Upload metadata first |
| "Recipient address is required" | Missing recipient | Provide recipient address |
| "Token is required" | Missing token | Provide token identifier |
See references/error-handling.md for complete error taxonomy.
References & Examples
Guides:
collection-guide |
asset-guide |
tokenization-guide |
configuration |
error-handling
Scripts:
fetch-collections-example.ts |
create-collection-example.ts |
mint-example.ts |
transfer-example.ts |
upload-ipfs-example.ts
Links:
SDK Source |
External API Docs |
App