// ======================================== // USDT Bridge Utilities // ======================================== // Handles wUSDT minting, burning, and reserve management import type { ApiPromise } from '@pezkuwi/api'; import { ASSET_IDS, ASSET_CONFIGS } from './wallet'; import { getMultisigMembers, createMultisigTx } from './multisig'; // ======================================== // CONSTANTS // ======================================== export const WUSDT_ASSET_ID = ASSET_CONFIGS.WUSDT.id; export const WUSDT_DECIMALS = ASSET_CONFIGS.WUSDT.decimals; // Withdrawal limits and timeouts export const WITHDRAWAL_LIMITS = { instant: { maxAmount: 1000, // $1,000 delay: 0, // No delay }, standard: { maxAmount: 10000, // $10,000 delay: 3600, // 1 hour in seconds }, large: { maxAmount: Infinity, delay: 86400, // 24 hours }, }; // ======================================== // ASSET QUERIES // ======================================== /** * Get wUSDT balance for an account * @param api - Polkadot API instance * @param address - Account address * @returns Balance in human-readable format */ export async function getWUSDTBalance(api: ApiPromise, address: string): Promise { try { const balance = await api.query.assets.account(WUSDT_ASSET_ID, address); if (balance.isSome) { const balanceCodec = balance.unwrap() as { toJSON: () => unknown }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const balanceData = balanceCodec.toJSON() as any; return Number(balanceData.balance) / Math.pow(10, WUSDT_DECIMALS); } return 0; } catch (error) { console.error('Error fetching wUSDT balance:', error); return 0; } } /** * Get total wUSDT supply * @param api - Polkadot API instance * @returns Total supply in human-readable format */ export async function getWUSDTTotalSupply(api: ApiPromise): Promise { try { const assetDetails = await api.query.assets.asset(WUSDT_ASSET_ID); if (assetDetails.isSome) { const assetCodec = assetDetails.unwrap() as { toJSON: () => unknown }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const details = assetCodec.toJSON() as any; return Number(details.supply) / Math.pow(10, WUSDT_DECIMALS); } return 0; } catch (error) { console.error('Error fetching wUSDT supply:', error); return 0; } } /** * Get wUSDT asset metadata * @param api - Polkadot API instance * @returns Asset metadata */ export async function getWUSDTMetadata(api: ApiPromise) { try { const metadata = await api.query.assets.metadata(WUSDT_ASSET_ID); return metadata.toJSON(); } catch (error) { console.error('Error fetching wUSDT metadata:', error); return null; } } // ======================================== // MULTISIG OPERATIONS // ======================================== /** * Create multisig transaction to mint wUSDT * @param api - Polkadot API instance * @param beneficiary - Who will receive the wUSDT * @param amount - Amount in human-readable format (e.g., 100.50 USDT) * @param signerAddress - Address of the signer creating this tx * @param specificAddresses - Addresses for non-unique multisig members * @returns Multisig transaction */ export async function createMintWUSDTTx( api: ApiPromise, beneficiary: string, amount: number, signerAddress: string, specificAddresses: Record = {} ) { // Convert to smallest unit const amountBN = BigInt(Math.floor(amount * Math.pow(10, WUSDT_DECIMALS))); // Create the mint call const mintCall = api.tx.assets.mint(WUSDT_ASSET_ID, beneficiary, amountBN.toString()); // Get all multisig members const allMembers = await getMultisigMembers(api, specificAddresses); // Other signatories (excluding current signer) const otherSignatories = allMembers.filter((addr) => addr !== signerAddress); // Create multisig transaction return createMultisigTx(api, mintCall, otherSignatories); } /** * Create multisig transaction to burn wUSDT * @param api - Polkadot API instance * @param from - Who will have their wUSDT burned * @param amount - Amount in human-readable format * @param signerAddress - Address of the signer creating this tx * @param specificAddresses - Addresses for non-unique multisig members * @returns Multisig transaction */ export async function createBurnWUSDTTx( api: ApiPromise, from: string, amount: number, signerAddress: string, specificAddresses: Record = {} ) { const amountBN = BigInt(Math.floor(amount * Math.pow(10, WUSDT_DECIMALS))); const burnCall = api.tx.assets.burn(WUSDT_ASSET_ID, from, amountBN.toString()); const allMembers = await getMultisigMembers(api, specificAddresses); const otherSignatories = allMembers.filter((addr) => addr !== signerAddress); return createMultisigTx(api, burnCall, otherSignatories); } // ======================================== // WITHDRAWAL HELPERS // ======================================== /** * Calculate withdrawal delay based on amount * @param amount - Withdrawal amount in USDT * @returns Delay in seconds */ export function calculateWithdrawalDelay(amount: number): number { if (amount <= WITHDRAWAL_LIMITS.instant.maxAmount) { return WITHDRAWAL_LIMITS.instant.delay; } else if (amount <= WITHDRAWAL_LIMITS.standard.maxAmount) { return WITHDRAWAL_LIMITS.standard.delay; } else { return WITHDRAWAL_LIMITS.large.delay; } } /** * Get withdrawal tier name * @param amount - Withdrawal amount * @returns Tier name */ export function getWithdrawalTier(amount: number): string { if (amount <= WITHDRAWAL_LIMITS.instant.maxAmount) return 'Instant'; if (amount <= WITHDRAWAL_LIMITS.standard.maxAmount) return 'Standard'; return 'Large'; } /** * Format delay time for display * @param seconds - Delay in seconds * @returns Human-readable format */ export function formatDelay(seconds: number): string { if (seconds === 0) return 'Instant'; if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes`; if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours`; return `${Math.floor(seconds / 86400)} days`; } // ======================================== // RESERVE CHECKING // ======================================== export interface ReserveStatus { wusdtSupply: number; offChainReserve: number; // This would come from off-chain oracle/API collateralRatio: number; isHealthy: boolean; } /** * Check reserve health * @param api - Polkadot API instance * @param offChainReserve - Off-chain USDT reserve amount (from treasury) * @returns Reserve status */ export async function checkReserveHealth( api: ApiPromise, offChainReserve: number ): Promise { const wusdtSupply = await getWUSDTTotalSupply(api); const collateralRatio = wusdtSupply > 0 ? (offChainReserve / wusdtSupply) * 100 : 0; return { wusdtSupply, offChainReserve, collateralRatio, isHealthy: collateralRatio >= 100, // At least 100% backed }; } // ======================================== // EVENT MONITORING // ======================================== /** * Subscribe to wUSDT mint events * @param api - Polkadot API instance * @param callback - Callback function for each mint event */ export function subscribeToMintEvents( api: ApiPromise, callback: (beneficiary: string, amount: number, txHash: string) => void ) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return api.query.system.events((events: any[]) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any events.forEach((record: any) => { const { event } = record; if (api.events.assets.Issued.is(event)) { const [assetId, beneficiary, amount] = event.data; if (assetId.toNumber() === WUSDT_ASSET_ID) { const amountNum = Number(amount.toString()) / Math.pow(10, WUSDT_DECIMALS); callback(beneficiary.toString(), amountNum, record.hash.toHex()); } } }); }); } /** * Subscribe to wUSDT burn events * @param api - Polkadot API instance * @param callback - Callback function for each burn event */ export function subscribeToBurnEvents( api: ApiPromise, callback: (account: string, amount: number, txHash: string) => void ) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return api.query.system.events((events: any[]) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any events.forEach((record: any) => { const { event } = record; if (api.events.assets.Burned.is(event)) { const [assetId, account, amount] = event.data; if (assetId.toNumber() === WUSDT_ASSET_ID) { const amountNum = Number(amount.toString()) / Math.pow(10, WUSDT_DECIMALS); callback(account.toString(), amountNum, record.hash.toHex()); } } }); }); } // ======================================== // DISPLAY HELPERS // ======================================== /** * Format wUSDT amount for display * @param amount - Amount in smallest unit or human-readable * @param fromSmallestUnit - Whether input is in smallest unit * @returns Formatted string */ export function formatWUSDT(amount: number | string, fromSmallestUnit = false): string { const value = typeof amount === 'string' ? parseFloat(amount) : amount; if (fromSmallestUnit) { return (value / Math.pow(10, WUSDT_DECIMALS)).toFixed(2); } return value.toFixed(2); } /** * Parse human-readable USDT to smallest unit * @param amount - Human-readable amount * @returns Amount in smallest unit (BigInt) */ export function parseWUSDT(amount: number | string): bigint { const value = typeof amount === 'string' ? parseFloat(amount) : amount; return BigInt(Math.floor(value * Math.pow(10, WUSDT_DECIMALS))); }