mirror of
https://github.com/pezkuwichain/pezkuwi-telegram-miniapp.git
synced 2026-04-29 21:58:01 +00:00
b67809481e
- Get block number from finalized block hash in frontend - Pass as hint to verify-deposit-telegram to avoid 100-block scan - Improve error message extraction in callEdgeFunction
414 lines
11 KiB
TypeScript
414 lines
11 KiB
TypeScript
import { supabase } from './supabase';
|
|
|
|
// ─── Types ───────────────────────────────────────────────────
|
|
|
|
export interface InternalBalance {
|
|
token: string;
|
|
available_balance: number;
|
|
locked_balance: number;
|
|
total: number;
|
|
}
|
|
|
|
export interface PaymentMethod {
|
|
id: string;
|
|
currency: string;
|
|
country: string;
|
|
method_name: string;
|
|
method_type: string;
|
|
logo_url: string | null;
|
|
fields: Record<string, unknown>;
|
|
min_trade_amount: number;
|
|
max_trade_amount: number | null;
|
|
processing_time_minutes: number;
|
|
}
|
|
|
|
export interface P2POffer {
|
|
id: string;
|
|
seller_id: string;
|
|
token: string;
|
|
amount_crypto: number;
|
|
fiat_currency: string;
|
|
fiat_amount: number;
|
|
price_per_unit: number;
|
|
payment_method_id: string;
|
|
payment_method_name?: string;
|
|
min_order_amount: number | null;
|
|
max_order_amount: number | null;
|
|
time_limit_minutes: number;
|
|
status: string;
|
|
remaining_amount: number;
|
|
ad_type: 'buy' | 'sell';
|
|
created_at: string;
|
|
expires_at: string;
|
|
seller_reputation?: SellerReputation | null;
|
|
}
|
|
|
|
export interface SellerReputation {
|
|
completed_trades: number;
|
|
total_trades: number;
|
|
reputation_score: number;
|
|
trust_level: string;
|
|
avg_confirmation_time_minutes: number | null;
|
|
}
|
|
|
|
export interface P2PTrade {
|
|
id: string;
|
|
offer_id: string;
|
|
seller_id: string;
|
|
buyer_id: string;
|
|
buyer_wallet: string;
|
|
crypto_amount: number;
|
|
fiat_amount: number;
|
|
price_per_unit: number;
|
|
escrow_locked_amount: number;
|
|
status: string;
|
|
payment_deadline: string;
|
|
confirmation_deadline: string | null;
|
|
buyer_marked_paid_at: string | null;
|
|
seller_confirmed_at: string | null;
|
|
completed_at: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
// Joined from offer
|
|
token?: string;
|
|
fiat_currency?: string;
|
|
ad_type?: string;
|
|
payment_method_name?: string;
|
|
}
|
|
|
|
export interface P2PMessage {
|
|
id: string;
|
|
trade_id: string;
|
|
sender_id: string;
|
|
message: string;
|
|
message_type: string;
|
|
is_read: boolean;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface P2PDispute {
|
|
id: string;
|
|
trade_id: string;
|
|
opened_by: string;
|
|
reason: string;
|
|
category: string;
|
|
status: string;
|
|
created_at: string;
|
|
}
|
|
|
|
// ─── API Helper ──────────────────────────────────────────────
|
|
|
|
async function callEdgeFunction<T>(
|
|
functionName: string,
|
|
body: Record<string, unknown>
|
|
): Promise<T> {
|
|
const { data, error } = await supabase.functions.invoke(functionName, { body });
|
|
|
|
if (error) {
|
|
let msg = error.message || 'Unknown error';
|
|
// Try to extract the actual error from response body
|
|
const body =
|
|
error.context?.body ??
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
(error as any)?.body ??
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
(error as any)?.responseBody;
|
|
if (body) {
|
|
try {
|
|
const parsed = typeof body === 'string' ? JSON.parse(body) : body;
|
|
if (parsed.error) msg = parsed.error;
|
|
} catch {
|
|
if (typeof body === 'string' && body.length < 500) msg = body;
|
|
}
|
|
}
|
|
throw new Error(msg);
|
|
}
|
|
|
|
if (data?.error) {
|
|
throw new Error(data.error);
|
|
}
|
|
|
|
return data as T;
|
|
}
|
|
|
|
// ─── Balance ─────────────────────────────────────────────────
|
|
|
|
export async function getInternalBalance(sessionToken: string): Promise<InternalBalance[]> {
|
|
const result = await callEdgeFunction<{ success: boolean; balances: InternalBalance[] }>(
|
|
'get-internal-balance',
|
|
{ sessionToken }
|
|
);
|
|
return result.balances || [];
|
|
}
|
|
|
|
// ─── Deposit / Withdraw ─────────────────────────────────────
|
|
|
|
export interface VerifyDepositResult {
|
|
success: boolean;
|
|
amount: number;
|
|
token: string;
|
|
newBalance: number;
|
|
txHash: string;
|
|
}
|
|
|
|
export interface RequestWithdrawResult {
|
|
success: boolean;
|
|
requestId: string;
|
|
amount: number;
|
|
fee: number;
|
|
netAmount: number;
|
|
token: string;
|
|
status: string;
|
|
}
|
|
|
|
export async function verifyDeposit(
|
|
sessionToken: string,
|
|
txHash: string,
|
|
token: 'HEZ' | 'PEZ',
|
|
expectedAmount: number,
|
|
blockNumber?: number
|
|
): Promise<VerifyDepositResult> {
|
|
return callEdgeFunction<VerifyDepositResult>('verify-deposit-telegram', {
|
|
sessionToken,
|
|
txHash,
|
|
token,
|
|
expectedAmount,
|
|
blockNumber,
|
|
});
|
|
}
|
|
|
|
export async function requestWithdraw(
|
|
sessionToken: string,
|
|
token: 'HEZ' | 'PEZ',
|
|
amount: number,
|
|
walletAddress: string
|
|
): Promise<RequestWithdrawResult> {
|
|
return callEdgeFunction<RequestWithdrawResult>('request-withdraw-telegram', {
|
|
sessionToken,
|
|
token,
|
|
amount,
|
|
walletAddress,
|
|
});
|
|
}
|
|
|
|
// ─── Payment Methods ─────────────────────────────────────────
|
|
|
|
export async function getPaymentMethods(
|
|
sessionToken: string,
|
|
currency?: string
|
|
): Promise<PaymentMethod[]> {
|
|
const result = await callEdgeFunction<{ success: boolean; methods: PaymentMethod[] }>(
|
|
'get-payment-methods',
|
|
{ sessionToken, currency }
|
|
);
|
|
return result.methods || [];
|
|
}
|
|
|
|
// ─── Offers ──────────────────────────────────────────────────
|
|
|
|
interface GetOffersParams {
|
|
sessionToken: string;
|
|
adType?: 'buy' | 'sell';
|
|
token?: string;
|
|
fiatCurrency?: string;
|
|
page?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
interface GetOffersResult {
|
|
offers: P2POffer[];
|
|
total: number;
|
|
page: number;
|
|
limit: number;
|
|
}
|
|
|
|
export async function getP2POffers(params: GetOffersParams): Promise<GetOffersResult> {
|
|
const result = await callEdgeFunction<{ success: boolean } & GetOffersResult>(
|
|
'get-p2p-offers',
|
|
params as unknown as Record<string, unknown>
|
|
);
|
|
return {
|
|
offers: result.offers || [],
|
|
total: result.total || 0,
|
|
page: result.page || 1,
|
|
limit: result.limit || 20,
|
|
};
|
|
}
|
|
|
|
export async function getMyOffers(sessionToken: string): Promise<P2POffer[]> {
|
|
const result = await callEdgeFunction<{ success: boolean; offers: P2POffer[] }>('get-my-offers', {
|
|
sessionToken,
|
|
});
|
|
return result.offers || [];
|
|
}
|
|
|
|
// ─── Accept Offer ────────────────────────────────────────────
|
|
|
|
interface AcceptOfferParams {
|
|
sessionToken: string;
|
|
offerId: string;
|
|
amount: number;
|
|
buyerWallet: string;
|
|
}
|
|
|
|
export async function acceptP2POffer(
|
|
params: AcceptOfferParams
|
|
): Promise<{ tradeId: string; trade: P2PTrade }> {
|
|
return callEdgeFunction<{ success: boolean; tradeId: string; trade: P2PTrade }>(
|
|
'accept-p2p-offer',
|
|
params as unknown as Record<string, unknown>
|
|
);
|
|
}
|
|
|
|
// ─── Create Offer ────────────────────────────────────────────
|
|
|
|
interface CreateOfferParams {
|
|
sessionToken: string;
|
|
token: 'HEZ' | 'PEZ';
|
|
amountCrypto: number;
|
|
fiatCurrency: string;
|
|
fiatAmount: number;
|
|
paymentMethodId: string;
|
|
paymentDetailsEncrypted: string;
|
|
minOrderAmount?: number;
|
|
maxOrderAmount?: number;
|
|
timeLimitMinutes?: number;
|
|
adType?: 'buy' | 'sell';
|
|
}
|
|
|
|
export async function createP2POffer(
|
|
params: CreateOfferParams
|
|
): Promise<{ offerId: string; offer: P2POffer }> {
|
|
const result = await callEdgeFunction<{ success: boolean; offer_id: string; offer: P2POffer }>(
|
|
'create-offer-telegram',
|
|
params as unknown as Record<string, unknown>
|
|
);
|
|
return { offerId: result.offer_id, offer: result.offer };
|
|
}
|
|
|
|
// ─── Trades ──────────────────────────────────────────────────
|
|
|
|
export async function getP2PTrades(
|
|
sessionToken: string,
|
|
status?: 'active' | 'completed' | 'all'
|
|
): Promise<P2PTrade[]> {
|
|
const result = await callEdgeFunction<{ success: boolean; trades: P2PTrade[] }>(
|
|
'get-p2p-trades',
|
|
{ sessionToken, status }
|
|
);
|
|
return result.trades || [];
|
|
}
|
|
|
|
// ─── Trade Actions ───────────────────────────────────────────
|
|
|
|
export async function markTradePaid(sessionToken: string, tradeId: string): Promise<P2PTrade> {
|
|
const result = await callEdgeFunction<{ success: boolean; trade: P2PTrade }>('trade-action', {
|
|
sessionToken,
|
|
tradeId,
|
|
action: 'mark_paid',
|
|
});
|
|
return result.trade;
|
|
}
|
|
|
|
export async function confirmTradePayment(
|
|
sessionToken: string,
|
|
tradeId: string
|
|
): Promise<P2PTrade> {
|
|
const result = await callEdgeFunction<{ success: boolean; trade: P2PTrade }>('trade-action', {
|
|
sessionToken,
|
|
tradeId,
|
|
action: 'confirm',
|
|
});
|
|
return result.trade;
|
|
}
|
|
|
|
export async function cancelTrade(
|
|
sessionToken: string,
|
|
tradeId: string,
|
|
reason?: string
|
|
): Promise<P2PTrade> {
|
|
const result = await callEdgeFunction<{ success: boolean; trade: P2PTrade }>('trade-action', {
|
|
sessionToken,
|
|
tradeId,
|
|
action: 'cancel',
|
|
payload: { reason },
|
|
});
|
|
return result.trade;
|
|
}
|
|
|
|
export async function rateTrade(
|
|
sessionToken: string,
|
|
tradeId: string,
|
|
rating: number,
|
|
review?: string
|
|
): Promise<void> {
|
|
await callEdgeFunction<{ success: boolean }>('trade-action', {
|
|
sessionToken,
|
|
tradeId,
|
|
action: 'rate',
|
|
payload: { rating, review },
|
|
});
|
|
}
|
|
|
|
// ─── Messages ────────────────────────────────────────────────
|
|
|
|
export async function sendTradeMessage(
|
|
sessionToken: string,
|
|
tradeId: string,
|
|
message: string
|
|
): Promise<string> {
|
|
const result = await callEdgeFunction<{ success: boolean; messageId: string }>('p2p-messages', {
|
|
sessionToken,
|
|
action: 'send',
|
|
tradeId,
|
|
message,
|
|
});
|
|
return result.messageId;
|
|
}
|
|
|
|
export async function getTradeMessages(
|
|
sessionToken: string,
|
|
tradeId: string
|
|
): Promise<P2PMessage[]> {
|
|
const result = await callEdgeFunction<{ success: boolean; messages: P2PMessage[] }>(
|
|
'p2p-messages',
|
|
{ sessionToken, action: 'list', tradeId }
|
|
);
|
|
return result.messages || [];
|
|
}
|
|
|
|
// ─── Disputes ────────────────────────────────────────────────
|
|
|
|
export async function openDispute(
|
|
sessionToken: string,
|
|
tradeId: string,
|
|
reason: string,
|
|
category: string
|
|
): Promise<P2PDispute> {
|
|
const result = await callEdgeFunction<{ success: boolean; dispute: P2PDispute }>('p2p-dispute', {
|
|
sessionToken,
|
|
action: 'open',
|
|
tradeId,
|
|
reason,
|
|
category,
|
|
});
|
|
return result.dispute;
|
|
}
|
|
|
|
export async function addDisputeEvidence(
|
|
sessionToken: string,
|
|
tradeId: string,
|
|
evidenceUrl: string,
|
|
evidenceType: string,
|
|
description?: string
|
|
): Promise<void> {
|
|
await callEdgeFunction<{ success: boolean }>('p2p-dispute', {
|
|
sessionToken,
|
|
action: 'add_evidence',
|
|
tradeId,
|
|
evidenceUrl,
|
|
evidenceType,
|
|
description,
|
|
});
|
|
}
|