This commit is contained in:
Rytek Digital Inc
2025-08-06 17:19:17 -03:00
parent 43a73d62a0
commit 2c46b8613b
7 changed files with 473 additions and 22 deletions

View File

@@ -39,7 +39,7 @@ Parse.Cloud.define('getBankBalance', async (request) => {
const record = await query.first({ useMasterKey: true });
// console.log('record', record.get('kash'));
if (!record) {
log.warning(`No bank records found for user: ${params.userId}`);
// No bank records is normal for new users - return default values without logging
return {
success: true,
data: {

View File

@@ -2,6 +2,9 @@ const { createCanvas, loadImage } = require('canvas');
const fs = require('fs').promises;
const path = require('path');
// Import the correct function name from checkOwnership.js
const { batchVerifyOwnership } = require('../../../utility/web3/chia/checkOwnership');
// Logging utilities
const log = {
section: (title) => console.log(`\n🔸 ${'-'.repeat(20)} ${title} ${'-'.repeat(20)}`),
@@ -333,6 +336,93 @@ Parse.Cloud.define("breedAssetsById", async (request) => {
throw new Error('Could not find one or both parent Dracattus. Check dracattus1 and dracattus2 parameters.');
}
log.success('Both parent Dracattus found successfully');
// Check for ownership using encoded_ids - Only if wallet was provided
if (wallet) {
log.section('Ownership Verification');
log.debug(`Starting ownership verification for wallet: ${wallet}`);
// First check if godMode is enabled - if it is, skip ownership checks
let godMode = false;
try {
const config = await Parse.Config.get({ useMasterKey: true });
godMode = config.get('godMode') === true;
log.debug(`Parse Config loaded successfully, godMode value: ${godMode}`);
if (godMode) {
log.success('GOD MODE enabled - bypassing ownership verification');
} else {
log.info('GOD MODE not enabled - verifying ownership');
}
} catch (error) {
log.error(`Error checking godMode config: ${error.message}`);
log.debug(`Config error details: ${JSON.stringify(error)}`);
}
// If not in godMode, proceed with ownership check
if (!godMode) {
try {
// Get encoded_ids from both Dracattus
const encoded_id1 = parent1.get('encoded_id');
const encoded_id2 = parent2.get('encoded_id');
log.debug(`Retrieved encoded_ids: [${encoded_id1}, ${encoded_id2}]`);
// Check if encoded_ids exist before proceeding
if (!encoded_id1 || !encoded_id2) {
log.error('One or both Dracattus are missing encoded_id (launcher_bech32)');
// Display which encoded_ids are missing for better debugging
const missingIdsInfo = {
'Dracattus 1': {
id: parent1.id,
name: parent1.get('name'),
encoded_id: encoded_id1 || 'MISSING'
},
'Dracattus 2': {
id: parent2.id,
name: parent2.get('name'),
encoded_id: encoded_id2 || 'MISSING'
}
};
console.table(missingIdsInfo);
throw new Error('Cannot verify ownership: One or both Dracattus are missing encoded_id (launcher_bech32)');
}
log.info(`Verifying ownership of Dracattus NFTs (${encoded_id1}, ${encoded_id2}) by wallet ${wallet}`);
log.debug(`Calling batchVerifyOwnership with wallet=${wallet} and encoded_ids=[${encoded_id1}, ${encoded_id2}]`);
// Call the batchVerifyOwnership function to check both NFTs at once
const { allOwned, results } = await batchVerifyOwnership(wallet, [encoded_id1, encoded_id2]);
log.debug(`Verification results received: ${JSON.stringify({ allOwned, results })}`);
// Display results in a table
console.table({
[encoded_id1]: results[encoded_id1] ? 'Owned ✓' : 'Not Owned ✗',
[encoded_id2]: results[encoded_id2] ? 'Owned ✓' : 'Not Owned ✗'
});
if (!allOwned) {
log.error('Ownership verification failed - user does not own both NFTs');
throw new Error('You do not own both of these Dracattus NFTs');
}
log.success('Ownership verification passed - proceeding with breeding');
} catch (verificationError) {
log.error(`Ownership verification failed: ${verificationError.message}`);
log.debug(`Error stack: ${verificationError.stack}`);
throw new Error(`Ownership verification error: ${verificationError.message}`);
}
}
} else {
log.warning('No wallet address provided - skipping ownership verification');
}
// Check if either parent has reached their monthly breeding limit
const parent1MintBalance = parent1.get('mintBalance') ?? 0;
const parent2MintBalance = parent2.get('mintBalance') ?? 0;

View File

@@ -0,0 +1,132 @@
Parse.Cloud.define("getDracattusByNftId", async (request) => {
try {
const { nft_id } = request.params;
// Validate input exists
if (!nft_id) {
throw new Error("Missing required parameter: nft_id");
}
// Validate nft_id is a non-empty string
if (typeof nft_id !== 'string' || nft_id.trim() === '') {
throw new Error("nft_id must be a non-empty string");
}
// Validate nft_id format (64-character hex string)
const nftIdPattern = /^[a-f0-9]{64}$/i;
if (!nftIdPattern.test(nft_id)) {
throw new Error("nft_id must be a 64-character hex string");
}
// Create query for DRACATTUS class
const query = new Parse.Query("DRACATTUS");
// Add constraint for the nft_id field
query.equalTo("nft_id", nft_id);
// Include all fields we might need
query.select([
"name",
"encoded_id",
"nft_id",
"taxonomy",
"edition",
"syncscore",
"prowess",
"location",
"background",
"base",
"wings",
"belly",
"fur",
"ears",
"eyes",
"horns",
"mouth",
"tail_tip",
"attributes",
"chain",
"thumbnail_uri",
"is_owned",
"health",
"attack",
"defense",
"parent1",
"parent2",
]);
// Execute query
const results = await query.find({ useMasterKey: true });
// Check if any results were found
if (results.length === 0) {
return {
success: false,
error: "No DRACATTUS record found for the provided nft_id",
queried_nft_id: nft_id
};
}
// For nft_id, we expect only one result since it should be unique
const record = results[0];
const thumbnailUri = record.get("thumbnail_uri");
const formattedResult = {
objectId: record.id,
name: record.get("name"),
thumbnail_uri: thumbnailUri ? (thumbnailUri._url || thumbnailUri.url || thumbnailUri) : null,
encoded_id: record.get("encoded_id"),
nft_id: record.get("nft_id"),
taxonomy: record.get("taxonomy"),
edition: record.get("edition"),
syncscore: record.get("syncopate"),
prowess: record.get("prowess"),
location: record.get("location"),
background: record.get("background"),
base: record.get("base"),
wings: record.get("wings"),
belly: record.get("belly"),
fur: record.get("fur"),
ears: record.get("ears"),
eyes: record.get("eyes"),
horns: record.get("horns"),
mouth: record.get("mouth"),
tail_tip: record.get("tail_tip"),
attributes: record.get("attributes"),
chain: record.get("chain"),
createdAt: record.createdAt,
updatedAt: record.updatedAt,
// Add metadata structure for compatibility with view-asset page
metadata: {
attributes: record.get("attributes"),
details: {
background: record.get("background"),
base: record.get("base"),
wings: record.get("wings"),
belly: record.get("belly"),
fur: record.get("fur"),
ears: record.get("ears"),
eyes: record.get("eyes"),
horns: record.get("horns"),
mouth: record.get("mouth"),
tail_tip: record.get("tail_tip"),
}
}
};
return {
success: true,
results: [formattedResult],
total: 1,
queried_nft_id: nft_id
};
} catch (error) {
console.error("Error in getDracattusByNftId:", error);
return {
success: false,
error: error.message,
queried_nft_id: nft_id
};
}
});

View File

@@ -14,6 +14,107 @@ const generateSHA256Hash = async (url) => {
}
};
// Helper function to create split royalty address using splitxch API
const createSplitRoyaltyAddress = async (originalAddress, targetAddress, minterPoints) => {
try {
const originalPoints = 9850 - minterPoints; // Total available minus minter share
const payload = {
recipients: [
{
name: "original",
address: originalAddress,
points: originalPoints,
id: 1
},
{
name: "minter",
address: targetAddress,
points: minterPoints,
id: 2
}
]
};
console.log(` 🔧 Payload Details:`);
console.log(` 📍 Original Wallet: ${originalAddress}`);
console.log(` 📍 Minter Wallet: ${targetAddress}`);
console.log(` 📊 Original Points: ${originalPoints.toLocaleString()}`);
console.log(` 📊 Minter Points: ${minterPoints.toLocaleString()}`);
console.log(` 🌐 Sending request to SplitXCH API...`);
const response = await fetch('https://splitxch.com/api/compute/fast', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorText = await response.text();
console.log(` ❌ SplitXCH API Error (${response.status}): ${errorText}`);
throw new Error(`SplitXCH API error (${response.status}): ${errorText}`);
}
const result = await response.json();
if (result.address) {
console.log(` 🎊 SplitXCH API Response Success!`);
console.log(` 🏦 Split Address: ${result.address}`);
console.log(` 🆔 Split ID: ${result.id}`);
console.log(` 📈 Progress: ${result.pctProgress || 'N/A'}%`);
return {
address: result.address,
splitId: result.id,
originalPoints,
minterPoints
};
} else {
console.log(` ❌ Invalid SplitXCH Response: ${JSON.stringify(result)}`);
throw new Error(`SplitXCH API did not return an address: ${JSON.stringify(result)}`);
}
} catch (error) {
console.log(` ⚠️ SPLITXCH ERROR DETAILS:`);
console.error(` 🚨 Error Type: ${error.name || 'Unknown'}`);
console.error(` 💬 Message: ${error.message}`);
if (error.stack) {
console.error(` 📋 Stack: ${error.stack.split('\n')[0]}`);
}
throw error;
}
};
// Helper function to calculate minter's royalty share based on magic field
const calculateMinterRoyaltyShare = (magicValue) => {
// Ensure magic value is within valid range
const clampedMagic = Math.max(0, Math.min(100, magicValue || 0));
console.log(` 🧮 CALCULATION BREAKDOWN:`);
console.log(` 🔢 Raw Magic Value: ${magicValue}`);
console.log(` ⚖️ Clamped Magic: ${clampedMagic} (0-100 range)`);
console.log(` 🏆 Available Points: 9,850 (10,000 - 150 platform fee)`);
console.log(` 📐 Formula: Magic × 49.25 = Minter Points`);
// Calculate minter's share as percentage of available points (max 50%)
// Available points: 9850 (10000 - 150 platform fee)
// Minter gets: (magic / 100) * 50% of available points
const minterPoints = Math.round(clampedMagic * 49.25); // 49.25 = 9850 * 0.5 / 100
const result = {
minterPoints,
minterPercentage: (minterPoints / 9850) * 100,
originalPoints: 9850 - minterPoints,
originalPercentage: ((9850 - minterPoints) / 9850) * 100
};
console.log(`${clampedMagic} × 49.25 = ${minterPoints} minter points`);
console.log(` 🏛️ 9,850 - ${minterPoints} = ${result.originalPoints} original points`);
return result;
};
Parse.Cloud.define('mintAsset', async (request) => {
try {
const { objectId, targetWallet, requested_mojos, isMainnet = true } = request.params;
@@ -23,7 +124,7 @@ Parse.Cloud.define('mintAsset', async (request) => {
// Get config variables and choose mainnet vs testnet based on isMainnet parameter
const config = await Parse.Config.get({ useMasterKey: true });
const royaltyAddress = config.get('DRAC_MINTGARDEN_ROYALY_ADDRESS');
const originalRoyaltyAddress = config.get('DRAC_MINTGARDEN_ROYALY_ADDRESS');
// Use environment variable pattern matching server.js configuration
const serverUrl = process.env.NODE_ENV === 'dev' ? process.env.serverURL : process.env.serverURLProd;
@@ -45,7 +146,7 @@ Parse.Cloud.define('mintAsset', async (request) => {
console.log(`Minting on ${isMainnet ? 'MAINNET' : 'TESTNET'} using ${mintgardenApiUrl}`);
// Get target address from either targetWallet param or config variable
const targetAddress = targetWallet || royaltyAddress;
const targetAddress = targetWallet || originalRoyaltyAddress;
if (!targetAddress) {
throw new Error('No target address available. Please provide targetWallet or set DRAC_MINTGARDEN_ROYALY_ADDRESS in Parse Config');
}
@@ -165,6 +266,84 @@ Parse.Cloud.define('mintAsset', async (request) => {
.update(JSON.stringify(metadataJson))
.digest('hex');
// Determine royalty address (split or original)
let finalRoyaltyAddress = originalRoyaltyAddress;
let splitRoyaltyInfo = null;
let royaltyCalculationResults = null; // Store calculation results for return data
const royaltyPercentage = 33; // Keep at 33% as requested
// Check if we should use split royalties
if (targetWallet && targetWallet !== originalRoyaltyAddress && object.className === 'DRACATTUS') {
console.log('\n💎 ═══════════════════════════════════════════════════════════════════════════════════════ 💎');
console.log('💎 🔮 SPLIT ROYALTY ANALYSIS 🔮 💎');
console.log('💎 ═══════════════════════════════════════════════════════════════════════════════════════ 💎');
try {
const magicValue = object.get('magic') || 0;
console.log(`\n🧙‍♂️ MAGIC DISCOVERY:`);
console.log(` ✨ Dracattus Magic Level: ${magicValue}/100`);
console.log(` 🆔 DRACATTUS ID: ${objectId}`);
const royaltyShare = calculateMinterRoyaltyShare(magicValue);
// Store results for return data (avoid duplicate calculation)
royaltyCalculationResults = {
magicValue: magicValue,
magicValueClamped: Math.max(0, Math.min(100, magicValue || 0)),
minterSharePercentage: royaltyShare.minterPercentage,
originalSharePercentage: royaltyShare.originalPercentage,
minterPoints: royaltyShare.minterPoints,
originalPoints: royaltyShare.originalPoints,
totalAvailablePoints: 9850,
platformFeePoints: 150,
minterWallet: targetWallet,
originalWallet: originalRoyaltyAddress
};
console.log(`\n📊 ROYALTY CALCULATION RESULTS:`);
console.log(` 🎯 Minter Share: ${royaltyShare.minterPercentage.toFixed(2)}% (${royaltyShare.minterPoints.toLocaleString()} points)`);
console.log(` 🏛️ Original Share: ${royaltyShare.originalPercentage.toFixed(2)}% (${royaltyShare.originalPoints.toLocaleString()} points)`);
console.log(` 💰 Total Royalty: 33% of NFT sales`);
// Only create split if minter gets more than 0%
if (royaltyShare.minterPoints > 0) {
console.log(`\n🚀 CREATING SPLIT ROYALTY ADDRESS...`);
console.log(` 📤 Calling SplitXCH API...`);
splitRoyaltyInfo = await createSplitRoyaltyAddress(
originalRoyaltyAddress,
targetWallet,
royaltyShare.minterPoints
);
finalRoyaltyAddress = splitRoyaltyInfo.address;
console.log(`\n✅ SPLIT ROYALTY SUCCESS!`);
console.log(` 🎉 New Split Address: ${finalRoyaltyAddress}`);
console.log(` 🆔 Split ID: ${splitRoyaltyInfo.splitId}`);
console.log(` 💎 This address will automatically distribute royalties based on magic level!`);
} else {
console.log(`\n⚪ NO SPLIT NEEDED:`);
console.log(` 📊 Magic value ${magicValue} results in 0% for minter`);
console.log(` 🏛️ Using original royalty address: ${originalRoyaltyAddress}`);
}
} catch (splitError) {
console.log(`\n❌ SPLIT ROYALTY ERROR:`);
console.error(` 🚨 Failed to create split royalty address: ${splitError.message}`);
console.log(` 🔄 Falling back to original royalty address: ${originalRoyaltyAddress}`);
// Continue with original address as fallback
}
console.log('\n💎 ═══════════════════════════════════════════════════════════════════════════════════════ 💎\n');
} else {
console.log('\n💰 ─────────────────────── ROYALTY ADDRESS SELECTION ───────────────────────');
console.log('💰 Using original royalty address - no split needed');
console.log(`💰 Reason: ${!targetWallet ? 'No target wallet provided' :
targetWallet === originalRoyaltyAddress ? 'Target wallet same as original' :
object.className !== 'DRACATTUS' ? 'Not a DRACATTUS object' : 'Unknown'}`);
console.log(`💰 Address: ${originalRoyaltyAddress}`);
console.log('💰 ─────────────────────────────────────────────────────────────────────────\n');
}
// Construct the mint request body
const mintRequestBody = {
profile_id: mintgardenProfileId,
@@ -177,8 +356,8 @@ Parse.Cloud.define('mintAsset', async (request) => {
edition_total: seriesTotal
},
target_address: targetAddress,
royalty_address: royaltyAddress,
royalty_percentage: 33,
royalty_address: finalRoyaltyAddress,
royalty_percentage: royaltyPercentage,
reserve_for_seconds:60
};
@@ -197,8 +376,23 @@ Parse.Cloud.define('mintAsset', async (request) => {
body: JSON.stringify(mintRequestBody)
});
// Log the request body for debugging
console.log('Mint request body:', JSON.stringify(mintRequestBody, null, 2));
// Log the final mint configuration with enhanced royalty info
console.log('\n🚀 ═══════════════════════════════════════════════════════════════════════════════════════ 🚀');
console.log('🚀 🎯 FINAL MINT CONFIGURATION 🎯 🚀');
console.log('🚀 ═══════════════════════════════════════════════════════════════════════════════════════ 🚀');
console.log(`\n📋 MINT DETAILS:`);
console.log(` 🎨 NFT Target: ${targetAddress}`);
console.log(` 👑 Royalty Address: ${finalRoyaltyAddress}`);
console.log(` 💎 Royalty Percentage: ${royaltyPercentage}%`);
if (splitRoyaltyInfo) {
console.log(` 🔀 Split Type: Magic-Based Split (${object.get('magic')}/100 magic)`);
console.log(` 🆔 Split ID: ${splitRoyaltyInfo.splitId}`);
} else {
console.log(` 🔀 Split Type: Single Address (No Split)`);
}
console.log(`\n📄 Complete Mint Request Body:`);
console.log(JSON.stringify(mintRequestBody, null, 2));
console.log('\n🚀 ═══════════════════════════════════════════════════════════════════════════════════════ 🚀\n');
if (!response.ok) {
const responseText = await response.text(); // Get raw response text
@@ -226,8 +420,23 @@ Parse.Cloud.define('mintAsset', async (request) => {
throw new Error('Unable to parse MintGarden API response');
}
// Log the successful mint
console.log('Successfully minted NFT:', mintResult);
// Log the successful mint with enhanced royalty details
console.log('\n🎉 ═══════════════════════════════════════════════════════════════════════════════════════ 🎉');
console.log('🎉 ✅ MINT SUCCESS! ✅ 🎉');
console.log('🎉 ═══════════════════════════════════════════════════════════════════════════════════════ 🎉');
console.log(`\n🏆 MINT RESULTS:`);
console.log(` 🆔 NFT ID: ${mintResult.nft_id}`);
console.log(` 🎨 Minted To: ${targetAddress}`);
console.log(` 👑 Royalty Setup: ${finalRoyaltyAddress}`);
if (splitRoyaltyInfo) {
console.log(` 💎 Royalty Type: Split Royalty (Magic: ${object.get('magic')}/100)`);
console.log(` 🔀 Split Details: ${splitRoyaltyInfo.minterPoints} pts → Minter, ${splitRoyaltyInfo.originalPoints} pts → Original`);
} else {
console.log(` 💎 Royalty Type: Single Address (${royaltyPercentage}% to ${finalRoyaltyAddress})`);
}
console.log(`\n📊 Full MintGarden Response:`);
console.log(mintResult);
console.log('\n🎉 ═══════════════════════════════════════════════════════════════════════════════════════ 🎉\n');
// Save mint record to Parse
const MintRecord = Parse.Object.extend('MintRecord');
@@ -244,8 +453,11 @@ Parse.Cloud.define('mintAsset', async (request) => {
status: 'success',
timestamp: new Date(),
targetWallet: targetAddress,
royaltyAddress: royaltyAddress,
royaltyPercentage: 5,
royaltyAddress: finalRoyaltyAddress,
royaltyPercentage: royaltyPercentage, // Fixed: Now logs actual percentage used (33%)
originalRoyaltyAddress: originalRoyaltyAddress,
splitRoyaltyInfo: splitRoyaltyInfo,
magicValue: object.className === 'DRACATTUS' ? (object.get('magic') || 0) : null,
profileId: mintgardenProfileId,
requested_mojos: requested_mojos ? parseInt(requested_mojos, 10) : null,
network: isMainnet ? 'mainnet' : 'testnet' // Track which network was used
@@ -253,6 +465,22 @@ Parse.Cloud.define('mintAsset', async (request) => {
await mintRecord.save(null, { useMasterKey: true });
// Prepare detailed royalty information for client
let royaltyDetails = {
address: finalRoyaltyAddress,
percentage: royaltyPercentage,
type: splitRoyaltyInfo ? 'split' : 'single',
splitInfo: splitRoyaltyInfo
};
// Add enhanced split details if this was a DRACATTUS with split royalties
if (royaltyCalculationResults) {
royaltyDetails.splitCalculation = {
...royaltyCalculationResults,
wasActuallySplit: !!splitRoyaltyInfo
};
}
return {
success: true,
data: mintResult,
@@ -260,6 +488,7 @@ Parse.Cloud.define('mintAsset', async (request) => {
dataHash,
metadataHash
},
royalty: royaltyDetails,
network: isMainnet ? 'mainnet' : 'testnet'
};
@@ -267,10 +496,10 @@ Parse.Cloud.define('mintAsset', async (request) => {
console.error('Error minting asset:', error);
// Get royalty address from config
let royaltyAddress = null;
let originalRoyaltyAddress = null;
try {
const config = await Parse.Config.get({ useMasterKey: true });
royaltyAddress = config.get('DRAC_MINTGARDEN_ROYALY_ADDRESS');
originalRoyaltyAddress = config.get('DRAC_MINTGARDEN_ROYALY_ADDRESS');
} catch (configError) {
console.error('Error fetching config:', configError);
}
@@ -285,8 +514,8 @@ Parse.Cloud.define('mintAsset', async (request) => {
status: 'failed',
timestamp: new Date(),
targetAddress: request.params.targetWallet || null,
royaltyAddress: royaltyAddress,
royaltyPercentage: 5,
royaltyAddress: originalRoyaltyAddress,
royaltyPercentage: 33, // Fixed: Now logs actual percentage used (33%)
network: request.params.isMainnet ? 'mainnet' : 'testnet'
});

View File

@@ -530,8 +530,8 @@ function startDexieWebSocketListener(saveAllItems = false) {
}
}
// Summary every 25 messages
if (messageCounter % 25 === 0) {
// Summary at milestones (100, 500, 1000, then every 1000)
if (messageCounter === 100 || messageCounter === 500 || messageCounter % 1000 === 0) {
await log(`📊 Processed ${messageCounter} events`);
}
} catch (parseError) {

View File

@@ -175,8 +175,8 @@ Parse.Cloud.define('sendDracattusCreatedEmail', async (request) => {
const mockDracattusData = {
id: 'TEST123456',
name: request.params?.dracattusName || 'Mystical Fire Dragon',
type: 'Legendary Dracattus',
rarity: 'Legendary',
type: 'Dracattus',
rarity: 'Common',
thumbnail: 'https://api.dracattus.com/v1/files/koba42/72e24849ca75695f0b324f4a177d5dc4_1.webp',
createdAt: new Date().toLocaleDateString()
};

View File

@@ -134,7 +134,7 @@ const dashboard = new ParseDashboard({
requireRevocableSessions: true,
expireInactiveSessions: true,
logsFolder: './logs',
logLevel: 'info',
logLevel: 'warn',
maxUploadSize: '100mb',
allowExpiredAuthDataToken: false,
encodeParseObjectInCloudFunction: true,
@@ -191,9 +191,9 @@ const dashboard = new ParseDashboard({
keepAlive: true,
keepAliveInitialDelay: 10000
},
logLevels: ['error', 'warn', 'info', 'verbose']
logLevels: ['error', 'warn']
},
verbose: true,
verbose: false,
startLiveQueryServer: true,
websocketTimeout: 100 * 1000
});