import {
  prepareContractCall,
  readContract,
  sendTransaction,
  waitForReceipt,
} from "thirdweb";
import { balanceOf } from "thirdweb/extensions/erc20";
import type { Account } from "thirdweb/wallets";
import { getBytes32FromMultiash } from "./multihash";
export { formatTxError } from "./formatTxError";
import {
  LOCAL_PROVIDER_ROLE,
  MASTER_ROLE,
  STAFF_ROLE,
  erc20,
  eventManager,
  eventTicket,
  revenueSplitter,
} from "./state";
export { client, wallets, cvlandChain, erc20, revenueSplitter } from "./state";

export async function mintERC20(
  account: Account,
  amount: bigint,
): Promise<TxHash> {
  const transaction = prepareContractCall({
    contract: erc20,
    method: "mint",
    params: [BigInt(amount)],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export function getCurrencySymbol(): string {
  return "/icons/tether.svg";
}

export type TxHash = `0x${string}`;

export async function submitEventPlaceRequest(
  account: Account,
  maxTickets: number,
  minTickets: number,
  minPrice: bigint,
  daysBeforeCancel: number,
  minDays: number,
  available: boolean,
  metaCID: string,
  eventDepositSize: bigint,
): Promise<TxHash> {
  const multihash = getBytes32FromMultiash(metaCID);
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "submitEventPlaceRequest",
    params: [
      maxTickets,
      minTickets,
      minPrice,
      daysBeforeCancel,
      minDays,
      available,
      multihash.digest,
      multihash.hashFunction,
      multihash.size,
      eventDepositSize,
    ],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function approveEventPlace(
  account: Account,
  placeId: bigint,
  deposit: bigint,
): Promise<TxHash> {
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "approveEventPlace",
    params: [placeId, deposit],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function declineEventPlace(
  account: Account,
  placeId: bigint,
): Promise<TxHash> {
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "declineEventPlace",
    params: [placeId],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function updatePlace(
  account: Account,
  placeId: bigint,
  maxTickets: number,
  minTickets: number,
  minPrice: bigint,
  daysBeforeCancel: number,
  minDays: number,
  available: boolean,
  metaCID: string,
  eventDepositSize: bigint,
): Promise<TxHash> {
  const multihash = getBytes32FromMultiash(metaCID);
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "updateEventPlace",
    params: [
      BigInt(placeId),
      maxTickets,
      minTickets,
      minPrice,
      daysBeforeCancel,
      minDays,
      available,
      multihash.digest,
      multihash.hashFunction,
      multihash.size,
      eventDepositSize,
    ],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function approveSubmitEventRequest(
  account: Account,
  deposit: bigint,
) {
  const approveTransaction = prepareContractCall({
    contract: erc20,
    method: "approve",
    params: [eventManager.address, deposit],
  });
  await sendTransaction({ account, transaction: approveTransaction });
}

export interface CategoryInput {
  name: string;
  discountPercentage: number;
  quota: number;
  hasQuota: boolean;
}

export interface SubmitEventResult {
  txHash: TxHash;
  eventId: bigint;
}

export async function submitEventRequest(
  account: Account,
  eventPlaceId: bigint,
  ticketPrice: bigint,
  startDate: bigint,
  daysAmount: number,
  metaCID: string,
  categories: CategoryInput[],
): Promise<SubmitEventResult> {
  const { digest, hashFunction, size } = getBytes32FromMultiash(metaCID);
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "submitEventRequest",
    params: [
      eventPlaceId,
      ticketPrice,
      startDate,
      daysAmount,
      digest,
      hashFunction,
      size,
      categories,
    ],
  });
  const result = await sendTransaction({ account, transaction });

  // Wait for receipt and then get the events count to determine the new event ID
  await waitForReceipt({
    client: eventManager.client,
    chain: eventManager.chain,
    transactionHash: result.transactionHash,
  });

  // Get the total events count - the new event ID is events.length - 1
  const eventsCount = await readContract({
    contract: eventManager,
    method: "getEventsCount",
    params: [],
  });
  const eventId = eventsCount - 1n;

  return {
    txHash: result.transactionHash,
    eventId,
  };
}

export async function updateEvent(
  account: Account,
  eventId: bigint,
  eventPlaceId: bigint,
  ticketPrice: bigint,
  startDate: bigint,
  daysAmount: number,
  metaCID: string,
): Promise<TxHash> {
  const { digest, hashFunction, size } = getBytes32FromMultiash(metaCID);
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "updateEvent",
    params: [
      eventId,
      eventPlaceId,
      ticketPrice,
      startDate,
      daysAmount,
      digest,
      hashFunction,
      size,
    ],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

async function validateEventApproval(
  account: Account,
  eventId: bigint,
): Promise<{ valid: boolean; reason?: string }> {
  try {
    // Check if user has MASTER_ROLE - master can approve any event
    const hasMasterRole = await readContract({
      contract: eventManager,
      method: "hasRole",
      params: [MASTER_ROLE, account.address],
    });
    console.log("User has MASTER_ROLE:", hasMasterRole);

    // If master, skip ownership checks
    if (hasMasterRole) {
      // Still check event status for master
      const event = await readContract({
        contract: eventManager,
        method: "events",
        params: [eventId],
      });
      console.log("Event details:", event);

      // Check event status (should be Submitted = 0)
      if (event[5] !== 0) {
        const statusNames = [
          "Submitted",
          "Approved",
          "Declined",
          "Cancelled",
          "Closed",
        ];
        return {
          valid: false,
          reason: `Event status is ${statusNames[event[5]]} but must be Submitted`,
        };
      }

      return { valid: true };
    }

    // Check if user has LOCAL_PROVIDER_ROLE
    const hasRole = await readContract({
      contract: eventManager,
      method: "hasRole",
      params: [LOCAL_PROVIDER_ROLE, account.address],
    });
    console.log("User has LOCAL_PROVIDER_ROLE:", hasRole);

    if (!hasRole) {
      return {
        valid: false,
        reason: "You don't have LOCAL_PROVIDER_ROLE permission",
      };
    }

    // Get event details
    const event = await readContract({
      contract: eventManager,
      method: "events",
      params: [eventId],
    });
    console.log("Event details:", event);

    // Check event status (should be Submitted = 0)
    if (event[5] !== 0) {
      // event[5] is status
      const statusNames = [
        "Submitted",
        "Approved",
        "Declined",
        "Cancelled",
        "Closed",
      ];
      return {
        valid: false,
        reason: `Event status is ${statusNames[event[5]]} but must be Submitted`,
      };
    }

    // Get event place details
    const eventPlaceId = event[1]; // event[1] is eventPlaceId
    const eventPlace = await readContract({
      contract: eventManager,
      method: "eventPlaces",
      params: [eventPlaceId],
    });
    console.log("Event place details:", eventPlace);

    // Check if the event place provider matches the caller
    const placeProvider = eventPlace[1]; // eventPlace[1] is provider
    console.log("Place provider:", placeProvider, "Caller:", account.address);

    if (placeProvider.toLowerCase() !== account.address.toLowerCase()) {
      return {
        valid: false,
        reason: `This event belongs to place owned by ${placeProvider}, but you are ${account.address}`,
      };
    }

    return { valid: true };
  } catch (error) {
    console.error("Validation failed:", error);
    return {
      valid: false,
      reason: `Validation check failed: ${error instanceof Error ? error.message : "Unknown error"}`,
    };
  }
}

export async function approveEvent(
  account: Account,
  eventId: bigint,
  distributionProfileId: bigint,
): Promise<TxHash> {
  console.log("approveEvent called with:", {
    account: account.address,
    eventId,
    distributionProfileId,
  });
  console.log("eventManager contract:", {
    address: eventManager.address,
    chain: eventManager.chain,
  });

  // Pre-flight validation
  const validation = await validateEventApproval(account, eventId);
  if (!validation.valid) {
    console.error("Pre-flight validation failed:", validation.reason);
    throw new Error(validation.reason);
  }

  try {
    const transaction = prepareContractCall({
      contract: eventManager,
      method: "approveEvent",
      params: [eventId, distributionProfileId],
    });
    console.log("prepareContractCall result:", transaction);
    console.log("Sending transaction...");
    const result = await sendTransaction({ account, transaction });
    console.log("sendTransaction result:", result);
    return result.transactionHash;
  } catch (error) {
    console.error("approveEvent failed:", error);
    // Try to extract more meaningful error info
    if (error && typeof error === "object" && "data" in error) {
      const errorData = (error as { data?: { message?: string } }).data;
      console.error("Contract revert reason:", errorData?.message);
    }
    throw error;
  }
}

export async function declineEvent(
  account: Account,
  eventId: bigint,
): Promise<TxHash> {
  console.log("declineEvent called with:", {
    account: account.address,
    eventId,
  });
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "declineEvent",
    params: [eventId],
  });
  console.log("prepareContractCall result:", transaction);
  const result = await sendTransaction({ account, transaction });
  console.log("sendTransaction result:", result);
  return result.transactionHash;
}

export async function closeEvent(
  account: Account,
  eventId: bigint,
): Promise<TxHash> {
  console.log("closeEvent called with:", { account: account.address, eventId });
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "closeEvent",
    params: [eventId],
  });
  console.log("prepareContractCall result:", transaction);
  const result = await sendTransaction({ account, transaction });
  console.log("sendTransaction result:", result);
  return result.transactionHash;
}

export async function cancelEvent(
  account: Account,
  eventId: bigint,
): Promise<TxHash> {
  console.log("cancelEvent called with:", {
    account: account.address,
    eventId,
  });
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "cancelEvent",
    params: [eventId],
  });
  console.log("prepareContractCall result:", transaction);
  const result = await sendTransaction({ account, transaction });
  console.log("sendTransaction result:", result);
  return result.transactionHash;
}

export async function approveMintTicket(account: Account, amount: bigint) {
  const transaction = prepareContractCall({
    contract: erc20,
    method: "approve",
    params: [eventManager.address, amount],
  });
  await sendTransaction({ account, transaction });
}

export async function mintTicket(
  account: Account,
  eventId: bigint,
  categoryId: bigint,
  socialsCID: string,
  referrer?: string,
): Promise<TxHash> {
  const { digest, hashFunction, size } = getBytes32FromMultiash(socialsCID);
  const mintTransaction = prepareContractCall({
    contract: eventManager,
    method: "mintTicket",
    params: [
      eventId,
      categoryId,
      digest,
      hashFunction,
      size,
      referrer ?? "0x0000000000000000000000000000000000000000",
    ],
  });
  const { transactionHash } = await sendTransaction({
    account,
    transaction: mintTransaction,
  });
  return transactionHash;
}

export async function mintTickets(
  account: Account,
  eventId: bigint,
  categoryId: bigint,
  amount: bigint,
  socialsCID: string,
  referrer?: string,
): Promise<TxHash> {
  const { digest, hashFunction, size } = getBytes32FromMultiash(socialsCID);
  const mintTransaction = prepareContractCall({
    contract: eventManager,
    method: "mintTickets",
    params: [
      eventId,
      categoryId,
      amount,
      digest,
      hashFunction,
      size,
      referrer ?? "0x0000000000000000000000000000000000000000",
    ],
  });
  const { transactionHash } = await sendTransaction({
    account,
    transaction: mintTransaction,
  });
  return transactionHash;
}

export async function redeemTicket(
  account: Account,
  ticketId: bigint,
): Promise<TxHash> {
  const transaction = prepareContractCall({
    contract: eventTicket,
    method: "redeemTicket",
    params: [ticketId],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function assignStaff(
  account: Account,
  address: string,
): Promise<TxHash> {
  const transaction = prepareContractCall({
    // STAFF_ROLE is enforced on the ticket contract (redeemTicket), not on EventManager.
    contract: eventTicket,
    method: "grantRole",
    params: [STAFF_ROLE, address],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function removeStaff(
  account: Account,
  address: string,
): Promise<TxHash> {
  const transaction = prepareContractCall({
    // STAFF_ROLE is enforced on the ticket contract (redeemTicket), not on EventManager.
    contract: eventTicket,
    method: "revokeRole",
    params: [STAFF_ROLE, address],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function grantLocalProvider(
  account: Account,
  address: string,
  defaultSharePercent: number,
): Promise<TxHash> {
  const shareBps = BigInt(Math.round(defaultSharePercent * 100));
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "grantLocalProvider",
    params: [address, Number(shareBps)],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function removeRevokeLocalProvider(
  account: Account,
  address: string,
): Promise<TxHash> {
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "revokeLocalProvider",
    params: [address],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function revokeVerifiedShaman(
  account: Account,
  address: string,
): Promise<TxHash> {
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "revokeVerifiedShaman",
    params: [address],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function hasEnoughtTokens(
  account: Account,
  tokens: bigint,
): Promise<{
  enoughTokens: boolean;
  balanceAfterPayment: bigint;
}> {
  const erc20Balance = await balanceOf({
    contract: erc20,
    address: account.address,
  });
  console.log("ERC20 balance", erc20Balance);
  const balanceAfterPayment = erc20Balance - tokens;
  return {
    enoughTokens: balanceAfterPayment >= BigInt(0),
    balanceAfterPayment: babs(balanceAfterPayment),
  };
}

function babs(x: bigint): bigint {
  return x < 0 ? -x : x;
}

export async function createCategory(
  account: Account,
  eventId: bigint,
  name: string,
  discountPercentage: number,
  quota: number,
  hasQuota: boolean,
): Promise<TxHash> {
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "createCategory",
    params: [eventId, name, discountPercentage, quota, hasQuota],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function updateCategory(
  account: Account,
  _eventId: bigint,
  categoryId: bigint,
  name: string,
  discountPercentage: number,
  quota: number,
  hasQuota: boolean,
): Promise<TxHash> {
  const transaction = prepareContractCall({
    contract: eventManager,
    method: "updateCategory",
    params: [categoryId, name, discountPercentage, quota, hasQuota],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

// ============================================================================
// Distribution Profile Functions
// ============================================================================

export async function createDistributionProfile(
  account: Account,
  recipients: string[],
  shares: number[],
): Promise<TxHash> {
  const transaction = prepareContractCall({
    contract: revenueSplitter,
    method: "createDistributionProfile",
    params: [recipients, shares.map((s) => BigInt(s))],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function createDistributionProfileAsMaster(
  account: Account,
  ownerAddress: string,
  recipients: string[],
  shares: number[],
): Promise<TxHash> {
  // DynamicRevenueSplitter does not support creating a profile for an arbitrary owner.
  // The profile owner is always `msg.sender`.
  if (ownerAddress.toLowerCase() !== account.address.toLowerCase()) {
    throw new Error(
      "Distribution profile owner is always the transaction sender (msg.sender).",
    );
  }
  return createDistributionProfile(account, recipients, shares);
}

export async function updateDistributionProfile(
  account: Account,
  profileId: bigint,
  recipients: string[],
  shares: number[],
): Promise<TxHash> {
  // In DynamicRevenueSplitter, only ADMIN_ROLE can update profiles.
  const adminRole = await readContract({
    contract: revenueSplitter,
    method: "ADMIN_ROLE",
    params: [],
  });
  const isAdmin = await readContract({
    contract: revenueSplitter,
    method: "hasRole",
    params: [adminRole, account.address],
  });
  if (!isAdmin) {
    throw new Error(
      "Only revenue splitter admins can update distribution profiles",
    );
  }

  const transaction = prepareContractCall({
    contract: revenueSplitter,
    method: "updateDistributionProfile",
    params: [profileId, recipients, shares.map((s) => BigInt(s))],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function deactivateProfile(
  account: Account,
  profileId: bigint,
): Promise<TxHash> {
  const transaction = prepareContractCall({
    contract: revenueSplitter,
    method: "deactivateProfile",
    params: [profileId],
  });
  const { transactionHash } = await sendTransaction({ account, transaction });
  return transactionHash;
}

export async function isProfileOwner(
  profileId: bigint,
  address: string,
): Promise<boolean> {
  return readContract({
    contract: revenueSplitter,
    method: "isProfileOwner",
    params: [profileId, address],
  });
}

Homonyms

soft3/js/src/index.ts
soft3/js/src/queries/index.ts
cyberia/research/events/client/src/pages/index.ts
cyberia/research/events/client/src/entities/order/index.ts
cyberia/research/events/client/src/entities/place/index.ts
cyberia/research/events/client/src/features/shaman-verification/index.ts
cyberia/research/events/client/src/features/staff-form/index.ts
cyberia/research/events/client/src/features/event-details/index.ts
cyberia/research/events/client/src/features/verified-shamans-list/index.ts
cyberia/research/events/client/src/features/home/index.ts
cyberia/research/events/client/src/features/create-edit-event/index.ts
cyberia/research/events/client/src/features/edit-event/index.ts
cyberia/research/events/client/src/features/localprovider-form/index.ts
cyberia/research/events/client/src/entities/user/index.ts
cyberia/research/events/client/src/entities/staff/index.ts
cyberia/research/events/client/src/features/referral/index.ts
cyberia/research/events/client/src/features/request-place/index.ts
cyberia/research/events/client/src/features/login/index.ts
cyberia/research/events/client/src/features/event-form/index.ts
cyberia/research/events/client/src/features/events-list/index.ts
cyberia/research/events/client/src/features/camera/index.ts
cyberia/research/events/client/src/features/localprovider-list/index.ts
cyberia/research/events/client/src/features/place-form/index.ts
cyberia/research/events/client/src/entities/geodata/index.ts
cyberia/research/events/client/src/features/notifications/index.ts
cyberia/research/events/client/src/app/providers/index.ts
cyberia/research/events/client/src/features/socials-form/index.ts
cyberia/research/events/client/src/entities/notification/index.ts
cyberia/research/events/client/src/features/verification-stats/index.ts
cyberia/research/events/client/src/features/staff-list/index.ts
cyberia/research/events/client/src/features/event-attendees/index.ts
cyberia/research/events/client/src/features/places-list/index.ts
cyberia/research/events/client/src/features/time-input/index.ts
cyberia/research/events/client/src/features/map/index.ts
cyberia/research/events/client/src/features/create-edit-place/index.ts
cyberia/research/events/client/src/entities/event/index.ts
cyberia/research/events/client/src/features/purchase/index.ts
cyberia/research/events/client/src/features/display-user/index.ts
cyberia/research/events/client/src/features/manage-event/index.ts
cyberia/research/events/client/src/shared/hooks/index.ts
cyberia/research/events/client/src/shared/api/index.ts
cyberia/research/events/client/src/entities/localprovider/index.ts
cyberia/research/events/client/src/features/ticket/index.ts
cyberia/research/events/client/src/shared/widgets/navigation/index.ts
cyberia/research/events/client/src/app/providers/authProvider/index.ts

Graph