Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions convex/analytics/blockchainData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@
import { internalAction, internalQuery } from "../_generated/server";
import { internal, api } from "../_generated/api";

interface TokenPriceData {
price: number;
marketCap: number;
supply: number;
reserveBalance: number;
isGraduated: boolean;
}

// Fetch real price data from blockchain
export const fetchTokenPrice: any = internalAction({
export const fetchTokenPrice = internalAction({
args: {
tokenId: v.id("memeCoins"),
contractAddress: v.string(),
Expand All @@ -11,7 +19,7 @@
},
handler: async (ctx, args) => {
// Get bonding curve state from blockchain
const bondingCurve = await ctx.runQuery(api.bondingCurve.getBondingCurve, { tokenId: args.tokenId });

Check failure on line 22 in convex/analytics/blockchainData.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Property 'bondingCurve' does not exist on type '{ memeCoins: { listMemeCoins: FunctionReference<"query", "public", { limit?: number | undefined; }, { deployment: { _id: Id<"deployments">; _creationTime: number; gasUsed?: number | undefined; deploymentCost?: number | undefined; ... 4 more ...; deployedAt: number; } | null; ... 15 more ...; status: "pending" | ... ...'. Did you mean 'bondingCurveApi'?

Check failure on line 22 in convex/analytics/blockchainData.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Type instantiation is excessively deep and possibly infinite.

Check failure on line 22 in convex/analytics/blockchainData.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Property 'bondingCurve' does not exist on type '{ memeCoins: { listMemeCoins: FunctionReference<"query", "public", { limit?: number | undefined; }, { deployment: { _id: Id<"deployments">; _creationTime: number; gasUsed?: number | undefined; deploymentCost?: number | undefined; ... 4 more ...; deployedAt: number; } | null; ... 15 more ...; status: "pending" | ... ...'. Did you mean 'bondingCurveApi'?

Check failure on line 22 in convex/analytics/blockchainData.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Type instantiation is excessively deep and possibly infinite.

if (!bondingCurve || !bondingCurve.contractAddress) {
throw new Error("Bonding curve not found");
Expand All @@ -23,18 +31,34 @@
blockchain: args.blockchain as "ethereum" | "bsc",
});

return {
const result: TokenPriceData = {
price: parseFloat(state.currentPrice),
marketCap: state.marketCap,
supply: parseFloat(state.tokenSupply),
reserveBalance: parseFloat(state.reserveBalance),
isGraduated: state.isGraduated,
};

return result;
},
});

interface TradingEventData {
volume24h: number;
transactions24h: number;
holders: number;
lastBlock: number;
events: Array<{
type: string;
buyer?: string;
ethSpent?: string;
ethReceived?: string;
blockNumber: number;
}>;
}

// Fetch trading events from blockchain
export const fetchTradingEvents: any = internalAction({
export const fetchTradingEvents = internalAction({
args: {
tokenId: v.id("memeCoins"),
bondingCurveAddress: v.string(),
Expand Down Expand Up @@ -71,18 +95,29 @@
}
}

return {
const result: TradingEventData = {
volume24h,
transactions24h,
holders: uniqueBuyers.size,
lastBlock: events.lastBlock,
events: events.events,
};

return result;
},
});

interface AnalyticsUpdateResult {
price: number;
marketCap: number;
volume24h: number;
holders: number;
transactions24h: number;
priceChange24h: number;
}

// Update analytics with real blockchain data
export const updateAnalyticsFromBlockchain: any = internalAction({
export const updateAnalyticsFromBlockchain = internalAction({
args: {
tokenId: v.id("memeCoins"),
},
Expand All @@ -92,7 +127,7 @@
if (!deployment) throw new Error("Token not deployed");

// Get bonding curve info
const bondingCurve = await ctx.runQuery(api.bondingCurve.getBondingCurve, { tokenId: args.tokenId });

Check failure on line 130 in convex/analytics/blockchainData.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Property 'bondingCurve' does not exist on type '{ memeCoins: { listMemeCoins: FunctionReference<"query", "public", { limit?: number | undefined; }, { deployment: { _id: Id<"deployments">; _creationTime: number; gasUsed?: number | undefined; deploymentCost?: number | undefined; ... 4 more ...; deployedAt: number; } | null; ... 15 more ...; status: "pending" | ... ...'. Did you mean 'bondingCurveApi'?

Check failure on line 130 in convex/analytics/blockchainData.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Property 'bondingCurve' does not exist on type '{ memeCoins: { listMemeCoins: FunctionReference<"query", "public", { limit?: number | undefined; }, { deployment: { _id: Id<"deployments">; _creationTime: number; gasUsed?: number | undefined; deploymentCost?: number | undefined; ... 4 more ...; deployedAt: number; } | null; ... 15 more ...; status: "pending" | ... ...'. Did you mean 'bondingCurveApi'?
if (!bondingCurve || !bondingCurve.contractAddress) throw new Error("Bonding curve not found");

// Simulate price data for now
Expand Down
44 changes: 30 additions & 14 deletions src/components/MultiSigManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,46 @@ export function MultiSigManager({ tokenId, isOwner }: MultiSigManagerProps) {
const [isDeploying, setIsDeploying] = useState(false);
const [owners, setOwners] = useState<string[]>(["", ""]);
const [requiredConfirmations, setRequiredConfirmations] = useState(2);

const [currentUserAddress, setCurrentUserAddress] = useState<string>("");

const multiSigWallet = useQuery(api.security.multiSig.getMultiSigWallet, { tokenId });
const pendingTransactions = useQuery(api.security.multiSig.getPendingTransactions, {
multiSigAddress: multiSigWallet?.address || "",
});

const tokenData = useQuery(api.memeCoins.get, { id: tokenId });

const deployMultiSig = useAction(api.security.multiSig.deployMultiSigWallet);
const confirmTransaction = useAction(api.security.multiSig.confirmMultiSigTransaction);

const handleDeploy = async () => {
const validOwners = owners.filter(o => o.trim().length === 42 && o.startsWith("0x"));

if (validOwners.length < 2) {
toast.error("At least 2 valid owner addresses required");
return;
}

if (requiredConfirmations < 1 || requiredConfirmations > validOwners.length) {
toast.error("Invalid number of required confirmations");
return;
}


// Get blockchain from token deployment data
const blockchain = tokenData?.blockchain || "ethereum";

setIsDeploying(true);
try {
const result = await deployMultiSig({
tokenId,
owners: validOwners,
requiredConfirmations,
blockchain: "ethereum", // TODO: Get from token deployment
blockchain,
});

toast.success("Multi-sig wallet deployed successfully!");
} catch (error) {
console.error("Failed to deploy multi-sig:", error);
const errorMessage = error instanceof Error ? error.message : "Unknown error";
console.error("Failed to deploy multi-sig:", errorMessage);
toast.error("Failed to deploy multi-sig wallet");
} finally {
setIsDeploying(false);
Expand All @@ -61,22 +67,29 @@ export function MultiSigManager({ tokenId, isOwner }: MultiSigManagerProps) {

const handleConfirm = async (txIndex: number) => {
if (!multiSigWallet) return;


// Get blockchain from token deployment data
const blockchain = tokenData?.blockchain || "ethereum";

// Get current user's address from wallet or use first owner as fallback
const confirmerAddress = currentUserAddress || multiSigWallet.owners[0];

try {
const result = await confirmTransaction({
multiSigAddress: multiSigWallet.address,
txIndex,
blockchain: "ethereum", // TODO: Get from deployment
confirmer: "owner1", // TODO: Get current user's owner ID
blockchain,
confirmer: confirmerAddress,
});

if (result.executed) {
toast.success("Transaction executed successfully!");
} else {
toast.success("Transaction confirmed");
}
} catch (error) {
console.error("Failed to confirm transaction:", error);
const errorMessage = error instanceof Error ? error.message : "Unknown error";
console.error("Failed to confirm transaction:", errorMessage);
toast.error("Failed to confirm transaction");
}
};
Expand Down Expand Up @@ -192,7 +205,10 @@ export function MultiSigManager({ tokenId, isOwner }: MultiSigManagerProps) {
<Button
size="sm"
onClick={() => handleConfirm(tx.txIndex)}
disabled={tx.confirmations.some(c => c.confirmer === "owner1")} // TODO: Check current user
disabled={tx.confirmations.some(c =>
c.confirmer === currentUserAddress ||
(currentUserAddress === "" && c.confirmer === multiSigWallet?.owners[0])
)}
>
Confirm
</Button>
Expand Down
141 changes: 141 additions & 0 deletions src/lib/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* Centralized error handling utilities for TokenForge
* Provides type-safe error handling and consistent error messages
*/

export class TokenForgeError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly details?: Record<string, unknown>
) {
super(message);
this.name = 'TokenForgeError';
}
}

export class WalletConnectionError extends TokenForgeError {
constructor(message: string, details?: Record<string, unknown>) {
super(message, 'WALLET_CONNECTION_ERROR', details);
this.name = 'WalletConnectionError';
}
}

export class TransactionError extends TokenForgeError {
constructor(message: string, details?: Record<string, unknown>) {
super(message, 'TRANSACTION_ERROR', details);
this.name = 'TransactionError';
}
}

export class ValidationError extends TokenForgeError {
constructor(message: string, details?: Record<string, unknown>) {
super(message, 'VALIDATION_ERROR', details);
this.name = 'ValidationError';
}
}

export class DeploymentError extends TokenForgeError {
constructor(message: string, details?: Record<string, unknown>) {
super(message, 'DEPLOYMENT_ERROR', details);
this.name = 'DeploymentError';
}
}

/**
* Type guard to check if error is a TokenForgeError
*/
export function isTokenForgeError(error: unknown): error is TokenForgeError {
return error instanceof TokenForgeError;
}

/**
* Safely extract error message from unknown error type
*/
export function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
if (typeof error === 'string') {
return error;
}
return 'An unknown error occurred';
}

/**
* Extract error code from various error types
*/
export function getErrorCode(error: unknown): string | undefined {
if (isTokenForgeError(error)) {
return error.code;
}
if (typeof error === 'object' && error !== null && 'code' in error) {
const code = (error as { code: unknown }).code;
if (typeof code === 'string' || typeof code === 'number') {
return String(code);
}
}
return undefined;
}

/**
* Format error for logging with structured data
*/
export function formatErrorForLogging(error: unknown): Record<string, unknown> {
const baseLog: Record<string, unknown> = {
message: getErrorMessage(error),
timestamp: new Date().toISOString(),
};

if (error instanceof Error) {
baseLog.name = error.name;
baseLog.stack = error.stack;
}

if (isTokenForgeError(error)) {
baseLog.code = error.code;
baseLog.details = error.details;
}

return baseLog;
}

/**
* Handle async errors with proper typing
*/
export async function handleAsyncError<T>(
fn: () => Promise<T>,
fallback?: T
): Promise<T | undefined> {
try {
return await fn();
} catch (error) {
console.error('Async operation failed:', formatErrorForLogging(error));
return fallback;
}
}

/**
* Retry an async operation with exponential backoff
*/
export async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
let lastError: unknown;

for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
lastError = error;
if (i < maxRetries - 1) {
const delay = baseDelay * Math.pow(2, i);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}

throw lastError;
}
Loading
Loading