@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
JavaScript
"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