wisdom-sdk
Version:
Core business logic and data access layer for prediction markets
950 lines (943 loc) • 31.4 kB
JavaScript
;
var kv = require('@vercel/kv');
var transactions = require('@stacks/transactions');
var network = require('@stacks/network');
var backend = require('@clerk/backend');
// src/kv-store.ts
// src/logger.ts
var createLogger = () => {
const logLevel = process.env.LOG_LEVEL || "info";
const logLevels = {
debug: 0,
info: 1,
warn: 2,
error: 3
};
const currentLevelValue = logLevel in logLevels ? logLevels[logLevel] : logLevels.info;
const formatLog = (level, obj, msg) => {
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
const service = "wisdom-sdk";
const objStr = JSON.stringify(obj);
return `[${timestamp}] ${level.toUpperCase()} [${service}] ${msg || ""} ${objStr}`;
};
return {
debug: (obj, msg) => {
if (currentLevelValue <= 0) {
console.debug(formatLog("debug", obj, msg));
}
},
info: (obj, msg) => {
if (currentLevelValue <= 1) {
console.info(formatLog("info", obj, msg));
}
},
warn: (obj, msg) => {
if (currentLevelValue <= 2) {
console.warn(formatLog("warn", obj, msg));
}
},
error: (obj, msg) => {
if (currentLevelValue <= 3) {
console.error(formatLog("error", obj, msg));
}
},
child: (bindings) => {
const childLogger = createLogger();
return {
debug: (obj, msg) => childLogger.debug({ ...obj, ...bindings }, msg),
info: (obj, msg) => childLogger.info({ ...obj, ...bindings }, msg),
warn: (obj, msg) => childLogger.warn({ ...obj, ...bindings }, msg),
error: (obj, msg) => childLogger.error({ ...obj, ...bindings }, msg),
child: (nestedBindings) => childLogger.child({ ...bindings, ...nestedBindings })
};
}
};
};
var logger = createLogger();
function getContextLogger(context) {
return logger.child({ context });
}
var AppError = class extends Error {
constructor({
message,
context = "general",
code = "INTERNAL_ERROR",
originalError,
data
}) {
super(message);
this.name = "AppError";
this.context = context;
this.code = code;
this.originalError = originalError;
this.data = data;
Error.captureStackTrace(this, this.constructor);
}
// Logs this error with appropriate context and returns it
log() {
const contextLogger = getContextLogger(this.context);
const logObj = {
code: this.code,
error: this.message,
...this.originalError && { originalError: this.originalError.message },
...this.data && { data: this.data }
};
contextLogger.error(logObj, this.message);
return this;
}
};
// src/kv-store.ts
var kvLogger = logger.child({ context: "kv-store" });
var KV_PREFIXES = {
MARKET: "market",
MARKET_IDS: "market_ids",
USER_MARKETS: "user_markets",
MARKET_PARTICIPANTS: "market_participants",
MARKET_CATEGORY: "market_category",
// Index for markets by category
MARKET_STATUS: "market_status",
// Index for markets by status
PREDICTION: "prediction",
USER_PREDICTIONS: "user_predictions",
MARKET_PREDICTIONS: "market_predictions",
PREDICTION_NFT: "prediction_nft",
USER_BALANCE: "user_balance",
USER_STATS: "user_stats",
LEADERBOARD: "leaderboard",
LEADERBOARD_EARNINGS: "leaderboard_earnings",
LEADERBOARD_ACCURACY: "leaderboard_accuracy",
BUG_REPORT: "bug_report",
BUG_REPORT_IDS: "bug_report_ids",
USER_BUG_REPORTS: "user_bug_reports",
// Transaction custody-related prefixes
CUSTODY_TRANSACTION: "custody_transaction",
CUSTODY_TRANSACTION_IDS: "custody_transaction_ids",
USER_TRANSACTIONS: "user_transactions",
SIGNER_TRANSACTIONS: "signer_transactions",
MARKET_TRANSACTIONS: "market_transactions",
CUSTODY_NFT_RECEIPT: "custody_nft_receipt",
// Claim reward transaction prefixes
CLAIM_REWARD_TRANSACTION: "claim_reward_transaction",
USER_CLAIM_REWARDS: "user_claim_rewards"
};
function getKey(entityType, id) {
const prefix = KV_PREFIXES[entityType];
if (entityType === "MARKET_IDS" && !id) {
return prefix;
}
return id ? `${prefix}:${id}` : prefix;
}
async function storeEntity(entityType, id, data) {
try {
const key = getKey(entityType, id);
await kv.kv.set(key, JSON.stringify(data));
return data;
} catch (error) {
throw new AppError({
message: `Failed to store ${entityType} with ID ${id}`,
context: "kv-store",
code: "KV_STORE_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { entityType, id, operation: "store" }
}).log();
}
}
async function getEntity(entityType, id) {
try {
const key = getKey(entityType, id);
let data = await kv.kv.get(key);
if (!data && entityType === "MARKET") {
data = await kv.kv.get(`markets:${id}`);
}
if (!data) {
kvLogger.debug({ entityType, id }, `Entity not found: ${entityType}:${id}`);
return null;
}
if (typeof data !== "string") {
return data;
}
try {
return JSON.parse(data);
} catch (e) {
throw new AppError({
message: `Error parsing JSON for ${entityType} with ID ${id}`,
context: "kv-store",
code: "KV_JSON_PARSE_ERROR",
originalError: e instanceof Error ? e : new Error(String(e)),
data: { entityType, id, operation: "parse" }
}).log();
}
} catch (error) {
if (error instanceof AppError) {
throw error;
}
throw new AppError({
message: `Failed to retrieve ${entityType} with ID ${id}`,
context: "kv-store",
code: "KV_RETRIEVE_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { entityType, id, operation: "get" }
}).log();
}
}
async function deleteEntity(entityType, id) {
try {
const key = getKey(entityType, id);
await kv.kv.del(key);
if (entityType === "MARKET") ;
return true;
} catch (error) {
new AppError({
message: `Failed to delete ${entityType} with ID ${id}`,
context: "kv-store",
code: "KV_DELETE_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { entityType, id, operation: "delete" }
}).log();
return false;
}
}
async function addToSet(setType, id, memberId) {
try {
const key = getKey(setType, id);
await kv.kv.sadd(key, memberId);
if (setType === "MARKET_IDS") {
await kv.kv.sadd("market_ids", memberId);
}
return true;
} catch (error) {
new AppError({
message: `Failed to add member ${memberId} to set ${setType}:${id}`,
context: "kv-store",
code: "KV_SET_ADD_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { setType, id, memberId, operation: "sadd" }
}).log();
return false;
}
}
async function removeFromSet(setType, id, memberId) {
try {
const key = getKey(setType, id);
await kv.kv.srem(key, memberId);
if (setType === "MARKET_IDS") {
await kv.kv.srem("market_ids", memberId);
}
return true;
} catch (error) {
new AppError({
message: `Failed to remove member ${memberId} from set ${setType}:${id}`,
context: "kv-store",
code: "KV_SET_REMOVE_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { setType, id, memberId, operation: "srem" }
}).log();
return false;
}
}
async function getSetMembers(setType, id) {
try {
const key = getKey(setType, id);
let members = await kv.kv.smembers(key);
if (setType === "MARKET_IDS" && members.length === 0) {
const legacyMembers = await kv.kv.smembers("market_ids");
if (legacyMembers.length > 0) {
for (const marketId of legacyMembers) {
await addToSet("MARKET_IDS", "", marketId);
}
members = legacyMembers;
}
}
return members;
} catch (error) {
new AppError({
message: `Failed to get members from set ${setType}:${id}`,
context: "kv-store",
code: "KV_SET_MEMBERS_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { setType, id, operation: "smembers" }
}).log();
return [];
}
}
async function startTransaction() {
try {
const transaction = kv.kv.multi();
const operations = [];
const txObject = {
operations,
// Add entity to transaction
async addEntity(entityType, id, data) {
const key = getKey(entityType, id);
transaction.set(key, JSON.stringify(data));
operations.push({ type: "entity", entityType, id, data });
},
// Add to set in transaction
async addToSetInTransaction(setType, id, memberId) {
const key = getKey(setType, id);
transaction.sadd(key, memberId);
operations.push({ type: "set", entityType: setType, id: memberId });
if (setType === "MARKET_IDS") {
transaction.sadd("market_ids", memberId);
}
},
// Add to sorted set in transaction
async addToSortedSetInTransaction(setType, memberId, score) {
const key = getKey(setType);
transaction.zadd(key, { score, member: memberId });
operations.push({ type: "sortedSet", entityType: setType, id: memberId, data: score });
},
// Execute all queued commands atomically
async execute() {
try {
kvLogger.debug(
{ operationCount: operations.length },
`Executing transaction with ${operations.length} operations`
);
await transaction.exec();
return true;
} catch (error) {
const appError = new AppError({
message: "Transaction execution failed",
context: "kv-store",
code: "TRANSACTION_FAILED",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { operationCount: operations.length }
});
appError.log();
return false;
}
}
};
return txObject;
} catch (error) {
throw new AppError({
message: "Failed to start transaction",
context: "kv-store",
code: "TRANSACTION_START_ERROR",
originalError: error instanceof Error ? error : new Error(String(error))
}).log();
}
}
// src/utils.ts
function generateUUID() {
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
const getRandomBytes = (n) => {
const bytes = new Uint8Array(n);
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
crypto.getRandomValues(bytes);
} else {
for (let i = 0; i < n; i++) {
bytes[i] = Math.floor(Math.random() * 256);
}
}
return bytes;
};
const randomBytes = getRandomBytes(16);
randomBytes[6] = randomBytes[6] & 15 | 64;
randomBytes[8] = randomBytes[8] & 63 | 128;
let hex = "";
for (let i = 0; i < 16; i++) {
hex += randomBytes[i].toString(16).padStart(2, "0");
if (i === 3 || i === 5 || i === 7 || i === 9) {
hex += "-";
}
}
return hex;
}
var CONTRACT_ADDRESS = "SP2ZNGJ85ENDY6QRHQ5P2D4FXKGZWCKTB2T0Z55KS";
var CONTRACT_NAME = "blaze-welsh-v1";
var clerkClient = backend.createClerkClient({
secretKey: process.env.CLERK_SECRET_KEY,
publishableKey: process.env.CLERK_PUBLISHABLE_KEY
});
var userBalanceStore = {
/**
* Get the user's Stacks address from Clerk's publicMetadata
*/
async getUserStacksAddress(userId) {
try {
const user = await clerkClient.users.getUser(userId);
if (user.publicMetadata && typeof user.publicMetadata === "object") {
const metadata = user.publicMetadata;
if (metadata.stacksAddress) {
return metadata.stacksAddress;
}
}
console.warn(`No Stacks address found for user ${userId}`);
return null;
} catch (error) {
console.error(`Error getting Stacks address for user ${userId}:`, error);
return null;
}
},
/**
* Fetch a user's on-chain balance from the contract
*/
async fetchContractBalance(user) {
try {
const result = await transactions.fetchCallReadOnlyFunction({
contractAddress: CONTRACT_ADDRESS,
contractName: CONTRACT_NAME,
functionName: "get-balance",
functionArgs: [transactions.Cl.principal(user)],
network: network.STACKS_MAINNET,
senderAddress: user
});
const balance = result.type === transactions.ClarityType.UInt ? Number(result.value) : 0;
return balance;
} catch (error) {
console.error("Failed to fetch contract balance:", error);
return 0;
}
},
/**
* Get user balance using their Clerk ID
* Fetches directly from blockchain if Stacks address is available
*/
async getUserBalance(userId) {
try {
if (!userId) return null;
const stacksAddress = await this.getUserStacksAddress(userId);
let balance = await kvStore.getEntity("USER_BALANCE", userId);
if (!balance) {
balance = {
userId,
availableBalance: 0,
totalDeposited: 0,
totalWithdrawn: 0,
inPredictions: 0,
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
stacksAddress: null
};
}
if (stacksAddress) {
balance.stacksAddress = stacksAddress;
const contractBalance = await this.fetchContractBalance(stacksAddress);
balance.availableBalance = contractBalance;
}
return balance;
} catch (error) {
console.error(`Error getting user balance for ${userId}:`, error);
return null;
}
},
// Update user balance when making a prediction
async updateBalanceForPrediction(userId, amount) {
try {
const balance = await this.getUserBalance(userId);
if (!balance) return null;
if (balance.availableBalance < amount) {
throw new Error("Insufficient balance");
}
const updatedBalance = {
...balance,
availableBalance: balance.availableBalance - amount,
inPredictions: balance.inPredictions + amount,
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
};
await kvStore.storeEntity("USER_BALANCE", userId, updatedBalance);
return updatedBalance;
} catch (error) {
console.error(`Error updating balance for prediction, user ${userId}:`, error);
throw error;
}
},
// Update user balance when a prediction is resolved
async updateBalanceForResolvedPrediction(userId, originalAmount, winnings = 0) {
try {
const balance = await this.getUserBalance(userId);
if (!balance) return null;
const updatedBalance = {
...balance,
availableBalance: balance.availableBalance + winnings,
inPredictions: balance.inPredictions - originalAmount,
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
};
await kvStore.storeEntity("USER_BALANCE", userId, updatedBalance);
return updatedBalance;
} catch (error) {
console.error(`Error updating balance for resolved prediction, user ${userId}:`, error);
throw error;
}
},
// Add funds to user balance (for deposit functionality)
async addFunds(userId, amount) {
try {
const balance = await this.getUserBalance(userId);
if (!balance) return null;
const updatedBalance = {
...balance,
availableBalance: balance.availableBalance + amount,
totalDeposited: balance.totalDeposited + amount,
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
};
await kvStore.storeEntity("USER_BALANCE", userId, updatedBalance);
return updatedBalance;
} catch (error) {
console.error(`Error adding funds for user ${userId}:`, error);
throw error;
}
},
// Withdraw funds from user balance
async withdrawFunds(userId, amount) {
try {
const balance = await this.getUserBalance(userId);
if (!balance) return null;
if (balance.availableBalance < amount) {
throw new Error("Insufficient balance");
}
const updatedBalance = {
...balance,
availableBalance: balance.availableBalance - amount,
totalWithdrawn: balance.totalWithdrawn + amount,
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
};
await kvStore.storeEntity("USER_BALANCE", userId, updatedBalance);
return updatedBalance;
} catch (error) {
console.error(`Error withdrawing funds for user ${userId}:`, error);
throw error;
}
},
// Force refresh a user's balance from the blockchain
async refreshBalance(userId) {
try {
const stacksAddress = await this.getUserStacksAddress(userId);
if (!stacksAddress) {
throw new Error(`No Stacks address found for user ${userId}`);
}
const contractBalance = await this.fetchContractBalance(stacksAddress);
const balance = await kvStore.getEntity("USER_BALANCE", userId) || {
userId,
availableBalance: 0,
totalDeposited: 0,
totalWithdrawn: 0,
inPredictions: 0,
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
};
const updatedBalance = {
...balance,
availableBalance: contractBalance,
stacksAddress,
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
};
await kvStore.storeEntity("USER_BALANCE", userId, updatedBalance);
return updatedBalance;
} catch (error) {
console.error(`Error refreshing balance for user ${userId}:`, error);
throw error;
}
}
};
var kvStore = {
async getEntity(collection, id) {
console.log({ collection, id });
const stacksAddress = await userBalanceStore.getUserStacksAddress(id);
let availableBalance = 0;
if (stacksAddress) {
availableBalance = await userBalanceStore.fetchContractBalance(stacksAddress);
}
const balance = {
userId: id,
availableBalance,
totalDeposited: 0,
totalWithdrawn: 0,
inPredictions: 0,
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
stacksAddress
};
return balance;
},
async storeEntity(collection, id, entity) {
console.log({ collection, id, entity });
return null;
}
};
// src/bug-report-store.ts
var bugReportLogger = logger.child({ context: "bug-report-store" });
var DEFAULT_INITIAL_REWARD = 10;
var DEFAULT_CONFIRMATION_REWARD = 90;
var BugReportStore = class {
/**
* Get all bug reports
*/
async getAllBugReports() {
try {
const reportIds = await getSetMembers("BUG_REPORT_IDS", "");
if (!reportIds || reportIds.length === 0) {
return [];
}
const reports = await Promise.all(
reportIds.map(async (id) => {
const report = await getEntity("BUG_REPORT", id);
return report;
})
);
return reports.filter((report) => report !== null);
} catch (error) {
throw new AppError({
message: "Failed to retrieve bug reports",
context: "bug-report-store",
code: "BUG_REPORT_FETCH_ERROR",
originalError: error instanceof Error ? error : new Error(String(error))
}).log();
}
}
/**
* Get specific bug report by ID
*/
async getBugReport(id) {
if (!id) {
bugReportLogger.warn({}, "Attempted to get bug report with empty ID");
return null;
}
const report = await getEntity("BUG_REPORT", id);
if (!report) {
bugReportLogger.debug({ reportId: id }, `Bug report ${id} not found`);
}
return report;
}
/**
* Get all bug reports for a specific user
*/
async getUserBugReports(userId) {
try {
if (!userId) {
return [];
}
const reportIds = await getSetMembers("USER_BUG_REPORTS", userId);
if (reportIds.length === 0) {
return [];
}
const reports = await Promise.all(
reportIds.map((id) => this.getBugReport(id))
);
return reports.filter((report) => report !== null);
} catch (error) {
throw new AppError({
message: `Failed to retrieve bug reports for user ${userId}`,
context: "bug-report-store",
code: "USER_BUG_REPORTS_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { userId }
}).log();
}
}
/**
* Create a new bug report
*/
async createBugReport(data) {
try {
if (!data.title || !data.description || !data.createdBy) {
throw new AppError({
message: "Missing required bug report data",
context: "bug-report-store",
code: "BUG_REPORT_VALIDATION_ERROR",
data: {
hasTitle: !!data.title,
hasDescription: !!data.description,
hasCreatedBy: !!data.createdBy
}
}).log();
}
const id = generateUUID();
const now = (/* @__PURE__ */ new Date()).toISOString();
const bugReport = {
id,
title: data.title,
description: data.description,
severity: data.severity,
url: data.url,
createdBy: data.createdBy,
createdAt: now,
status: "open"
};
const tx = await startTransaction();
try {
await tx.addEntity("BUG_REPORT", id, bugReport);
await tx.addToSetInTransaction("BUG_REPORT_IDS", "", id);
await tx.addToSetInTransaction("USER_BUG_REPORTS", data.createdBy, id);
const success = await tx.execute();
if (!success) {
throw new AppError({
message: "Failed to create bug report - transaction failed",
context: "bug-report-store",
code: "BUG_REPORT_TRANSACTION_ERROR",
data: { reportId: id }
}).log();
}
bugReportLogger.info(
{ reportId: id, userId: data.createdBy },
`Bug report created: ${data.title}`
);
return bugReport;
} catch (error) {
if (error instanceof AppError) {
throw error;
} else {
throw new AppError({
message: "Error during bug report creation transaction",
context: "bug-report-store",
code: "BUG_REPORT_CREATE_TRANSACTION_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { title: data.title }
}).log();
}
}
} catch (error) {
if (error instanceof AppError) {
throw error;
} else {
throw new AppError({
message: "Failed to create bug report",
context: "bug-report-store",
code: "BUG_REPORT_CREATE_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { title: data.title }
}).log();
}
}
}
/**
* Update an existing bug report
*/
async updateBugReport(id, data) {
try {
const existingReport = await this.getBugReport(id);
if (!existingReport) {
bugReportLogger.warn({ reportId: id }, `Cannot update non-existent bug report with ID ${id}`);
return null;
}
const safeData = { ...data };
if (safeData.id && safeData.id !== id) {
delete safeData.id;
bugReportLogger.warn(
{ reportId: id, attemptedId: data.id },
"Attempted to change bug report ID during update - ignoring"
);
}
const updatedReport = {
...existingReport,
...safeData,
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
};
await storeEntity("BUG_REPORT", id, updatedReport);
bugReportLogger.debug(
{ reportId: id },
`Bug report updated: ${existingReport.title}`
);
return updatedReport;
} catch (error) {
throw new AppError({
message: `Failed to update bug report ${id}`,
context: "bug-report-store",
code: "BUG_REPORT_UPDATE_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { reportId: id }
}).log();
}
}
async deleteBugReport(id) {
try {
const report = await this.getBugReport(id);
if (!report) {
throw new Error(`Bug report ${id} not found`);
}
await deleteEntity("BUG_REPORT", id);
await removeFromSet("BUG_REPORT_IDS", "", id);
await removeFromSet("USER_BUG_REPORTS", report.createdBy, id);
return true;
} catch (error) {
console.error(`Error deleting bug report ${id}:`, error);
return false;
}
}
/**
* Process a reward payment for a bug report
* This handles giving the initial or confirmation reward to a user
*
* @param reportId Bug report ID
* @param userId User ID to receive the reward
* @param rewardType Type of reward (initial or confirmation)
* @param customAmount Optional custom amount (overrides defaults)
* @returns Result object with success/error status
*/
/**
* Confirm a bug report (mark as verified by admin)
* @param id Bug report ID
* @param adminId ID of the admin confirming the report
*/
async confirmBugReport(id, adminId) {
try {
const report = await this.getBugReport(id);
if (!report) {
bugReportLogger.warn({ reportId: id }, `Cannot confirm non-existent bug report: ${id}`);
return null;
}
if (report.status === "resolved") {
bugReportLogger.warn({ reportId: id }, `Bug report ${id} is already resolved`);
return report;
}
const now = (/* @__PURE__ */ new Date()).toISOString();
const updatedReport = await this.updateBugReport(id, {
status: "resolved",
confirmedBy: adminId,
confirmedAt: now,
updatedBy: adminId,
updatedAt: now
});
bugReportLogger.info(
{ reportId: id, adminId },
`Bug report confirmed: ${report.title}`
);
return updatedReport;
} catch (error) {
throw new AppError({
message: `Failed to confirm bug report ${id}`,
context: "bug-report-store",
code: "BUG_REPORT_CONFIRM_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { reportId: id, adminId }
}).log();
}
}
/**
* Pay a reward for a bug report
* @param id Bug report ID
* @param isInitialReward Whether to pay the initial (true) or confirmation (false) reward
*/
async payReward(id, isInitialReward) {
try {
const report = await this.getBugReport(id);
if (!report) {
bugReportLogger.warn({ reportId: id }, `Cannot pay reward for non-existent bug report: ${id}`);
return null;
}
const recipientId = report.createdBy;
const rewardType = isInitialReward ? "initial" : "confirmation";
const result = await this.processRewardPayment(id, recipientId, rewardType);
if (!result.success) {
throw new AppError({
message: result.error || "Failed to pay reward",
context: "bug-report-store",
code: "REWARD_PAYMENT_FAILED",
data: {
reportId: id,
userId: recipientId,
rewardType,
error: result.error
}
});
}
return result.report || null;
} catch (error) {
if (error instanceof AppError) {
throw error;
} else {
throw new AppError({
message: `Failed to pay ${isInitialReward ? "initial" : "confirmation"} reward for bug report ${id}`,
context: "bug-report-store",
code: "REWARD_PAYMENT_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { reportId: id, isInitialReward }
}).log();
}
}
}
/**
* Internal helper method to process reward payments
* This handles giving the initial or confirmation reward to a user
*/
async processRewardPayment(reportId, userId, rewardType, customAmount) {
try {
const opLogger = bugReportLogger.child({
operation: "processRewardPayment",
reportId,
userId,
rewardType
});
opLogger.info({}, `Processing ${rewardType} reward payment`);
const report = await this.getBugReport(reportId);
if (!report) {
return { success: false, error: "Bug report not found" };
}
const amount = customAmount || (rewardType === "initial" ? DEFAULT_INITIAL_REWARD : DEFAULT_CONFIRMATION_REWARD);
if (rewardType === "initial" && report.initialRewardPaid) {
opLogger.warn({}, "Initial reward already paid for this report");
return {
success: false,
error: "Initial reward already paid for this report",
report
};
}
if (rewardType === "confirmation" && report.confirmationRewardPaid) {
opLogger.warn({}, "Confirmation reward already paid for this report");
return {
success: false,
error: "Confirmation reward already paid for this report",
report
};
}
opLogger.debug({ amount }, `Processing payment of ${amount} tokens`);
const updatedBalance = await userBalanceStore.addFunds(userId, amount);
if (!updatedBalance) {
throw new AppError({
message: "Failed to update user balance",
context: "bug-report-store",
code: "BALANCE_UPDATE_FAILED",
data: { userId, amount, reportId }
});
}
const updateData = {};
if (rewardType === "initial") {
updateData.initialRewardPaid = true;
} else {
updateData.confirmationRewardPaid = true;
if (report.status !== "resolved") {
updateData.status = "resolved";
updateData.confirmedAt = (/* @__PURE__ */ new Date()).toISOString();
}
}
const updatedReport = await this.updateBugReport(reportId, updateData);
if (!updatedReport) {
throw new AppError({
message: "Failed to update bug report after payment",
context: "bug-report-store",
code: "REPORT_UPDATE_FAILED",
data: { reportId, rewardType }
});
}
opLogger.info(
{ amount, reportId, userId },
`Successfully processed ${rewardType} reward payment`
);
return {
success: true,
amount,
report: updatedReport
};
} catch (error) {
if (error instanceof AppError) {
error.log();
return { success: false, error: error.message };
} else {
const appError = new AppError({
message: `Error processing reward payment for report ${reportId}`,
context: "bug-report-store",
code: "REWARD_PROCESS_ERROR",
originalError: error instanceof Error ? error : new Error(String(error)),
data: { reportId, userId, rewardType }
}).log();
return { success: false, error: appError.message };
}
}
}
};
var bugReportStore = new BugReportStore();
exports.BugReportStore = BugReportStore;
exports.bugReportStore = bugReportStore;
//# sourceMappingURL=bug-report-store.cjs.map
//# sourceMappingURL=bug-report-store.cjs.map