Protocol Standardization
This document covers cross-collection interoperability and the standards required for Jaccard Swap to work across different NFT ecosystems.
The Interoperability Challenge
For similarity-based trading to work across collections, all participants must agree on:
- Feature extraction: How metadata becomes
key:valuepairs - Hash parameters: Seeds, k value, hash function
- Schema compliance: Which keys are required/optional
Without standardization, collections describing the same concept differently have zero similarity.
Schema Definition
A schema specifies how to extract features from metadata:
interface Schema {
name: string // "gaming-nft-v1"
version: string // "1.0.0"
requiredKeys: string[] // ["rarity", "element"]
optionalKeys: string[] // ["faction", "season"]
hashFunction: string // "keccak256"
numHashes: number // 5
seeds: string[] // MINHASH_SEEDS
}
Example Schemas
Gaming NFTs:
{
name: "gaming-nft-v1",
requiredKeys: ["rarity", "element", "class"],
optionalKeys: ["faction", "season", "generation"],
numHashes: 5,
}
Art NFTs:
{
name: "art-nft-v1",
requiredKeys: ["artist", "medium", "year"],
optionalKeys: ["style", "collection", "edition"],
numHashes: 5,
}
Relic Safari (this implementation):
{
name: "relic-safari-v1",
requiredKeys: [], // All optional
optionalKeys: ["rarity", "quality", "inscription", "age", "material", "form", "site"],
numHashes: 5,
}
Feature Extraction Rules
Key Normalization
All keys are lowercase, no spaces:
// ✗ BAD
{ "Rarity Level": "Legendary" }
// ✓ GOOD
{ rarity: "legendary" }
Value Normalization
Values are lowercase strings:
// ✗ BAD
{ attack: 100 } // Numeric
{ rarity: "LEGENDARY" } // Uppercase
// ✓ GOOD
{ attack: "100" } // String
{ rarity: "legendary" } // Lowercase
Array Handling
Arrays become multiple features:
// Input
{ elements: ["fire", "water"] }
// Extracted features
"elements:fire"
"elements:water"
Nested Objects
Flatten with dot notation:
// Input
{ stats: { attack: 100, defense: 50 } }
// Extracted features
"stats.attack:100"
"stats.defense:50"
On-Chain Registry
Contract Interface
contract SchemaRegistry {
struct Schema {
string name;
string[] requiredKeys;
string[] optionalKeys;
bytes32[] seeds;
uint8 numHashes;
address creator;
bool active;
}
// Schema ID → Schema
mapping(bytes32 => Schema) public schemas;
// Collection → Schema ID
mapping(address => bytes32) public collectionSchema;
event SchemaRegistered(bytes32 indexed schemaId, string name, address creator);
event SchemaCompliance(address indexed collection, bytes32 indexed schemaId);
function registerSchema(Schema calldata schema)
external returns (bytes32 schemaId)
{
schemaId = keccak256(abi.encode(schema.name, schema.requiredKeys, schema.seeds));
schemas[schemaId] = schema;
emit SchemaRegistered(schemaId, schema.name, msg.sender);
}
function declareCompliance(bytes32 schemaId) external {
require(schemas[schemaId].active, "Schema not active");
collectionSchema[msg.sender] = schemaId;
emit SchemaCompliance(msg.sender, schemaId);
}
}
Usage
// Register a schema
const schemaId = await schemaRegistry.registerSchema({
name: "gaming-nft-v1",
requiredKeys: ["rarity", "element"],
optionalKeys: ["faction"],
seeds: MINHASH_SEEDS,
numHashes: 5,
})
// Collection declares compliance
await myNftCollection.declareCompliance(schemaId)
// Traders query schema before bidding
const schema = await schemaRegistry.schemas(collectionSchema(nftAddress))
Cross-Collection Trading
Same Schema
Collections using the same schema are directly interoperable:
Collection A (gaming-nft-v1): { rarity: "legendary", element: "fire" }
Collection B (gaming-nft-v1): { rarity: "legendary", element: "fire" }
Similarity: 5/5 (both use same keys, same seeds)
Different Schemas
Collections with different schemas need mapping:
interface SchemaMapping {
source: bytes32 // Source schema ID
target: bytes32 // Target schema ID
keyMappings: {
sourceKey: string
targetKey: string
}[]
}
// Example: Map gaming → art
{
source: gamingSchemaId,
target: artSchemaId,
keyMappings: [
{ sourceKey: "rarity", targetKey: "edition" },
{ sourceKey: "element", targetKey: "style" },
]
}
This is an advanced feature for future development.
Verification
Client-Side
Before bidding, verify the collection follows its declared schema:
async function verifyCompliance(
nftAddress: string,
tokenId: bigint,
metadata: Record<string, any>
): Promise<boolean> {
// Get declared schema
const schemaId = await schemaRegistry.collectionSchema(nftAddress)
const schema = await schemaRegistry.schemas(schemaId)
// Check required keys present
for (const key of schema.requiredKeys) {
if (!(key in metadata)) return false
}
// Recompute MinHash
const computed = computeMinHash(metadata, schema)
const stored = await nftContract.getMinHashByTokenId(tokenId)
return computed.every((h, i) => h === stored[i])
}
On-Chain (Future)
ZK proof of schema compliance:
Prove:
1. metadata contains all required keys
2. computeMinHash(metadata) == stored_minhash
Without revealing:
metadata contents
Migration
Schema Upgrades
When upgrading schemas:
- Register new schema (v2)
- Maintain backward compatibility
- Collections can migrate incrementally
// v1 schema
{ requiredKeys: ["rarity"] }
// v2 schema (adds element, keeps rarity)
{ requiredKeys: ["rarity", "element"] }
// Existing v1 NFTs remain valid
// New mints follow v2
MinHash Reminting
If schema changes require new MinHashes:
function remint(uint256 tokenId, bytes32[5] calldata newMinHash) external {
require(msg.sender == ownerOf(tokenId), "Not owner");
minHashes[tokenId] = newMinHash;
emit MinHashUpdated(tokenId, newMinHash);
}
Best Practices
For Collection Creators
- Choose established schemas when possible
- Document deviations from standard schemas
- Compute MinHash at mint (not later)
- Test interoperability before launch
For Traders
- Verify schema compliance before large bids
- Understand schema limitations (what traits matter)
- Use standing bids wisely (they match ANY compliant NFT)
For Protocol Developers
- Minimize required keys (more flexibility)
- Version schemas (backward compatibility)
- Publish seeds publicly (reproducibility)
Governance
Schema governance determines:
- Who can register schemas: Open vs permissioned
- Schema modification: Immutable vs upgradeable
- Dispute resolution: What if schema is violated?
Current implementation: Open registration, immutable schemas, no on-chain dispute resolution.
Future Work
- Schema marketplace: Discover and compare schemas
- Compliance proofs: ZK verification of schema adherence
- Cross-chain schemas: Same schemas across L1/L2
- Schema reputation: Track which schemas have wide adoption
Next Steps
- Deep Dive — dimensionality and optimization
- Introduction — back to basics