wisdom-sdk
Version:
Core business logic and data access layer for prediction markets
830 lines (828 loc) • 30.8 kB
JavaScript
import { predictionContractStore } from './chunk-5SR5XDZR.js';
import { generateUUID, filterMarkets, sortMarkets, paginateResults } from './chunk-7CBIP22I.js';
import { getSetMembers, addToSet, isSetMember, startTransaction, deleteEntity, removeFromSet, getEntity, storeEntity } from './chunk-FIJAO3BQ.js';
import { logger, AppError } from './chunk-2OHF4QSJ.js';
import { makeContractCall, PostConditionMode, stringAsciiCV, broadcastTransaction, listCV } from '@stacks/transactions';
import { STACKS_MAINNET } from '@stacks/network';
var marketLogger = logger.child({ context: "market-store" });
var onChainConfig = {
enabled: process.env.ENABLE_ONCHAIN_MARKETS === "true",
privateKey: process.env.MARKET_CREATOR_PRIVATE_KEY || "",
network: STACKS_MAINNET,
contractAddress: process.env.PREDICTION_CONTRACT_ADDRESS || "SP2ZNGJ85ENDY6QRHQ5P2D4FXKGZWCKTB2T0Z55KS",
contractName: process.env.PREDICTION_CONTRACT_NAME || "blaze-welsh-predictions-v1"
};
var autoCloseConfig = {
enabled: process.env.ENABLE_AUTO_CLOSE_MARKETS !== "false",
// Enabled by default
batchSize: Number(process.env.AUTO_CLOSE_BATCH_SIZE || "50"),
closeOnChain: process.env.AUTO_CLOSE_ON_CHAIN === "true"
};
var marketStore = {
// Get all markets
async getMarkets(options) {
let marketIds = [];
if (options?.category) {
marketIds = await getSetMembers("MARKET_CATEGORY", options.category);
} else if (options?.status && options.status !== "all") {
marketIds = await getSetMembers("MARKET_STATUS", options.status);
} else {
marketIds = await getSetMembers("MARKET_IDS", "");
}
if (marketIds.length === 0) {
return {
items: [],
total: 0,
hasMore: false
};
}
const limit = options?.limit || 100;
const offset = options?.offset || (options?.cursor ? parseInt(options.cursor, 10) : 0);
let idsToFetch = marketIds;
if (options?.sortBy === "createdAt") {
idsToFetch = marketIds.slice(offset, offset + limit * 2);
}
const markets = await Promise.all(
idsToFetch.map((id) => this.getMarket(id))
);
const validMarkets = markets.filter(Boolean);
if (validMarkets.length < idsToFetch.length) {
marketLogger.warn(
{ expected: idsToFetch.length, found: validMarkets.length },
"Some markets could not be retrieved"
);
}
let filteredMarkets = validMarkets;
if (options) {
const filterOpts = {
...options,
category: options.category && idsToFetch === marketIds ? options.category : void 0,
status: options.status && idsToFetch === marketIds ? options.status : void 0
};
console.log(filterOpts);
filteredMarkets = filterMarkets(validMarkets, filterOpts);
const sortedMarkets = sortMarkets(
filteredMarkets,
options.sortBy || "createdAt",
options.sortDirection || "desc"
);
return paginateResults(sortedMarkets, {
limit: options.limit,
offset: options.offset || (options.cursor ? parseInt(options.cursor, 10) : 0)
});
}
return {
items: validMarkets,
total: validMarkets.length,
hasMore: false
};
},
// Get a specific market by ID and verify its state with blockchain
async getMarket(id, options) {
try {
const market = await getEntity("MARKET", id);
if (!market) {
return void 0;
}
if (options?.verifyWithBlockchain) {
try {
const onChainMarket = await this.getMarketInfo(id);
if (onChainMarket) {
const isOpenOnChain = onChainMarket["is-open"];
const isResolvedOnChain = onChainMarket["is-resolved"];
const winningOutcomeOnChain = Number(onChainMarket["winning-outcome"]);
const verifiedMarket = { ...market };
if (isResolvedOnChain && market.status !== "resolved") {
marketLogger.info({
marketId: id,
localStatus: market.status,
blockchainStatus: "resolved"
}, "Local market status differs from blockchain state");
verifiedMarket.status = "resolved";
verifiedMarket.resolvedOutcomeId = winningOutcomeOnChain;
if (!verifiedMarket.resolvedAt) {
verifiedMarket.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
}
if (!verifiedMarket.resolvedBy) {
verifiedMarket.resolvedBy = "blockchain-verification";
}
} else if (!isOpenOnChain && !isResolvedOnChain && market.status !== "closed") {
marketLogger.info({
marketId: id,
localStatus: market.status,
blockchainStatus: "closed"
}, "Local market status differs from blockchain state");
verifiedMarket.status = "closed";
} else if (isOpenOnChain && !isResolvedOnChain && market.status !== "active") {
marketLogger.info({
marketId: id,
localStatus: market.status,
blockchainStatus: "active"
}, "Local market status differs from blockchain state");
verifiedMarket.status = "active";
}
if (isResolvedOnChain && verifiedMarket.resolvedOutcomeId !== winningOutcomeOnChain) {
marketLogger.warn({
marketId: id,
localOutcome: verifiedMarket.resolvedOutcomeId,
blockchainOutcome: winningOutcomeOnChain
}, "Local winning outcome differs from blockchain state");
verifiedMarket.resolvedOutcomeId = winningOutcomeOnChain;
}
if (JSON.stringify(market) !== JSON.stringify(verifiedMarket)) {
marketLogger.info({ marketId: id }, "Updating market in KV store to match blockchain state");
await storeEntity("MARKET", id, verifiedMarket);
}
return verifiedMarket;
}
} catch (verificationError) {
marketLogger.error({
marketId: id,
error: verificationError instanceof Error ? verificationError.message : String(verificationError)
}, "Error verifying market with blockchain");
}
}
return market;
} catch (error) {
if (error instanceof AppError) {
throw error;
} else {
throw new AppError({
message: `Failed to retrieve market ${id}`,
context: "market-store",
code: "MARKET_GET_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { marketId: id }
}).log();
}
}
},
/**
* Get market information directly from the blockchain
* This calls the prediction contract store to get the on-chain market data
* @param id Market ID
* @returns Market information from blockchain or null if not found
*/
async getMarketInfo(id) {
try {
const marketInfo = await predictionContractStore.getMarketInfo(id);
return marketInfo;
} catch (error) {
marketLogger.error(
{ marketId: id, error: error instanceof Error ? error.message : String(error) },
"Failed to get market info from blockchain"
);
return null;
}
},
/**
* Helper function to create a market on-chain
* @param marketId Unique ID for the market
* @param name Name of the market
* @param description Description of the market
* @param outcomes List of outcome names
* @returns Promise resolving to the broadcast transaction result
*/
async createMarketOnChain(marketId, name, description, outcomes) {
try {
if (!onChainConfig.enabled) {
marketLogger.info({ marketId }, "On-chain market creation is disabled");
return null;
}
if (!onChainConfig.privateKey) {
throw new Error("Private key is required for on-chain market creation");
}
const outcomeNames = outcomes.map((outcome) => outcome.name);
const transaction = await makeContractCall({
contractAddress: onChainConfig.contractAddress,
contractName: onChainConfig.contractName,
functionName: "create-market",
functionArgs: [
stringAsciiCV(marketId),
stringAsciiCV(name.substring(0, 64)),
// Limit to 64 chars for Clarity string-ascii 64
stringAsciiCV(description.substring(0, 128)),
// Limit to 128 chars for Clarity string-ascii 128
listCV(outcomeNames.map((name2) => stringAsciiCV(name2.substring(0, 32))))
// Limit each name to 32 chars
],
senderKey: onChainConfig.privateKey,
validateWithAbi: true,
network: onChainConfig.network,
postConditionMode: PostConditionMode.Allow,
fee: 1e3
// Set appropriate fee
});
const result = await broadcastTransaction({ transaction });
marketLogger.info({
marketId,
txId: result.txid || "unknown",
successful: !!result.txid
}, "On-chain market creation transaction broadcast");
return result;
} catch (error) {
marketLogger.error({
marketId,
error: error instanceof Error ? error.message : String(error)
}, "Failed to create market on-chain");
return null;
}
},
// Create a new market
async createMarket(data) {
try {
if (!data.name || !data.description || !data.outcomes || data.outcomes.length === 0) {
throw new AppError({
message: "Missing required market data",
context: "market-store",
code: "MARKET_VALIDATION_ERROR",
data: {
hasName: !!data.name,
hasDescription: !!data.description,
outcomeCount: data.outcomes?.length || 0
}
}).log();
}
const tx = await startTransaction();
const id = generateUUID();
const now = (/* @__PURE__ */ new Date()).toISOString();
const market = {
id,
type: data.type,
name: data.name,
description: data.description,
outcomes: data.outcomes,
createdBy: data.createdBy,
category: data.category,
endDate: data.endDate,
imageUrl: data.imageUrl,
createdAt: now,
participants: 0,
poolAmount: 0,
status: "active"
};
await tx.addEntity("MARKET", id, market);
await tx.addToSetInTransaction("MARKET_IDS", "", id);
if (data.createdBy) {
await tx.addToSetInTransaction("USER_MARKETS", data.createdBy, id);
}
if (data.category) {
await tx.addToSetInTransaction("MARKET_CATEGORY", data.category, id);
}
await tx.addToSetInTransaction("MARKET_STATUS", "active", id);
const success = await tx.execute();
if (!success) {
throw new AppError({
message: "Failed to create market - transaction failed",
context: "market-store",
code: "MARKET_CREATE_TRANSACTION_ERROR",
data: { marketId: id }
}).log();
}
this.createMarketOnChain(id, data.name, data.description, data.outcomes).then((result) => {
if (result?.txid) {
marketLogger.info({
marketId: id,
txId: result.txid
}, "Market created on-chain successfully");
}
}).catch((error) => {
marketLogger.error({
marketId: id,
error: error instanceof Error ? error.message : String(error)
}, "Error in on-chain market creation");
});
marketLogger.info({ marketId: id }, `Created new market: ${market.name}`);
return market;
} catch (error) {
if (error instanceof AppError) {
throw error;
} else {
throw new AppError({
message: "Failed to create market",
context: "market-store",
code: "MARKET_CREATE_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { marketName: data.name }
}).log();
}
}
},
/**
* Resolve a market on-chain with the winning outcome
* @param marketId Unique ID for the market
* @param winningOutcomeId The ID of the winning outcome
* @returns Promise resolving to the broadcast transaction result
*/
async resolveMarketOnChain(marketId, winningOutcomeId) {
try {
if (!onChainConfig.enabled) {
marketLogger.info({ marketId }, "On-chain market resolution is disabled");
return null;
}
if (!onChainConfig.privateKey) {
throw new Error("Private key is required for on-chain market resolution");
}
const { uintCV } = await import('@stacks/transactions');
const transaction = await makeContractCall({
contractAddress: onChainConfig.contractAddress,
contractName: onChainConfig.contractName,
functionName: "resolve-market",
functionArgs: [
stringAsciiCV(marketId),
uintCV(winningOutcomeId)
],
senderKey: onChainConfig.privateKey,
validateWithAbi: true,
network: onChainConfig.network,
postConditionMode: PostConditionMode.Allow,
fee: 1e3
// Set appropriate fee
});
const result = await broadcastTransaction({ transaction });
marketLogger.info({
marketId,
txId: result.txid || "unknown",
successful: !!result?.txid
}, "On-chain market resolution transaction broadcast");
return result;
} catch (error) {
marketLogger.error({
marketId,
error: error instanceof Error ? error.message : String(error)
}, "Failed to resolve market on-chain");
return null;
}
},
// Update a market
async updateMarket(id, marketData) {
try {
const market = await this.getMarket(id);
if (!market) {
marketLogger.warn({ marketId: id }, `Cannot update non-existent market with ID ${id}`);
return void 0;
}
const safeData = { ...marketData };
if (safeData.id && safeData.id !== id) {
delete safeData.id;
marketLogger.warn(
{ marketId: id, attemptedId: marketData.id },
"Attempted to change market ID during update - ignoring"
);
}
const updatedMarket = { ...market, ...safeData };
const tx = await startTransaction();
await tx.addEntity("MARKET", id, updatedMarket);
if (marketData.category && marketData.category !== market.category) {
await removeFromSet("MARKET_CATEGORY", market.category, id);
await tx.addToSetInTransaction("MARKET_CATEGORY", marketData.category, id);
}
if (marketData.status && marketData.status !== market.status) {
await removeFromSet("MARKET_STATUS", market.status, id);
await tx.addToSetInTransaction("MARKET_STATUS", marketData.status, id);
}
await tx.execute();
if (marketData.is_resolved === true && marketData.winning_outcome !== void 0 && (!market.is_resolved || market.is_resolved === false)) {
this.resolveMarketOnChain(id, marketData.winning_outcome).then((result) => {
if (result?.txid) {
marketLogger.info({
marketId: id,
txId: result.txid,
winningOutcome: marketData.winning_outcome
}, "Market resolved on-chain successfully");
}
}).catch((error) => {
marketLogger.error({
marketId: id,
error: error instanceof Error ? error.message : String(error)
}, "Error in on-chain market resolution");
});
}
marketLogger.debug(
{ marketId: id },
`Updated market: ${market.name}`
);
return updatedMarket;
} catch (error) {
if (error instanceof AppError) {
throw error;
} else {
throw new AppError({
message: `Failed to update market ${id}`,
context: "market-store",
code: "MARKET_UPDATE_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { marketId: id }
}).log();
}
}
},
// Delete a market
async deleteMarket(id) {
try {
const market = await this.getMarket(id);
if (!market) {
return false;
}
await startTransaction();
await deleteEntity("MARKET", id);
await removeFromSet("MARKET_IDS", "", id);
if (market.category) {
await removeFromSet("MARKET_CATEGORY", market.category, id);
}
if (market.status) {
await removeFromSet("MARKET_STATUS", market.status, id);
}
if (market.createdBy) {
await removeFromSet("USER_MARKETS", market.createdBy, id);
}
return true;
} catch (error) {
console.error(`Error deleting market ${id}:`, error);
return false;
}
},
// Update market stats when a prediction is made
async updateMarketStats(marketId, outcomeId, amount, userId) {
const market = await this.getMarket(marketId);
if (!market) return void 0;
const userParticipated = await isSetMember("MARKET_PARTICIPANTS", marketId, userId);
if (!userParticipated) {
market.participants = (market.participants || 0) + 1;
await addToSet("MARKET_PARTICIPANTS", marketId, userId);
}
market.poolAmount = (market.poolAmount || 0) + amount;
const outcome = market.outcomes.find((o) => o.id === outcomeId);
if (outcome) {
outcome.votes = (outcome.votes || 0) + 1;
outcome.amount = (outcome.amount || 0) + amount;
}
return this.updateMarket(marketId, market);
},
// Get related markets based on category and similarity
async getRelatedMarkets(marketId, limit = 3) {
try {
const market = await this.getMarket(marketId);
if (!market) return [];
const result = await this.getMarkets({
status: "active",
limit: 50
// Get enough markets to find good related ones
});
const allMarkets = result.items;
const candidates = allMarkets.filter(
(m) => m.id !== marketId && // Same category
(m.category === market.category || // Or contains similar keywords in name/description
this.calculateSimilarity(m, market) > 0.3)
);
const sortedMarkets = candidates.sort(
(a, b) => this.calculateSimilarity(b, market) - this.calculateSimilarity(a, market)
);
return sortedMarkets.slice(0, limit);
} catch (error) {
console.error("Error getting related markets:", error);
return [];
}
},
// Get markets by category
async getMarketsByCategory(category, options) {
return this.getMarkets({
...options,
category
});
},
// Search markets by text
async searchMarkets(searchText, options) {
return this.getMarkets({
...options,
search: searchText
});
},
// Get trending markets (highest participation or pool amount)
async getTrendingMarkets(limit = 10) {
const result = await this.getMarkets({
status: "active",
sortBy: "poolAmount",
sortDirection: "desc",
limit
});
return result.items;
},
// Calculate similarity score between two markets
calculateSimilarity(market1, market2) {
const text1 = `${market1.name} ${market1.description}`.toLowerCase();
const text2 = `${market2.name} ${market2.description}`.toLowerCase();
const words1 = new Set(text1.split(/\W+/));
const words2 = new Set(text2.split(/\W+/));
const intersection = new Set(Array.from(words1).filter((x) => words2.has(x)));
const union = new Set(Array.from(words1).concat(Array.from(words2)));
return intersection.size / union.size;
},
// Migration: Build indexes for existing markets
async buildMarketIndexes() {
try {
const marketIds = await getSetMembers("MARKET_IDS", "");
const markets = await Promise.all(
marketIds.map((id) => this.getMarket(id))
);
const validMarkets = markets.filter(Boolean);
let indexedCount = 0;
for (const market of validMarkets) {
if (market.category) {
await addToSet("MARKET_CATEGORY", market.category, market.id);
}
if (market.status) {
await addToSet("MARKET_STATUS", market.status, market.id);
}
indexedCount++;
}
marketLogger.info(
{ total: marketIds.length, indexed: indexedCount },
"Market indexes built successfully"
);
return { success: true, indexed: indexedCount };
} catch (error) {
marketLogger.error(
{ error: error instanceof Error ? error.message : String(error) },
"Error building market indexes"
);
return { success: false, indexed: 0 };
}
},
/**
* Close a market on-chain
* @param marketId Unique ID for the market
* @returns Promise resolving to the broadcast transaction result
*/
async closeMarketOnChain(marketId) {
try {
if (!onChainConfig.enabled) {
marketLogger.info({ marketId }, "On-chain market close is disabled");
return null;
}
if (!onChainConfig.privateKey) {
throw new Error("Private key is required for on-chain market close");
}
const transaction = await makeContractCall({
contractAddress: onChainConfig.contractAddress,
contractName: onChainConfig.contractName,
functionName: "close-market",
functionArgs: [
stringAsciiCV(marketId)
],
senderKey: onChainConfig.privateKey,
validateWithAbi: true,
network: onChainConfig.network,
postConditionMode: PostConditionMode.Allow,
fee: 1e3
});
const result = await broadcastTransaction({ transaction });
marketLogger.info({
marketId,
txId: result.txid || "unknown",
successful: !!result.txid
}, "On-chain market close transaction broadcast");
return result;
} catch (error) {
marketLogger.error({
marketId,
error: error instanceof Error ? error.message : String(error)
}, "Failed to close market on-chain");
return null;
}
},
/**
* Automatically close markets that have passed their end date
* This function is meant to be called by a cron job
* @returns Object with stats about markets that were closed
*/
async autoCloseExpiredMarkets() {
try {
if (!autoCloseConfig.enabled) {
return { success: true, processed: 0, closed: 0, errors: 0 };
}
const activeMarkets = await getSetMembers("MARKET_STATUS", "active");
let processed = 0;
let closed = 0;
let errors = 0;
let onChainSucceeded = 0;
let onChainFailed = 0;
const now = (/* @__PURE__ */ new Date()).toISOString();
for (let i = 0; i < activeMarkets.length; i += autoCloseConfig.batchSize) {
const batch = activeMarkets.slice(i, i + autoCloseConfig.batchSize);
const markets = await Promise.all(
batch.map((id) => this.getMarket(id))
);
const expiredMarkets = markets.filter(Boolean).filter((market) => market.endDate < now && market.status === "active");
for (const market of expiredMarkets) {
processed++;
try {
await this.updateMarket(market.id, {
status: "closed",
is_open: false
});
closed++;
if (autoCloseConfig.closeOnChain) {
const onChainResult = await this.closeMarketOnChain(market.id);
if (onChainResult?.txid) {
onChainSucceeded++;
marketLogger.info({
marketId: market.id,
txId: onChainResult.txid
}, "Market closed on-chain successfully");
} else {
onChainFailed++;
marketLogger.warn({
marketId: market.id
}, "Failed to close market on-chain");
}
}
marketLogger.info({
marketId: market.id,
name: market.name,
endDate: market.endDate
}, "Automatically closed expired market");
} catch (error) {
errors++;
marketLogger.error({
marketId: market.id,
error: error instanceof Error ? error.message : String(error)
}, "Error closing expired market");
}
}
}
marketLogger.info({
processed,
closed,
errors,
onChainSucceeded,
onChainFailed
}, "Completed auto-close of expired markets");
return {
success: true,
processed,
closed,
errors,
onChainSucceeded,
onChainFailed
};
} catch (error) {
marketLogger.error({
error: error instanceof Error ? error.message : String(error)
}, "Failed to auto-close expired markets");
return { success: false, processed: 0, closed: 0, errors: 1 };
}
},
/**
* Synchronize market statuses with blockchain state
* This checks all markets against their on-chain state and updates them if they don't match
* @returns Results of the synchronization operation
*/
async syncMarketsWithBlockchain() {
try {
marketLogger.info({}, "Starting market synchronization with blockchain");
const marketsResult = await this.getMarkets({ limit: 500 });
const markets = marketsResult.items;
if (markets.length === 0) {
return {
success: true,
processed: 0,
updated: 0,
errors: 0,
syncResults: []
};
}
let processed = 0;
let updated = 0;
let errors = 0;
const syncResults = [];
for (const market of markets) {
try {
processed++;
marketLogger.debug({ marketId: market.id }, `Checking on-chain state for market ${market.name}`);
const onChainMarket = await this.getMarketInfo(market.id);
if (!onChainMarket) {
marketLogger.warn({ marketId: market.id }, `Market ${market.name} not found on blockchain`);
syncResults.push({
marketId: market.id,
name: market.name,
status: "error",
error: "Market not found on blockchain"
});
errors++;
continue;
}
const isOpenOnChain = onChainMarket["is-open"];
const isResolvedOnChain = onChainMarket["is-resolved"];
const winningOutcomeOnChain = onChainMarket["winning-outcome"];
const isStatusMatch = market.status === "active" && isOpenOnChain && !isResolvedOnChain || market.status === "resolved" && isResolvedOnChain || market.status === "closed" && !isOpenOnChain;
const isOutcomeMatch = !isResolvedOnChain || isResolvedOnChain && market.resolvedOutcomeId !== void 0 && market.resolvedOutcomeId === winningOutcomeOnChain;
if (isStatusMatch && isOutcomeMatch) {
marketLogger.debug({ marketId: market.id }, `Market ${market.name} is already synced with blockchain`);
syncResults.push({
marketId: market.id,
name: market.name,
status: "already_synced",
onChainData: {
"is-open": isOpenOnChain,
"is-resolved": isResolvedOnChain,
"winning-outcome": winningOutcomeOnChain
},
localData: {
status: market.status,
resolvedOutcomeId: market.resolvedOutcomeId
}
});
continue;
}
const updates = {};
if (!isStatusMatch) {
if (isResolvedOnChain) {
updates.status = "resolved";
updates.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
if (!market.resolvedBy) {
updates.resolvedBy = "blockchain-sync";
}
} else if (!isOpenOnChain) {
updates.status = "closed";
} else {
updates.status = "active";
}
}
if (isResolvedOnChain && (market.resolvedOutcomeId === void 0 || market.resolvedOutcomeId !== winningOutcomeOnChain)) {
updates.resolvedOutcomeId = winningOutcomeOnChain;
if (!updates.status) {
updates.status = "resolved";
}
if (!updates.resolvedAt) {
updates.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
}
if (!market.resolvedBy && !updates.resolvedBy) {
updates.resolvedBy = "blockchain-sync";
}
}
if (Object.keys(updates).length > 0) {
marketLogger.info({
marketId: market.id,
updates
}, `Updating market ${market.name} to match blockchain state`);
await this.updateMarket(market.id, updates);
updated++;
syncResults.push({
marketId: market.id,
name: market.name,
status: "updated",
onChainData: {
"is-open": isOpenOnChain,
"is-resolved": isResolvedOnChain,
"winning-outcome": winningOutcomeOnChain
},
localData: {
status: market.status,
resolvedOutcomeId: market.resolvedOutcomeId
}
});
}
} catch (error) {
errors++;
marketLogger.error({
marketId: market.id,
error: error instanceof Error ? error.message : String(error)
}, `Error syncing market ${market.name} with blockchain`);
syncResults.push({
marketId: market.id,
name: market.name,
status: "error",
error: error instanceof Error ? error.message : String(error)
});
}
}
marketLogger.info({
processed,
updated,
errors
}, "Completed market synchronization with blockchain");
return {
success: true,
processed,
updated,
errors,
syncResults
};
} catch (error) {
marketLogger.error({
error: error instanceof Error ? error.message : String(error)
}, "Failed to synchronize markets with blockchain");
return {
success: false,
processed: 0,
updated: 0,
errors: 1,
syncResults: []
};
}
}
};
export { marketStore };
//# sourceMappingURL=chunk-IA2KNKYT.js.map
//# sourceMappingURL=chunk-IA2KNKYT.js.map