defarm-sdk
Version:
DeFarm SDK - On-premise blockchain data processing and tokenization engine for agriculture supply chain
439 lines (377 loc) β’ 12.5 kB
JavaScript
/**
* Blockchain Interface for DeFarm SDK
* AgnΓ³stic blockchain interface that connects to DeFarm's existing contracts
* WITHOUT exposing private keys to clients
*/
const { StellarSDK } = require('stellar-sdk');
const { ethers } = require('ethers');
class BlockchainInterface {
constructor(config = {}) {
this.config = {
network: config.network || 'stellar', // stellar, polygon, ethereum, etc
mode: config.mode || 'relay', // relay (no keys), direct (with keys)
relayUrl: config.relayUrl || process.env.DEFARM_RELAY_URL,
apiKey: config.apiKey || process.env.DEFARM_API_KEY,
clientId: config.clientId || process.env.DEFARM_CLIENT_ID,
// Contract addresses from DeFarm backbone
contracts: {
stellar: {
assetIssuer: process.env.STELLAR_ASSET_ISSUER,
distributorAccount: process.env.STELLAR_DISTRIBUTOR
},
polygon: {
assetRegistry: process.env.POLYGON_ASSET_REGISTRY,
tokenFactory: process.env.POLYGON_TOKEN_FACTORY,
supplyChain: process.env.POLYGON_SUPPLY_CHAIN
},
ethereum: {
assetRegistry: process.env.ETH_ASSET_REGISTRY,
tokenFactory: process.env.ETH_TOKEN_FACTORY
}
},
...config
};
// NO PRIVATE KEYS STORED HERE!
// All signing happens on DeFarm's secure backend
this.initialized = false;
this.networkHandlers = new Map();
}
/**
* Initialize blockchain interface
*/
async initialize() {
if (this.initialized) return;
console.log(`π Initializing Blockchain Interface (${this.config.network})...`);
// Setup network-specific handlers
this.setupNetworkHandlers();
// Verify relay connection
if (this.config.mode === 'relay') {
await this.verifyRelayConnection();
}
this.initialized = true;
console.log('β
Blockchain Interface ready (no private keys required!)');
}
/**
* Setup network-specific handlers
*/
setupNetworkHandlers() {
// Stellar handler
this.networkHandlers.set('stellar', {
name: 'Stellar',
createAsset: this.createStellarAsset.bind(this),
recordEvent: this.recordStellarEvent.bind(this),
verifyAsset: this.verifyStellarAsset.bind(this),
getExplorer: (hash) => `https://stellar.expert/explorer/public/tx/${hash}`
});
// Polygon handler
this.networkHandlers.set('polygon', {
name: 'Polygon',
createAsset: this.createEVMAsset.bind(this, 'polygon'),
recordEvent: this.recordEVMEvent.bind(this, 'polygon'),
verifyAsset: this.verifyEVMAsset.bind(this, 'polygon'),
getExplorer: (hash) => `https://polygonscan.com/tx/${hash}`
});
// Ethereum handler
this.networkHandlers.set('ethereum', {
name: 'Ethereum',
createAsset: this.createEVMAsset.bind(this, 'ethereum'),
recordEvent: this.recordEVMEvent.bind(this, 'ethereum'),
verifyAsset: this.verifyEVMAsset.bind(this, 'ethereum'),
getExplorer: (hash) => `https://etherscan.io/tx/${hash}`
});
}
/**
* Verify relay connection
*/
async verifyRelayConnection() {
try {
const response = await fetch(`${this.config.relayUrl}/health`, {
headers: {
'X-API-Key': this.config.apiKey,
'X-Client-ID': this.config.clientId
}
});
if (!response.ok) {
throw new Error('Relay connection failed');
}
const data = await response.json();
console.log(` β Connected to DeFarm Relay: ${data.version}`);
console.log(` β Network: ${data.network}`);
console.log(` β Mode: Gas-free transactions enabled`);
} catch (error) {
console.warn(' β Relay not available, some features may be limited');
}
}
/**
* Create asset on blockchain (gas-free for client)
*/
async createAsset(assetData, clientAddress, options = {}) {
const handler = this.networkHandlers.get(this.config.network);
if (!handler) {
throw new Error(`Unsupported network: ${this.config.network}`);
}
console.log(`π Creating asset on ${handler.name}...`);
console.log(` Client: ${clientAddress} (no gas required)`);
// Route through relay for gas-free transaction
if (this.config.mode === 'relay') {
return await this.relayTransaction({
type: 'CREATE_ASSET',
network: this.config.network,
data: assetData,
owner: clientAddress,
options
});
}
// Direct mode (only for DeFarm's own operations)
return await handler.createAsset(assetData, clientAddress, options);
}
/**
* Record event on blockchain (gas-free for client)
*/
async recordEvent(eventData, clientAddress, options = {}) {
const handler = this.networkHandlers.get(this.config.network);
if (!handler) {
throw new Error(`Unsupported network: ${this.config.network}`);
}
console.log(`π Recording event on ${handler.name}...`);
// Route through relay for gas-free transaction
if (this.config.mode === 'relay') {
return await this.relayTransaction({
type: 'RECORD_EVENT',
network: this.config.network,
data: eventData,
owner: clientAddress,
options
});
}
return await handler.recordEvent(eventData, clientAddress, options);
}
/**
* Verify asset on blockchain (read-only, no gas needed)
*/
async verifyAsset(assetId, expectedHash) {
const handler = this.networkHandlers.get(this.config.network);
if (!handler) {
throw new Error(`Unsupported network: ${this.config.network}`);
}
// Read operations don't need gas
return await handler.verifyAsset(assetId, expectedHash);
}
/**
* Relay transaction through DeFarm backend
*/
async relayTransaction(request) {
const payload = {
id: this.generateRequestId(),
timestamp: Date.now(),
client: {
id: this.config.clientId,
address: request.owner
},
transaction: {
type: request.type,
network: request.network,
data: request.data,
options: request.options
},
signature: this.signRequest(request)
};
try {
const response = await fetch(`${this.config.relayUrl}/transaction`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.config.apiKey,
'X-Client-ID': this.config.clientId
},
body: JSON.stringify(payload)
});
const result = await response.json();
if (result.status === 'success') {
console.log('β
Transaction sponsored by DeFarm');
console.log(` Hash: ${result.transactionHash}`);
console.log(` Gas saved: ${result.gasCost}`);
return {
success: true,
transactionHash: result.transactionHash,
blockNumber: result.blockNumber,
network: result.network,
gasSaved: result.gasCost,
explorerUrl: this.getExplorerUrl(result.network, result.transactionHash)
};
} else {
throw new Error(result.error || 'Transaction failed');
}
} catch (error) {
console.error('Relay transaction failed:', error);
throw error;
}
}
// ===== STELLAR IMPLEMENTATIONS =====
async createStellarAsset(assetData, clientAddress, options) {
// Stellar-specific implementation
// This would be called by DeFarm backend, not directly by client
const request = {
operation: 'createAsset',
assetCode: assetData.asset_code || 'DEFARM',
issuer: this.config.contracts.stellar.assetIssuer,
distributor: this.config.contracts.stellar.distributorAccount,
destination: clientAddress,
amount: assetData.amount || '1',
memo: JSON.stringify({
type: assetData.asset_type,
id: assetData.asset_id
})
};
// This goes to DeFarm backend which has the keys
return await this.sendToBackend(request);
}
async recordStellarEvent(eventData, clientAddress, options) {
// Store event data in Stellar memo or use Soroban smart contract
const request = {
operation: 'recordEvent',
account: clientAddress,
memo: JSON.stringify({
event: eventData.event_type,
asset: eventData.asset_id,
timestamp: eventData.timestamp
})
};
return await this.sendToBackend(request);
}
async verifyStellarAsset(assetId, expectedHash) {
// Query Stellar Horizon API (public, no keys needed)
try {
const server = new StellarSDK.Server('https://horizon.stellar.org');
// Query logic here
return true;
} catch (error) {
return false;
}
}
// ===== EVM IMPLEMENTATIONS =====
async createEVMAsset(network, assetData, clientAddress, options) {
const contracts = this.config.contracts[network];
const request = {
operation: 'createAsset',
contract: contracts.assetRegistry,
method: 'createAsset',
params: [
assetData.asset_id,
assetData.asset_type,
clientAddress,
this.hashData(assetData)
]
};
return await this.sendToBackend(request);
}
async recordEVMEvent(network, eventData, clientAddress, options) {
const contracts = this.config.contracts[network];
const request = {
operation: 'recordEvent',
contract: contracts.supplyChain,
method: 'recordEvent',
params: [
eventData.asset_id,
eventData.event_type,
eventData.actor,
this.hashData(eventData)
]
};
return await this.sendToBackend(request);
}
async verifyEVMAsset(network, assetId, expectedHash) {
// Read from blockchain (no gas needed)
const contracts = this.config.contracts[network];
const provider = new ethers.JsonRpcProvider(this.getRpcUrl(network));
// ABI for view function
const abi = ['function getAsset(bytes32) view returns (address, string, string, uint256)'];
const contract = new ethers.Contract(contracts.assetRegistry, abi, provider);
try {
const asset = await contract.getAsset(assetId);
const onChainHash = asset[2]; // Assuming hash is third return value
return onChainHash === expectedHash;
} catch (error) {
return false;
}
}
// ===== HELPER METHODS =====
/**
* Send request to DeFarm backend (which has the keys)
*/
async sendToBackend(request) {
// This would communicate with DeFarm's secure backend
// The backend holds all private keys and signs transactions
console.log('π€ Sending to DeFarm backend for signing...');
// Mock response for now
return {
success: true,
transactionHash: '0x' + this.generateRequestId(),
message: 'Transaction will be processed by DeFarm'
};
}
/**
* Get RPC URL for network
*/
getRpcUrl(network) {
const urls = {
'polygon': 'https://polygon-rpc.com',
'ethereum': 'https://eth.llamarpc.com',
'bsc': 'https://bsc-dataseed.binance.org'
};
return urls[network];
}
/**
* Hash data
*/
hashData(data) {
const crypto = require('crypto');
return '0x' + crypto.createHash('sha256')
.update(JSON.stringify(data))
.digest('hex');
}
/**
* Sign request (for authentication, not blockchain signing)
*/
signRequest(request) {
const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', this.config.apiKey || 'secret');
hmac.update(JSON.stringify(request));
return hmac.digest('hex');
}
/**
* Generate request ID
*/
generateRequestId() {
const crypto = require('crypto');
return crypto.randomBytes(16).toString('hex');
}
/**
* Get explorer URL
*/
getExplorerUrl(network, txHash) {
const handler = this.networkHandlers.get(network);
return handler ? handler.getExplorer(txHash) : null;
}
/**
* Get supported networks
*/
getSupportedNetworks() {
return Array.from(this.networkHandlers.keys());
}
/**
* Switch network
*/
async switchNetwork(network) {
if (!this.networkHandlers.has(network)) {
throw new Error(`Unsupported network: ${network}`);
}
this.config.network = network;
console.log(`π Switched to ${network}`);
// Re-verify relay for new network
if (this.config.mode === 'relay') {
await this.verifyRelayConnection();
}
}
}
module.exports = { BlockchainInterface };