UNPKG

@myronkoch/andromeda-mcp-queries

Version:

Andromeda MCP Server - Queries Package: 12 read-only tools for safe Andromeda blockchain exploration and discovery

1,063 lines 53.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AndromedaMCPServer = void 0; // Andromeda MCP Server - Complete Server Implementation const stargate_1 = require("@cosmjs/stargate"); const cosmwasm_stargate_1 = require("@cosmjs/cosmwasm-stargate"); const proto_signing_1 = require("@cosmjs/proto-signing"); const network_js_1 = require("./config/network.js"); const constants_js_1 = require("./config/constants.js"); // Complete AndromedaMCPServer class implementation class AndromedaMCPServer { constructor() { this.cosmosClient = null; this.cosmWasmClient = null; } async initialize() { try { // Initialize both clients this.cosmosClient = await stargate_1.StargateClient.connect(network_js_1.ANDROMEDA_RPC_ENDPOINT); this.cosmWasmClient = await cosmwasm_stargate_1.CosmWasmClient.connect(network_js_1.ANDROMEDA_RPC_ENDPOINT); console.error('Connected to Andromeda blockchain with CosmWasm support'); } catch (error) { console.error('Failed to connect to Andromeda blockchain:', error); throw error; } } async getSigningClient(mnemonic) { const wallet = await proto_signing_1.DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: 'andr' }); return cosmwasm_stargate_1.SigningCosmWasmClient.connectWithSigner(network_js_1.ANDROMEDA_RPC_ENDPOINT, wallet, { gasPrice: network_js_1.DEFAULT_GAS_PRICE, // Note: gasAdjustment is handled in individual transaction methods }); } async getWalletAddress(mnemonic) { // 🔍 DEBUG: Trace mnemonic parameter (WORKING FUNCTION) console.error(`🔍 GET_WALLET_ADDRESS DEBUG (WORKING) - Mnemonic words: ${mnemonic.split(' ').length}, First: "${mnemonic.split(' ')[0]}", Full: "${mnemonic.substring(0, 50)}..."`); const wallet = await proto_signing_1.DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: 'andr' }); const accounts = await wallet.getAccounts(); return accounts[0].address; } async generateWallet() { const wallet = await proto_signing_1.DirectSecp256k1HdWallet.generate(24, { prefix: 'andr' }); const accounts = await wallet.getAccounts(); return { mnemonic: wallet.mnemonic, address: accounts[0].address }; } async getBlockInfo(height) { if (!this.cosmosClient) throw new Error('Client not initialized'); if (height) { return await this.cosmosClient.getBlock(height); } else { return await this.cosmosClient.getBlock(); } } async getTransaction(txHash) { if (!this.cosmosClient) throw new Error('Client not initialized'); return await this.cosmosClient.getTx(txHash); } async getAccountInfo(address) { if (!this.cosmosClient) throw new Error('Client not initialized'); return await this.cosmosClient.getAccount(address); } async getAccountBalance(address) { if (!this.cosmosClient) throw new Error('Client not initialized'); return await this.cosmosClient.getAllBalances(address); } async queryADO(contractAddress, query) { if (!this.cosmWasmClient) throw new Error('CosmWasm client not initialized'); try { return await this.cosmWasmClient.queryContractSmart(contractAddress, query); } catch (error) { // Fallback to direct REST API call if the client method fails const queryB64 = Buffer.from(JSON.stringify(query)).toString('base64'); const response = await fetch(`${network_js_1.ANDROMEDA_REST_ENDPOINT}/cosmwasm/wasm/v1/contract/${contractAddress}/smart/${queryB64}`); if (!response.ok) { throw new Error(`Query failed: ${response.statusText}`); } const result = await response.json(); return result.data || result; } } async executeADO(contractAddress, msg, mnemonic, funds = [], gas) { const signingClient = await this.getSigningClient(mnemonic); const senderAddress = await this.getWalletAddress(mnemonic); // Determine appropriate gas limit based on operation complexity let gasLimit = gas || constants_js_1.GAS_LIMITS.DEFAULT; // Default gas limit // Increase gas for complex operations that frequently run out of gas if (msg.start_sale || msg.purchase_tokens || msg.send) { gasLimit = gas || constants_js_1.GAS_LIMITS.HIGH; // Higher gas for CW20-Exchange operations console.error(`DEBUG: Using increased gas limit ${gasLimit} for complex operation`); } const result = await signingClient.execute(senderAddress, contractAddress, msg, { amount: [{ denom: 'uandr', amount: '5000' }], gas: gasLimit }, '', funds); return result; } async transferTokens(recipient, amount, denom, mnemonic, memo) { // 🔍 DEBUG: Trace mnemonic parameter console.error(`🔍 TRANSFER_TOKENS DEBUG - Mnemonic words: ${mnemonic.split(' ').length}, First: "${mnemonic.split(' ')[0]}", Full: "${mnemonic.substring(0, 50)}..."`); const signingClient = await this.getSigningClient(mnemonic); const senderAddress = await this.getWalletAddress(mnemonic); const result = await signingClient.sendTokens(senderAddress, recipient, [{ denom, amount }], (0, constants_js_1.createFeeConfig)(constants_js_1.TRANSACTION_FEES.LOW, constants_js_1.GAS_LIMITS.LOW), // Fixed gas limit with proper fee memo); return result; } async getValidators() { if (!this.cosmosClient) throw new Error('Client not initialized'); // Use REST API since getValidators() doesn't exist on StargateClient const response = await fetch(`${network_js_1.ANDROMEDA_REST_ENDPOINT}/cosmos/staking/v1beta1/validators`); if (!response.ok) { throw new Error(`Failed to fetch validators: ${response.statusText}`); } return await response.json(); } async getChainInfo() { if (!this.cosmosClient) throw new Error('Client not initialized'); const latestBlock = await this.cosmosClient.getBlock(); const chainId = await this.cosmosClient.getChainId(); return { chainId, latestBlock: { height: latestBlock.header.height, time: latestBlock.header.time, hash: latestBlock.id } }; } async getCodeInfo(codeId) { if (!this.cosmWasmClient) { return { error: 'CosmWasm client not initialized' }; } try { const fullInfo = await this.cosmWasmClient.getCodeDetails(codeId); // Return the full info object directly since CodeDetails type is opaque return { codeId, ...fullInfo, upload_time: fullInfo.upload_time || undefined }; } catch (error) { return { error: 'Failed to get code info', codeId, details: error?.message || String(error) }; } } async getContractInfo(contractAddress) { if (!this.cosmWasmClient) throw new Error('CosmWasm client not initialized'); return await this.cosmWasmClient.getContract(contractAddress); } async getContracts(codeId) { if (!this.cosmWasmClient) throw new Error('CosmWasm client not initialized'); return await this.cosmWasmClient.getContracts(codeId); } // Enhanced transaction parsing for better readability async getRecentTransactions(limit = 50) { if (!this.cosmosClient) throw new Error('Client not initialized'); try { // Get the latest block height first const latestBlock = await this.cosmosClient.getBlock(); const latestHeight = parseInt(latestBlock.header.height.toString()); const allTransactions = []; let currentHeight = latestHeight; // Search through the last 20 blocks or until we have enough transactions while (allTransactions.length < limit && currentHeight > Math.max(1, latestHeight - 20)) { try { const block = await this.cosmosClient.getBlock(currentHeight); const rawTxs = block.txs || []; // Parse the transactions to extract readable data const parsedTxs = rawTxs.map((tx, index) => { try { return { block_height: block.header.height, block_time: block.header.time, tx_index: index, raw_size: tx.length, tx_hash: this.calculateTxHash(tx), // Calculate hash if possible // Note: Full parsing would require proper protobuf decoding raw_data: tx }; } catch (error) { return { block_height: block.header.height, tx_index: index, error: 'Failed to parse transaction', raw_size: tx.length }; } }); allTransactions.push(...parsedTxs); // If we found transactions in this block, continue searching recent blocks // Otherwise, continue to search more blocks currentHeight--; } catch (blockError) { // Failed to get block, continue to next currentHeight--; continue; } } // Return the most recent transactions up to the limit return allTransactions.slice(0, limit); } catch (error) { console.error('Error getting recent transactions:', error); throw new Error(`Failed to get recent transactions: ${error}`); } } // Helper method to calculate transaction hash (basic implementation) calculateTxHash(txData) { // This is a simplified hash calculation - in production you'd want proper SHA256 return Buffer.from(txData.slice(0, 32)).toString('hex'); } // ADODB (ADO Database) Methods async queryADODB(adoType, startAfter) { try { // First, get the ADODB address from kernel const kernelQuery = { key_address: { key: "adodb" } }; const kernelResult = await this.queryADO(network_js_1.KERNEL_ADDRESS, kernelQuery); const adobAddress = kernelResult; // kernelResult is the address string directly // Use the working ADODB query format let query; if (adoType) { // Query versions for specific ADO type query = { ado_versions: { ado_type: adoType, start_after: startAfter, limit: 50 } }; } else { // Query all ADO types query = { all_ado_types: { start_after: startAfter, limit: 50 } }; } // Query the ADODB with working format return await this.queryADO(adobAddress, query); } catch (error) { throw new Error(`Failed to query ADODB: ${error}`); } } async getADOCodeId(adoType, version) { try { // **FIX #2: ADO TYPE NORMALIZATION** - Handle underscore/hyphen variants const normalizedAdoType = this.normalizeADOType(adoType); console.error(`DEBUG: ADO type normalization: "${adoType}" → "${normalizedAdoType}"`); // First, get the ADODB address from kernel const kernelQuery = { key_address: { key: "adodb" } }; const kernelResult = await this.queryADO(network_js_1.KERNEL_ADDRESS, kernelQuery); const adobAddress = kernelResult; // kernelResult is the address string directly // Get all versions for the ADO type using working format const query = { ado_versions: { ado_type: normalizedAdoType, limit: 50 } }; const result = await this.queryADO(adobAddress, query); // Parse the versions to find the requested one or latest if (Array.isArray(result)) { let targetVersion = version; if (!targetVersion) { // Find the latest version (highest version number) targetVersion = result[0]; // First in list should be latest } // Find the specific version in the results const versionMatch = result.find((v) => v.includes(targetVersion || '')); if (versionMatch) { // Extract just the ADO type and version for fallback compatibility return { code_id: this.extractCodeIdFromVersion(versionMatch) || this.getFallbackCodeId(normalizedAdoType), ado_type: normalizedAdoType, version: versionMatch, source: 'adodb_query' }; } } // If specific version not found, return fallback throw new Error(`Version ${version || 'latest'} not found for ADO type ${normalizedAdoType}`); } catch (error) { // **Fallback with improved type normalization** const normalizedAdoType = this.normalizeADOType(adoType); console.warn(`ADODB query failed, using fallback for ${normalizedAdoType}:`, error); const codeId = this.getFallbackCodeId(normalizedAdoType); if (codeId) { return { code_id: codeId, ado_type: normalizedAdoType, version: version || 'fallback', source: 'fallback' }; } throw new Error(`Failed to get ADO code ID and no fallback available for type: ${normalizedAdoType}. Original error: ${error}`); } } /** * **FIX #2: ADO TYPE NORMALIZATION** * Handles various ADO type naming conventions (hyphen vs underscore) */ normalizeADOType(adoType) { // Convert to lowercase for consistent comparison const lower = adoType.toLowerCase(); // Handle specific problematic cases const typeMapping = { 'address-list': 'address-list', 'address_list': 'address-list', // Convert underscore to hyphen 'app-contract': 'app-contract', 'app_contract': 'app-contract', 'conditional-splitter': 'conditional-splitter', 'conditional_splitter': 'conditional-splitter', 'cw20-exchange': 'cw20-exchange', 'cw20_exchange': 'cw20-exchange', 'cw20-staking': 'cw20-staking', 'cw20_staking': 'cw20-staking', 'merkle-airdrop': 'merkle-airdrop', 'merkle_airdrop': 'merkle-airdrop' }; return typeMapping[lower] || lower; } getFallbackCodeId(adoType) { // **Updated with normalized type names** const fallbackCodeIds = { 'cw20': 10, 'cw721': 13, 'marketplace': 15, 'cw20-exchange': 29, // CONFIRMED! CW20 Exchange Code ID 29 works perfectly 'auction': 26, // CONFIRMED! Auction Code ID 26 from earlier discovery 'cw20-staking': 30, // CW20-Staking Code ID for DeFi reward pools 'merkle-airdrop': 17, // Merkle Airdrop Code ID for community token distribution 'splitter': 20, 'app': 6, 'app-contract': 6, // Alternative name for app 'kernel': 6, 'address-list': 28, // **NEW**: Address-List Code ID (estimated) }; return fallbackCodeIds[adoType.toLowerCase()] || null; } extractCodeIdFromVersion(versionString) { // This is a placeholder - we'd need to query additional ADODB endpoints // to get the actual code ID for a version. For now, return null to use fallback. return null; } async listADOVersions(adoType) { try { // Get ADODB address from kernel const kernelQuery = { key_address: { key: "adodb" } }; const kernelResult = await this.queryADO(network_js_1.KERNEL_ADDRESS, kernelQuery); const adobAddress = kernelResult; // kernelResult is the address string directly // Use the working ADODB query format for versions const query = { ado_versions: { ado_type: adoType, limit: 100 } }; return await this.queryADO(adobAddress, query); } catch (error) { throw new Error(`Failed to list ADO versions: ${error}`); } } // GraphQL Methods async graphqlQuery(query, variables) { try { const response = await fetch(network_js_1.ANDROMEDA_GRAPHQL_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query, variables }) }); if (!response.ok) { throw new Error(`GraphQL query failed: ${response.statusText}`); } const result = await response.json(); if (result.errors) { throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`); } return result.data; } catch (error) { throw new Error(`GraphQL query failed: ${error}`); } } async subscribeADOEvents(contractAddress) { // Note: This would typically use WebSocket for real-time subscriptions // For now, we'll query recent events via GraphQL const query = ` query GetADOEvents($contractAddress: String!) { events(where: { contract_address: { _eq: $contractAddress } }, order_by: { block_height: desc }, limit: 50) { id contract_address event_type attributes block_height block_time transaction_hash } } `; return await this.graphqlQuery(query, { contractAddress }); } // App Management Methods async createApp(name, components, mnemonic) { const signingClient = await this.getSigningClient(mnemonic); const senderAddress = await this.getWalletAddress(mnemonic); // Get App code ID from ADODB (will use fallback if ADODB fails) const appCodeIdResponse = await this.getADOCodeId('app'); const appCodeId = appCodeIdResponse.code_id; console.error(`DEBUG: **FIX #3: APP CREATION AUTHORIZATION** - Testing multiple formats with Code ID: ${appCodeId}`); // **FIX #3: APP CREATION AUTHORIZATION** - Systematic format testing with improved authorization handling // FORMAT VARIATION 1: Minimal instantiation (test basic authorization first) let instantiateMsg = { kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress, modules: [] // Minimal modules array }; console.error(`DEBUG: Trying FORMAT 1 (minimal authorization test):`, JSON.stringify(instantiateMsg, null, 2)); try { const basicFee = { amount: [{ denom: 'uandr', amount: '12500' }], // Higher fee for app creation gas: '500000' // Higher gas limit }; const result = await signingClient.instantiate(senderAddress, appCodeId, instantiateMsg, `${name} App - Basic`, basicFee); console.error(`DEBUG: FORMAT 1 (minimal) SUCCESS! Basic authorization works.`); return result; } catch (error1) { console.error(`DEBUG: FORMAT 1 failed:`, error1.message); // Common enhanced fee for subsequent attempts const enhancedFee = { amount: [{ denom: 'uandr', amount: '25000' }], gas: '1000000' }; // FORMAT VARIATION 2: App components with proper encoding (fix linter error) try { instantiateMsg = { app_components: components.map(comp => ({ name: comp.name, ado_type: comp.ado_type, component_type: { new: comp.component_type.new } })), name: name, kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress }; console.error(`DEBUG: Trying FORMAT 2 (app_components with encoding):`, JSON.stringify(instantiateMsg, null, 2)); // Try with platform fee funding const platformFunds = [{ denom: 'uandr', amount: '5000000' }]; // 5 ANDR platform fee const result = await signingClient.instantiate(senderAddress, appCodeId, instantiateMsg, `${name} App`, enhancedFee, { funds: platformFunds }); console.error(`DEBUG: FORMAT 2 (app_components + platform funds) SUCCESS!`); return result; } catch (error2) { console.error(`DEBUG: FORMAT 2 failed:`, error2.message); // FORMAT VARIATION 3: Alternative 'app' field name try { instantiateMsg = { app: components.map(comp => ({ name: comp.name, ado_type: comp.ado_type, component_type: { new: comp.component_type.new } })), name: name, kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress }; console.error(`DEBUG: Trying FORMAT 3 (app field):`, JSON.stringify(instantiateMsg, null, 2)); const result = await signingClient.instantiate(senderAddress, appCodeId, instantiateMsg, `${name} App`, enhancedFee); console.error(`DEBUG: FORMAT 3 (app field) SUCCESS!`); return result; } catch (error3) { console.error(`DEBUG: FORMAT 3 failed:`, error3.message); // FORMAT VARIATION 4: No base64 encoding (direct instantiate messages) try { instantiateMsg = { app_components: components.map(comp => ({ name: comp.name, ado_type: comp.ado_type, component_type: comp.component_type // Direct message, no encoding })), name: name, kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress }; console.error(`DEBUG: Trying FORMAT 4 (no encoding):`, JSON.stringify(instantiateMsg, null, 2)); const result = await signingClient.instantiate(senderAddress, appCodeId, instantiateMsg, `${name} App`, enhancedFee); console.error(`DEBUG: FORMAT 4 (no encoding) SUCCESS!`); return result; } catch (error4) { console.error(`DEBUG: FORMAT 4 failed:`, error4.message); // FORMAT VARIATION 5: Different App Contract (app-contract vs app) try { // Try with app-contract ADO type instead const appContractCodeIdResponse = await this.getADOCodeId('app-contract'); const appContractCodeId = appContractCodeIdResponse.code_id; console.error(`DEBUG: Trying FORMAT 5 (app-contract) with Code ID: ${appContractCodeId}`); const minimalAppContractMsg = { kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress }; const result = await signingClient.instantiate(senderAddress, appContractCodeId, minimalAppContractMsg, `${name} AppContract`, enhancedFee); console.error(`DEBUG: FORMAT 5 (app-contract) SUCCESS!`); return result; } catch (error5) { console.error(`DEBUG: FORMAT 5 failed:`, error5.message); // All formats failed - throw comprehensive error throw new Error(`All App format variations failed: FORMAT 1 (minimal): ${error1.message} FORMAT 2 (app_components + funds): ${error2.message} FORMAT 3 (app field): ${error3.message} FORMAT 4 (no encoding): ${error4.message} FORMAT 5 (app-contract): ${error5.message}`); } } } } } } async getAppInfo(appAddress) { const query = { get_components: {} }; return await this.queryADO(appAddress, query); } async listAppComponents(appAddress) { const query = { get_components: {} }; return await this.queryADO(appAddress, query); } async updateAppConfig(appAddress, updates, mnemonic) { const msg = { update_app_config: updates }; return await this.executeADO(appAddress, msg, mnemonic); } // ADO Deployment Methods async deployADO(adoType, name, instantiateMsg, mnemonic, codeId) { const signingClient = await this.getSigningClient(mnemonic); const senderAddress = await this.getWalletAddress(mnemonic); // Get code ID if not provided let resolvedCodeId = codeId; if (!resolvedCodeId) { const adoInfo = await this.getADOCodeId(adoType); resolvedCodeId = adoInfo.code_id; if (!resolvedCodeId) { throw new Error(`Could not find code_id for ADO type: ${adoType}`); } } // Apply ADO-specific fixes based on known failure patterns let enhancedMsg = { ...instantiateMsg }; // Fix 1: CW721 - Simplify complex instantiate messages that often fail if (adoType === 'cw721') { const simplifiedCW721Msg = { name: enhancedMsg.name || `${name} Collection`, symbol: enhancedMsg.symbol || name.toUpperCase().substring(0, 8), minter: enhancedMsg.minter || senderAddress, kernel_address: network_js_1.KERNEL_ADDRESS }; console.error(`DEBUG: CW721 simplified instantiate message applied`); enhancedMsg = simplifiedCW721Msg; } // Fix 2: Marketplace - Remove unauthorized fields, use minimal format else if (adoType === 'marketplace') { const simplifiedMarketplaceMsg = { kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress, modules: enhancedMsg.modules || [] }; // Remove problematic fields that cause "unknown field" errors delete enhancedMsg.authorized_token_addresses; delete enhancedMsg.authorized_cw20_address; console.error(`DEBUG: Marketplace simplified instantiate message applied`); enhancedMsg = simplifiedMarketplaceMsg; } // Fix 3: Splitter - Use correct recipient format structure else if (adoType === 'splitter') { if (enhancedMsg.recipients) { const correctedSplitterMsg = { recipients: enhancedMsg.recipients.map((r) => ({ recipient: r.recipient || { address: r.address }, percent: r.percent })), kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress }; console.error(`DEBUG: Splitter corrected recipient format applied`); enhancedMsg = correctedSplitterMsg; } } // Add kernel address to instantiate message if not present if (!enhancedMsg.kernel_address) { enhancedMsg.kernel_address = network_js_1.KERNEL_ADDRESS; } try { const result = await signingClient.instantiate(senderAddress, resolvedCodeId, enhancedMsg, name, { amount: [{ denom: 'uandr', amount: '6250' }], gas: '250000' }); console.error(`DEBUG: ${adoType.toUpperCase()} deployment SUCCESS on first attempt`); return result; } catch (firstError) { console.error(`DEBUG: ${adoType.toUpperCase()} deployment failed with enhanced message:`, firstError.message); // Fallback for CW721: Even more minimal format if the simplified version fails if (adoType === 'cw721') { const minimalCW721Msg = { name: enhancedMsg.name, symbol: enhancedMsg.symbol, minter: senderAddress, kernel_address: network_js_1.KERNEL_ADDRESS }; try { const result = await signingClient.instantiate(senderAddress, resolvedCodeId, minimalCW721Msg, name, { amount: [{ denom: 'uandr', amount: '6250' }], gas: '250000' }); console.error(`DEBUG: CW721 minimal fallback SUCCESS`); return result; } catch (fallbackError) { console.error(`DEBUG: CW721 minimal fallback also failed:`, fallbackError.message); } } // Fallback for Marketplace: Ultra-minimal format else if (adoType === 'marketplace') { const minimalMarketplaceMsg = { modules: [] }; try { const result = await signingClient.instantiate(senderAddress, resolvedCodeId, minimalMarketplaceMsg, name, { amount: [{ denom: 'uandr', amount: '6250' }], gas: '250000' }); console.error(`DEBUG: Marketplace minimal fallback SUCCESS`); return result; } catch (fallbackError) { console.error(`DEBUG: Marketplace minimal fallback also failed:`, fallbackError.message); } } // Fallback for Splitter: Try different recipient formats else if (adoType === 'splitter' && enhancedMsg.recipients) { // Format 1: Direct address/percent structure const format1Msg = { recipients: enhancedMsg.recipients.map((r) => ({ address: r.recipient?.address || r.address, percent: r.percent })), kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress }; try { const result = await signingClient.instantiate(senderAddress, resolvedCodeId, format1Msg, name, { amount: [{ denom: 'uandr', amount: '6250' }], gas: '250000' }); console.error(`DEBUG: Splitter format1 fallback SUCCESS`); return result; } catch (format1Error) { console.error(`DEBUG: Splitter format1 failed:`, format1Error.message); // Format 2: Percentage as decimal const format2Msg = { recipients: enhancedMsg.recipients.map((r) => ({ recipient: r.recipient || { address: r.address }, percent: parseFloat(r.percent) < 1 ? r.percent : (parseFloat(r.percent) / 100).toString() })), kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress }; try { const result = await signingClient.instantiate(senderAddress, resolvedCodeId, format2Msg, name, { amount: [{ denom: 'uandr', amount: '6250' }], gas: '250000' }); console.error(`DEBUG: Splitter format2 (decimal percent) fallback SUCCESS`); return result; } catch (format2Error) { console.error(`DEBUG: Splitter format2 also failed:`, format2Error.message); } } } // If all fallbacks fail, throw the original error throw new Error(`${adoType.toUpperCase()} deployment failed: ${firstError.message}`); } } async instantiateADO(codeId, instantiateMsg, label, mnemonic) { const signingClient = await this.getSigningClient(mnemonic); const senderAddress = await this.getWalletAddress(mnemonic); // Add required fields for specific ADO types let enhancedMsg = { ...instantiateMsg }; // For CW721 (Code ID 13), add required minter field if missing if (codeId === 13 && !enhancedMsg.minter) { enhancedMsg.minter = senderAddress; // Default to sender as minter } // Add kernel address if not present if (!enhancedMsg.kernel_address) { enhancedMsg.kernel_address = network_js_1.KERNEL_ADDRESS; } const result = await signingClient.instantiate(senderAddress, codeId, enhancedMsg, label, { amount: [{ denom: 'uandr', amount: '5000' }], gas: '250000' } // Fixed gas for consistency ); return result; } async migrateADO(contractAddress, newCodeId, migrateMsg, mnemonic) { const signingClient = await this.getSigningClient(mnemonic); const senderAddress = await this.getWalletAddress(mnemonic); const result = await signingClient.migrate(senderAddress, contractAddress, newCodeId, migrateMsg, { amount: [{ denom: 'uandr', amount: '5000' }], gas: '200000' } // Fixed gas for consistency ); return result; } async publishADO(codeId, adoType, version, mnemonic) { // Get ADODB address from kernel const kernelQuery = { key_address: { key: "adodb" } }; const kernelResult = await this.queryADO(network_js_1.KERNEL_ADDRESS, kernelQuery); const adobAddress = kernelResult.address; const msg = { publish: { code_id: codeId, ado_type: adoType, version, action_fees: null, publisher: null } }; return await this.executeADO(adobAddress, msg, mnemonic); } // ADO-Specific Methods async cw20Mint(contractAddress, recipient, amount, mnemonic) { const msg = { mint: { recipient, amount } }; return await this.executeADO(contractAddress, msg, mnemonic); } async cw20Burn(contractAddress, amount, mnemonic) { const msg = { burn: { amount } }; return await this.executeADO(contractAddress, msg, mnemonic); } async cw721MintNFT(contractAddress, tokenId, owner, mnemonic, tokenUri, extension) { const senderAddress = await this.getWalletAddress(mnemonic); // **HARDCODED SAFE EXTENSION** - Andromeda expects URI to point to metadata // Only use the required publisher field, ignore any custom extension fields const safeExtension = { publisher: senderAddress }; console.error(`DEBUG: CW721 mint using hardcoded safe extension (publisher only)`); const msg = { mint: { token_id: tokenId, owner, token_uri: tokenUri, extension: safeExtension // Always use safe extension with only publisher field } }; return await this.executeADO(contractAddress, msg, mnemonic); } async marketplaceListItem(marketplaceAddress, nftContract, tokenId, price, mnemonic) { // **MARKETPLACE FIX**: Use the correct approach from Andromeda docs // Call send_nft on the CW721 contract, not receive_nft on marketplace const hookMsg = { start_sale: { price: price.amount, coin_denom: price.denom } }; // Call send_nft on the NFT contract (correct approach) const sendNftMsg = { send_nft: { contract: marketplaceAddress, token_id: tokenId, msg: Buffer.from(JSON.stringify(hookMsg)).toString('base64') } }; console.error(`DEBUG: MARKETPLACE FIX - Using send_nft on NFT contract:`, JSON.stringify(sendNftMsg, null, 2)); return await this.executeADO(nftContract, sendNftMsg, mnemonic, [], '300000'); } async auctionPlaceBid(auctionAddress, amount, denom, mnemonic) { // 🔍 DEBUG: Trace mnemonic parameter console.error(`🔍 AUCTION_PLACE_BID DEBUG - Mnemonic words: ${mnemonic.split(' ').length}, First: "${mnemonic.split(' ')[0]}", Full: "${mnemonic.substring(0, 50)}..."`); const msg = { place_bid: {} }; return await this.executeADO(auctionAddress, msg, mnemonic, [{ denom, amount }]); } async splitterUpdateRecipients(splitterAddress, recipients, mnemonic) { const msg = { update_recipients: { recipients: recipients.map(r => ({ recipient: { address: r.address }, percent: r.percent })) } }; return await this.executeADO(splitterAddress, msg, mnemonic); } // CW20 Exchange Methods async deployCW20Exchange(tokenAddress, name, mnemonic) { const signingClient = await this.getSigningClient(mnemonic); const senderAddress = await this.getWalletAddress(mnemonic); // Get CW20-Exchange code ID from ADODB const exchangeCodeIdResponse = await this.getADOCodeId('cw20-exchange'); const exchangeCodeId = exchangeCodeIdResponse.code_id; // Prepare CW20-Exchange instantiation message const instantiateMsg = { token_address: tokenAddress, kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress }; const fee = { amount: [{ denom: 'uandr', amount: '6250' }], gas: '250000' }; const result = await signingClient.instantiate(senderAddress, exchangeCodeId, instantiateMsg, name, fee); return result; } async startCW20Sale(exchangeAddress, tokenAddress, amount, asset, exchangeRate, mnemonic, recipient, startTime, duration) { const senderAddress = await this.getWalletAddress(mnemonic); // First, send CW20 tokens to the exchange with StartSale hook const hookMsg = { start_sale: { asset: asset.type === 'native' ? { native: asset.value } : { cw20: asset.value }, exchange_rate: exchangeRate, recipient: recipient || senderAddress, ...(startTime && { start_time: { at_time: startTime.toString() } }), ...(duration && { duration: duration.toString() }) } }; const sendMsg = { send: { contract: exchangeAddress, amount: amount, msg: Buffer.from(JSON.stringify(hookMsg)).toString('base64') } }; return await this.executeADO(tokenAddress, sendMsg, mnemonic); } async purchaseCW20Tokens(exchangeAddress, purchaseAsset, mnemonic, recipient) { const senderAddress = await this.getWalletAddress(mnemonic); if (purchaseAsset.type === 'native') { // Purchase with native tokens const msg = { purchase: { recipient: recipient || senderAddress } }; return await this.executeADO(exchangeAddress, msg, mnemonic, [{ denom: purchaseAsset.denom, amount: purchaseAsset.amount }]); } else { // Purchase with CW20 tokens const hookMsg = { purchase: { recipient: recipient || senderAddress } }; const sendMsg = { send: { contract: exchangeAddress, amount: purchaseAsset.amount, msg: Buffer.from(JSON.stringify(hookMsg)).toString('base64') } }; return await this.executeADO(purchaseAsset.address, sendMsg, mnemonic); } } async cancelCW20Sale(exchangeAddress, asset, mnemonic) { const msg = { cancel_sale: { asset: asset.type === 'native' ? { native: asset.value } : { cw20: asset.value } } }; return await this.executeADO(exchangeAddress, msg, mnemonic); } async queryCW20Sale(exchangeAddress, asset) { const query = { sale: { asset: asset.type === 'native' ? { native: asset.value } : { cw20: asset.value } } }; return await this.queryADO(exchangeAddress, query); } // Auction Methods async deployAuction(name, mnemonic, authorizedTokenAddresses, authorizedCw20Address) { const signingClient = await this.getSigningClient(mnemonic); const senderAddress = await this.getWalletAddress(mnemonic); // Get Auction code ID from ADODB with fallback const auctionCodeIdResponse = await this.getADOCodeId('auction'); const auctionCodeId = auctionCodeIdResponse.code_id; // Prepare Auction instantiation message const instantiateMsg = { kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress, ...(authorizedTokenAddresses && { authorized_token_addresses: authorizedTokenAddresses }), ...(authorizedCw20Address && { authorized_cw20_address: authorizedCw20Address }) }; const fee = { amount: [{ denom: 'uandr', amount: '6250' }], gas: '250000' }; const result = await signingClient.instantiate(senderAddress, auctionCodeId, instantiateMsg, name, fee); return result; } async startAuction(auctionAddress, tokenId, tokenAddress, duration, mnemonic, startTime, coinDenom = 'uandr', startingBid, recipient) { const senderAddress = await this.getWalletAddress(mnemonic); // First, approve the auction contract to transfer the NFT const approveMsg = { approve: { spender: auctionAddress, token_id: tokenId } }; await this.executeADO(tokenAddress, approveMsg, mnemonic, [], '200000'); // **AUCTION FIX: Use official Andromeda documentation approach** // Call send_nft on the CW721 contract (not receive_nft on auction contract) const currentTime = Date.now(); const endTime = currentTime + duration; // Create the start_auction hook message as per official docs const auctionHookMsg = { start_auction: { end_time: endTime, uses_cw20: false, // Using native tokens coin_denom: coinDenom, ...(startingBid && { min_bid: startingBid }), ...(recipient && { recipient: { address: recipient } }), ...(startTime && { start_time: startTime }) } }; // Call send_nft on the CW721 contract as per official documentation const sendNftMsg = { send_nft: { contract: auctionAddress, token_id: tokenId, msg: Buffer.from(JSON.stringify(auctionHookMsg)).toString('base64') } }; console.error(`DEBUG: Using official Andromeda approach - send_nft on CW721 contract:`, JSON.stringify(sendNftMsg, null, 2)); console.error(`DEBUG: Auction hook message:`, JSON.stringify(auctionHookMsg, null, 2)); return await this.executeADO(tokenAddress, sendNftMsg, mnemonic, [], '400000'); } async placeAuctionBid(auctionAddress, tokenId, tokenAddress, bidAmount, denom, mnemonic) { // 🔍 DEBUG: Trace mnemonic parameter console.error(`🔍 PLACE_AUCTION_BID DEBUG - Mnemonic words: ${mnemonic.split(' ').length}, First: "${mnemonic.split(' ')[0]}", Full: "${mnemonic.substring(0, 50)}..."`); const msg = { place_bid: { token_id: tokenId, token_address: tokenAddress } }; return await this.executeADO(auctionAddress, msg, mnemonic, [{ denom, amount: bidAmount }], '300000'); } async finalizeAuction(auctionAddress, tokenId, tokenAddress, mnemonic) { const msg = { claim: { token_id: tokenId, token_address: tokenAddress } }; return await this.executeADO(auctionAddress, msg, mnemonic, [], '250000'); } // CW20-Staking Methods (DeFi-focused) async deployCW20Staking(name, stakingToken, rewardToken, rewardAllocation, mnemonic, unbondingPeriod) { const signingClient = await this.getSigningClient(mnemonic); const senderAddress = await this.getWalletAddress(mnemonic); // Get CW20-Staking code ID from ADODB with fallback const stakingCodeIdResponse = await this.getADOCodeId('cw20-staking'); const stakingCodeId = stakingCodeIdResponse.code_id; // Prepare CW20-Staking instantiation message (minimal working version) const instantiateMsg = { staking_token: stakingToken, kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress }; const fee = { amount: [{ denom: 'uandr', amount: '6250' }], gas: '250000' }; const result = await signingClient.instantiate(senderAddress, stakingCodeId, instantiateMsg, name, fee); return result; } async stakeCW20Tokens(stakingAddress, tokenAddress, amount, mnemonic) { // Use CW20 send hook to stake tokens const hookMsg = { stake_tokens: {} }; const sendMsg = { send: { contract: stakingAddress, amount: amount, msg: Buffer.from(JSON.stringify(hookMsg)).toString('base64') } }; return await this.executeADO(tokenAddress, sendMsg, mnemonic, [], '300000'); } async unstakeCW20Tokens(stakingAddress, amount, mnemonic) { const msg = { unstake_tokens: { amount: amount } }; return await this.executeADO(stakingAddress, msg, mnemonic, [], '300000'); } async claimStakingRewards(stakingAddress, mnemonic) { const msg = { claim_rewards: {} }; return await this.executeADO(stakingAddress, msg, mnemonic, [], '250000'); } // Merkle Airdrop Methods async deployMerkleAirdrop(name, asset, merkleRoot, totalAmount, mnemonic, startTime, endTime) { const signingClient = await this.getSigningClient(mnemonic); const senderAddress = await this.getWalletAddress(mnemonic); // Get Merkle Airdrop code ID from ADODB with fallback const airdropCodeIdResponse = await this.getADOCodeId('merkle-airdrop'); const airdropCodeId = airdropCodeIdResponse.code_id || 17; // Fallback to Code ID 17 // **FIX #1: MERKLE AIRDROP FIELD MAPPING** - Correct field names based on error // Error: "unknown field `merkle_root`, expected one of `asset_info`, `kernel_address`, `owner`, `modules`" console.error(`DEBUG: Applying Merkle Airdrop field mapping fix`); // Primary format attempt - minimal required fields only let instantiateMsg = { asset_info: asset.type === 'native' ? { native: asset.value } : { cw20: asset.value }, kernel_address: network_js_1.KERNEL_ADDRESS, owner: senderAddress, modules: [] // Add minimal modules array }; const fee = { amount: [{ denom: 'uandr', amount: '6250' }], gas