defarm-sdk
Version:
DeFarm SDK - On-premise blockchain data processing and tokenization engine for agriculture supply chain
657 lines (568 loc) • 17.7 kB
JavaScript
const crypto = require('crypto');
const { ethers } = require('ethers');
/**
* Tokenization Engine for DeFarm SDK
* Handles asset tokenization, NFT creation, and token management
*/
class TokenizationEngine {
constructor(config = {}) {
this.config = {
tokenStandard: config.tokenStandard || 'ERC721', // ERC721, ERC1155, ERC20
metadataStandard: config.metadataStandard || 'opensea', // opensea, defarm
baseUri: config.baseUri || 'https://api.defarm.io/metadata/',
royaltyPercentage: config.royaltyPercentage || 2.5,
maxSupply: config.maxSupply || 10000,
decimals: config.decimals || 18,
...config
};
this.blockchainEngine = config.blockchain;
this.databaseManager = config.database;
// Token registry
this.tokens = new Map();
this.tokenMetadata = new Map();
this.tokenOwners = new Map();
// Token templates for different asset types
this.tokenTemplates = {
livestock: {
name: 'DeFarm Livestock Token',
symbol: 'DFLIVE',
standard: 'ERC721',
attributes: ['breed', 'age', 'weight', 'health_score', 'location', 'certifications']
},
crop: {
name: 'DeFarm Crop Token',
symbol: 'DFCROP',
standard: 'ERC1155',
attributes: ['crop_type', 'variety', 'quantity', 'quality_score', 'harvest_date', 'location']
},
land: {
name: 'DeFarm Land Token',
symbol: 'DFLAND',
standard: 'ERC721',
attributes: ['location', 'area', 'soil_type', 'water_rights', 'usage_rights']
},
produce: {
name: 'DeFarm Produce Token',
symbol: 'DFPROD',
standard: 'ERC1155',
attributes: ['product_type', 'quantity', 'quality', 'origin', 'certifications', 'expiry']
},
carbon_credit: {
name: 'DeFarm Carbon Credit',
symbol: 'DFCARB',
standard: 'ERC20',
attributes: ['tons_co2', 'project', 'verification', 'vintage_year']
}
};
// Statistics
this.stats = {
totalTokensCreated: 0,
totalTokensBurned: 0,
totalTransfers: 0,
tokensByType: {},
averageTokenValue: 0
};
}
/**
* Create token for asset
*/
async createToken(assetData, options = {}) {
console.log('🪙 Creating token for asset:', assetData.asset_id);
// Determine token template
const template = this.tokenTemplates[assetData.asset_type] || this.getDefaultTemplate();
// Generate token ID
const tokenId = await this.generateTokenId(assetData);
// Prepare metadata
const metadata = await this.prepareMetadata(assetData, template, options);
// Store metadata (IPFS simulation)
const metadataUri = await this.storeMetadata(tokenId, metadata);
// Mint token on blockchain if configured
let blockchainResult = null;
if (this.blockchainEngine && options.blockchain !== false) {
blockchainResult = await this.mintOnBlockchain(
tokenId,
assetData,
metadataUri,
options
);
}
// Create token object
const token = {
tokenId,
assetId: assetData.asset_id,
assetType: assetData.asset_type,
standard: template.standard,
owner: options.owner || assetData.owner_address || 'pending',
amount: this.calculateTokenAmount(assetData, template),
decimals: template.standard === 'ERC20' ? this.config.decimals : 0,
metadata,
metadataUri,
blockchainTx: blockchainResult?.transactionHash,
createdAt: Date.now(),
status: 'active',
history: [{
event: 'minted',
from: '0x0000000000000000000000000000000000000000',
to: options.owner || assetData.owner_address,
timestamp: Date.now(),
txHash: blockchainResult?.transactionHash
}]
};
// Store token
this.tokens.set(tokenId, token);
this.tokenMetadata.set(tokenId, metadata);
// Update owner registry
this.updateOwnerRegistry(token.owner, tokenId, 'add');
// Store in database if configured
if (this.databaseManager) {
await this.databaseManager.storeToken(token);
}
// Update statistics
this.stats.totalTokensCreated++;
this.stats.tokensByType[assetData.asset_type] =
(this.stats.tokensByType[assetData.asset_type] || 0) + 1;
console.log(`✅ Token created: ${tokenId}`);
return token;
}
/**
* Generate token from data
*/
async generateToken(data, options = {}) {
// This is called by the SDK for quick token generation
const tokenData = {
...data,
asset_id: data.asset_id || this.generateAssetId(data),
asset_type: data.asset_type || 'generic'
};
return await this.createToken(tokenData, options);
}
/**
* Transfer token
*/
async transferToken(tokenId, fromAddress, toAddress, amount = 1, options = {}) {
const token = this.tokens.get(tokenId);
if (!token) {
throw new Error(`Token not found: ${tokenId}`);
}
// Validate ownership
if (token.owner !== fromAddress) {
throw new Error('Sender does not own this token');
}
// For ERC1155/ERC20, check balance
if (token.standard !== 'ERC721' && amount > token.amount) {
throw new Error('Insufficient token balance');
}
// Execute blockchain transfer if configured
let blockchainResult = null;
if (this.blockchainEngine && options.blockchain !== false) {
blockchainResult = await this.blockchainEngine.transferAsset(
token.assetId,
toAddress,
options
);
}
// Update token ownership
if (token.standard === 'ERC721') {
token.owner = toAddress;
this.updateOwnerRegistry(fromAddress, tokenId, 'remove');
this.updateOwnerRegistry(toAddress, tokenId, 'add');
} else {
// For fungible tokens, handle partial transfers
// This would require more complex balance tracking
token.owner = toAddress; // Simplified for now
}
// Add to history
token.history.push({
event: 'transfer',
from: fromAddress,
to: toAddress,
amount,
timestamp: Date.now(),
txHash: blockchainResult?.transactionHash
});
// Update database if configured
if (this.databaseManager) {
await this.databaseManager.storeToken(token);
}
// Update statistics
this.stats.totalTransfers++;
console.log(`✅ Token transferred: ${tokenId} from ${fromAddress} to ${toAddress}`);
return {
success: true,
tokenId,
from: fromAddress,
to: toAddress,
amount,
txHash: blockchainResult?.transactionHash
};
}
/**
* Burn token
*/
async burnToken(tokenId, options = {}) {
const token = this.tokens.get(tokenId);
if (!token) {
throw new Error(`Token not found: ${tokenId}`);
}
// Mark as burned
token.status = 'burned';
token.burnedAt = Date.now();
// Add to history
token.history.push({
event: 'burned',
from: token.owner,
to: '0x0000000000000000000000000000000000000000',
timestamp: Date.now()
});
// Remove from owner registry
this.updateOwnerRegistry(token.owner, tokenId, 'remove');
// Update statistics
this.stats.totalTokensBurned++;
console.log(`🔥 Token burned: ${tokenId}`);
return {
success: true,
tokenId,
burnedAt: token.burnedAt
};
}
/**
* Get token by ID
*/
getToken(tokenId) {
return this.tokens.get(tokenId);
}
/**
* Get tokens by owner
*/
getTokensByOwner(ownerAddress) {
const tokenIds = this.tokenOwners.get(ownerAddress) || [];
return tokenIds.map(id => this.tokens.get(id)).filter(Boolean);
}
/**
* Get tokens by asset type
*/
getTokensByType(assetType) {
const tokens = [];
for (const token of this.tokens.values()) {
if (token.assetType === assetType && token.status === 'active') {
tokens.push(token);
}
}
return tokens;
}
/**
* Verify token authenticity
*/
async verifyToken(tokenId, data = {}) {
const token = this.tokens.get(tokenId);
if (!token) {
return { valid: false, error: 'Token not found' };
}
// Verify metadata hash
const metadataHash = this.calculateHash(token.metadata);
const expectedHash = this.calculateHash(this.tokenMetadata.get(tokenId));
if (metadataHash !== expectedHash) {
return { valid: false, error: 'Metadata mismatch' };
}
// Verify on blockchain if configured
if (this.blockchainEngine && data.verifyOnChain) {
const isValid = await this.blockchainEngine.verifyData(
token.assetId,
token.metadata
);
if (!isValid) {
return { valid: false, error: 'Blockchain verification failed' };
}
}
return {
valid: true,
token,
verifiedAt: Date.now()
};
}
/**
* Prepare token metadata
*/
async prepareMetadata(assetData, template, options = {}) {
const metadata = {
name: `${template.name} #${assetData.asset_id}`,
description: options.description || `${template.name} representing ${assetData.asset_type}`,
image: options.image || await this.generateTokenImage(assetData),
external_url: `${this.config.baseUri}${assetData.asset_id}`,
attributes: []
};
// Add standard attributes
for (const attr of template.attributes) {
if (assetData[attr] !== undefined) {
metadata.attributes.push({
trait_type: this.formatTraitType(attr),
value: assetData[attr],
display_type: this.getDisplayType(attr, assetData[attr])
});
}
}
// Add custom attributes
if (options.customAttributes) {
for (const [key, value] of Object.entries(options.customAttributes)) {
metadata.attributes.push({
trait_type: this.formatTraitType(key),
value: value
});
}
}
// Add certifications as attributes
if (assetData.certifications && Array.isArray(assetData.certifications)) {
for (const cert of assetData.certifications) {
metadata.attributes.push({
trait_type: 'Certification',
value: cert.cert_type || cert
});
}
}
// Add sustainability metrics
if (assetData.metrics) {
if (assetData.metrics.sustainability_index) {
metadata.attributes.push({
trait_type: 'Sustainability Index',
value: assetData.metrics.sustainability_index,
display_type: 'boost_percentage',
max_value: 1
});
}
if (assetData.metrics.carbon_footprint) {
metadata.attributes.push({
trait_type: 'Carbon Footprint',
value: assetData.metrics.carbon_footprint,
display_type: 'number',
unit: 'kg CO2'
});
}
}
// Add blockchain properties
metadata.properties = {
category: assetData.asset_type,
created_at: Date.now(),
creator: options.creator || assetData.owner_address,
royalties: {
recipient: options.royaltyRecipient || assetData.owner_address,
percentage: this.config.royaltyPercentage
}
};
// Add location if available
if (assetData.location) {
metadata.properties.location = {
latitude: assetData.location.latitude,
longitude: assetData.location.longitude,
region: assetData.location.region,
country: assetData.location.country
};
}
return metadata;
}
/**
* Store metadata (IPFS simulation)
*/
async storeMetadata(tokenId, metadata) {
// In production, this would upload to IPFS
// For now, simulate with hash-based URI
const hash = this.calculateHash(metadata);
const uri = `ipfs://${hash}`;
// Store locally for retrieval
this.tokenMetadata.set(tokenId, metadata);
return uri;
}
/**
* Mint token on blockchain
*/
async mintOnBlockchain(tokenId, assetData, metadataUri, options = {}) {
if (!this.blockchainEngine) {
console.warn('Blockchain engine not configured');
return null;
}
try {
const result = await this.blockchainEngine.mintToken(
assetData.asset_id,
options.owner || assetData.owner_address,
options.amount || 1,
metadataUri
);
return result;
} catch (error) {
console.error('Failed to mint on blockchain:', error);
throw error;
}
}
/**
* Calculate token amount based on asset data
*/
calculateTokenAmount(assetData, template) {
if (template.standard === 'ERC721') {
return 1; // NFTs are unique
}
// For fungible tokens, base on quantity
if (assetData.metrics && assetData.metrics.quantity) {
return Math.floor(assetData.metrics.quantity * Math.pow(10, this.config.decimals));
}
// Default amount
return Math.pow(10, this.config.decimals);
}
/**
* Generate token ID
*/
async generateTokenId(assetData) {
const input = {
assetId: assetData.asset_id,
assetType: assetData.asset_type,
timestamp: Date.now(),
nonce: crypto.randomBytes(16).toString('hex')
};
const hash = this.calculateHash(input);
return `TKN-${hash.substring(0, 16).toUpperCase()}`;
}
/**
* Generate asset ID
*/
generateAssetId(data) {
const hash = crypto.createHash('sha256');
hash.update(JSON.stringify({
type: data.asset_type,
timestamp: Date.now(),
random: crypto.randomBytes(8).toString('hex')
}));
return `ASSET-${hash.digest('hex').substring(0, 12).toUpperCase()}`;
}
/**
* Generate token image
*/
async generateTokenImage(assetData) {
// This would generate or retrieve an image for the token
// For now, return a placeholder based on asset type
const images = {
livestock: 'https://api.defarm.io/images/livestock-default.png',
crop: 'https://api.defarm.io/images/crop-default.png',
land: 'https://api.defarm.io/images/land-default.png',
produce: 'https://api.defarm.io/images/produce-default.png'
};
return images[assetData.asset_type] || 'https://api.defarm.io/images/default.png';
}
/**
* Update owner registry
*/
updateOwnerRegistry(owner, tokenId, action) {
if (!this.tokenOwners.has(owner)) {
this.tokenOwners.set(owner, []);
}
const tokens = this.tokenOwners.get(owner);
if (action === 'add') {
if (!tokens.includes(tokenId)) {
tokens.push(tokenId);
}
} else if (action === 'remove') {
const index = tokens.indexOf(tokenId);
if (index > -1) {
tokens.splice(index, 1);
}
}
}
/**
* Get default token template
*/
getDefaultTemplate() {
return {
name: 'DeFarm Asset Token',
symbol: 'DFARM',
standard: 'ERC721',
attributes: ['type', 'quantity', 'quality', 'location', 'timestamp']
};
}
/**
* Format trait type for metadata
*/
formatTraitType(attribute) {
return attribute
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
/**
* Get display type for attribute
*/
getDisplayType(attribute, value) {
if (typeof value === 'number') {
if (attribute.includes('score') || attribute.includes('index')) {
return 'boost_percentage';
}
if (attribute.includes('date') || attribute.includes('time')) {
return 'date';
}
return 'number';
}
return null;
}
/**
* Calculate hash
*/
calculateHash(data) {
const hash = crypto.createHash('sha256');
hash.update(JSON.stringify(data));
return hash.digest('hex');
}
/**
* Get tokenization statistics
*/
getStats() {
return {
...this.stats,
activeTokens: Array.from(this.tokens.values()).filter(t => t.status === 'active').length,
totalOwners: this.tokenOwners.size,
tokensPerOwner: this.tokenOwners.size > 0
? this.stats.totalTokensCreated / this.tokenOwners.size
: 0
};
}
/**
* Export token data
*/
exportTokenData(tokenId, format = 'json') {
const token = this.tokens.get(tokenId);
if (!token) {
throw new Error(`Token not found: ${tokenId}`);
}
switch (format) {
case 'json':
return JSON.stringify(token, null, 2);
case 'metadata':
return JSON.stringify(token.metadata, null, 2);
case 'certificate':
return this.generateCertificate(token);
default:
throw new Error(`Unsupported format: ${format}`);
}
}
/**
* Generate certificate for token
*/
generateCertificate(token) {
return `
====================================
DEFARM ASSET CERTIFICATE
====================================
Token ID: ${token.tokenId}
Asset ID: ${token.assetId}
Asset Type: ${token.assetType}
Standard: ${token.standard}
Owner: ${token.owner}
Created: ${new Date(token.createdAt).toISOString()}
Status: ${token.status}
Metadata URI: ${token.metadataUri}
Blockchain TX: ${token.blockchainTx || 'N/A'}
ATTRIBUTES:
${token.metadata.attributes.map(a => ` - ${a.trait_type}: ${a.value}`).join('\n')}
====================================
This certificate verifies the authenticity
of the DeFarm tokenized asset.
====================================
`.trim();
}
}
module.exports = { TokenizationEngine };