mediolano-protocol-skills
AI agent skills for zero-fee IP tokenization on Starknet using the Mediolano Protocol
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
AI agent skills for zero-fee IP tokenization on Starknet using the Mediolano Protocol
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| 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 is a permissionless intellectual property (IP) tokenization platform on Starknet, providing zero-fee IP registration, collection management, and NFT operations.
The SDK is integrated within the Next.js application at src/sdk:
import { getSDK, createSDK } from '@/sdk';
// Use singleton instance (recommended)
const sdk = getSDK();
// Or create a new instance
const sdk = createSDK({
rpcUrl: process.env.NEXT_PUBLIC_RPC_URL,
collectionContractAddress: process.env.NEXT_PUBLIC_COLLECTION_CONTRACT_ADDRESS as `0x${string}`,
});
// Read collections
const collections = await sdk.collections.getAllCollections();
// Read assets
const asset = await sdk.assets.getAsset('0xNftAddress', tokenId);
// Get SDK status
const status = await sdk.getStatus();
import { useAccount, useSendTransaction } from '@starknet-react/core';
const { address } = useAccount();
const { sendAsync } = useSendTransaction({ calls: [] });
// Execute transaction
const result = await sendAsync([mintCall]);
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 // NEVER commit!
);
const result = await account.execute([{
contractAddress: mintCall.contractAddress,
entrypoint: mintCall.entrypoint,
calldata: mintCall.calldata,
}]);
// Get all collections
const collections = await sdk.collections.getAllCollections();
// Get single collection by ID
const collection = await sdk.collections.getCollection('1');
// Get user's collections
const userCollections = await sdk.collections.getUserCollections('0x...');
// Paginated with filters
const result = await sdk.collections.getCollectionsPaginated(
{ page: 1, pageSize: 12 },
{ type: 'art', isActive: true }
);
// Get statistics
const stats = await sdk.collections.getCollectionStats('1');
// Validation
const isValid = await sdk.collections.isValidCollection('1');
const isOwner = await sdk.collections.isCollectionOwner('1', '0xAddress');
import { PinataSDK } from 'pinata';
// 1. Upload metadata to IPFS
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}`;
// 2. Build transaction
const createCall = sdk.collections.buildCreateCollectionCall({
name: 'My Collection',
symbol: 'MYCOL', // Optional, defaults to 'COLL'
baseUri,
});
// 3. Execute
await sendAsync([createCall]);
// Single mint
const mintCall = sdk.collections.buildMintCall({
collectionId: '1',
recipient: '0x...' as `0x${string}`,
tokenUri: 'ipfs://QmAssetMetadata',
});
// Batch mint
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.
// Get single asset by NFT address and token ID
const asset = await sdk.assets.getAsset('0xNftAddress', tokenId);
// Get all assets in a collection
const assets = await sdk.assets.getCollectionAssets('0xNftAddress');
// Get user's assets within a collection
const userAssets = await sdk.assets.getUserAssets('0xNftAddress', '0xUserAddress');
// Paginated
const result = await sdk.assets.getCollectionAssetsPaginated(
'0xNftAddress',
{ page: 1, pageSize: 12 }
);
// Get user tokens per collection from registry
const tokens = await sdk.assets.getUserTokensPerCollection('1', '0xUserAddress');
// Token validation
const isValid = await sdk.assets.isValidToken('tokenIdentifier');
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);
// Single transfer - token is a string identifier
const transferCall = sdk.assets.buildTransferCall({
from: '0xCurrentOwner' as `0x${string}`,
to: '0xNewOwner' as `0x${string}`,
token: 'tokenIdentifier', // Token identifier string
});
// Batch transfer
const batchTransferCall = sdk.assets.buildBatchTransferCall({
from: '0xCurrentOwner' as `0x${string}`,
to: '0xNewOwner' as `0x${string}`,
tokens: ['token1', 'token2', 'token3'],
});
const burnCall = sdk.assets.buildBurnCall({ token: 'tokenIdentifier' });
const batchBurnCall = sdk.assets.buildBatchBurnCall({ tokens: ['token1', 'token2'] });
See references/asset-guide.md for complete documentation.
/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 | - |
// Example usage
const res = await fetch('/api/sdk/collections?page=1&pageSize=10&type=art');
const { items, total, hasMore } = await res.json();
// Get single collection
const collection = await fetch('/api/sdk/collections/1').then(r => r.json());
| 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 |
Base URL: https://mediolano-api-service.onrender.com/api
OpenAPI Docs: Swagger UI | OpenAPI JSON
| Endpoint | Method | Description |
|---|---|---|
/assets | GET | List all assets |
/assets/{id} | GET | Get asset by ID |
/assets/owner/{owner} | GET | Get assets by owner address |
| 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
| 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 |
| 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 |
| 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 |
// External API usage example
const BASE_URL = 'https://mediolano-api-service.onrender.com/api';
// Get trending collections
const trending = await fetch(`${BASE_URL}/stats/trending?limit=10`).then(r => r.json());
// Get collections with pagination
const collections = await fetch(`${BASE_URL}/collections?limit=20&offset=0&sortBy=createdAtBlock&sortOrder=desc`).then(r => r.json());
// Get transfer history for a token
const transfers = await fetch(`${BASE_URL}/transfers/token/${tokenId}`).then(r => r.json());
| 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 |
Mediolano follows OpenSea/ERC-721 metadata standards for marketplace interoperability. All IP-specific metadata (type, license, registration, etc.) is stored in the attributes array.
{
"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..." }
]
}
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 |
{
"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" }
]
}
// Find specific attributes in metadata
const asset = await sdk.assets.getAsset(nftAddress, tokenId);
// Get license from attributes
const licenseAttr = asset.attributes?.find(a => a.trait_type === "License");
const license = licenseAttr?.value || "Unknown";
// Get type from attributes
const typeAttr = asset.attributes?.find(a => a.trait_type === "Type");
const type = typeAttr?.value || "Unknown";
// Check if remix
const isRemix = asset.attributes?.some(
a => a.trait_type === "Type" && a.value === "Remix"
);
See references/tokenization-guide.md for complete schemas.
| License | Commercial | Derivatives |
|---|---|---|
| CC0 | ✅ | ✅ |
| CC-BY | ✅ | ✅ |
| CC-BY-NC | ❌ | ✅ |
| CC-BY-ND | ✅ | ❌ |
| MIT | ✅ | ✅ |
| Apache-2.0 | ✅ | ✅ |
| All-Rights-Reserved | ❌ | ❌ |
import {
normalizeAddress,
decimalToHex,
isZeroAddress,
processIPFSUrl,
extractCID,
fetchIPFSMetadata,
processMetadataImage,
} from '@/sdk/utils';
// Address utilities
normalizeAddress('0x123...') // Normalize address format
decimalToHex('123456') // Convert decimal to hex
isZeroAddress('0x0') // Check if zero address
// IPFS utilities
processIPFSUrl('ipfs://Qm...', gateway) // Convert to gateway URL
extractCID('ipfs://Qm...') // Extract IPFS CID
await fetchIPFSMetadata(cid, gateway) // Fetch metadata from IPFS
# Starknet RPC endpoint (required)
NEXT_PUBLIC_RPC_URL=https://starknet-mainnet.g.alchemy.com/v2/KEY
# IP Collection contract address (required)
NEXT_PUBLIC_COLLECTION_CONTRACT_ADDRESS=0x...
# Network (optional, defaults to mainnet)
NEXT_PUBLIC_STARKNET_NETWORK=mainnet
# IPFS gateway (optional)
NEXT_PUBLIC_GATEWAY_URL=https://gateway.pinata.cloud
# Pinata JWT for uploads (server-side only, optional)
PINATA_JWT=eyJhbGc...
import { createSDK } from '@/sdk';
const sdk = createSDK({
rpcUrl: 'https://...',
network: 'mainnet', // 'mainnet' | 'sepolia'
collectionContractAddress: '0x...' as `0x${string}`,
timeout: 30000, // Request timeout (ms)
maxRetries: 3, // Max retry attempts
retryDelayMs: 1000, // Base retry delay
ipfsGateway: 'https://gateway.pinata.cloud',
cacheOptions: {
ttl: 30000, // Cache TTL (ms)
maxEntries: 500 // Max cache entries
},
debug: true, // Enable debug logging
});
// Cache management
sdk.clearCache();
sdk.invalidateContractCache('0xContractAddress');
See references/configuration.md for complete setup.
| Fee Type | Amount |
|---|---|
| Protocol Fee | $0 |
| Minting Fee | $0 |
| Transfer Fee | $0 |
| Gas Fee | ~0.001 STRK |
| 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.
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