import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Shield, RefreshCw, CheckCircle2, XCircle, AlertCircle, Loader2 } from 'lucide-react'; import { Card } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { getSigner } from '@/lib/get-signer'; import { usePezkuwi } from '@/contexts/PezkuwiContext'; import { getMultisigMembers, calculateMultisigAddress, approveMultisigTx, cancelMultisigTx, USDT_MULTISIG_CONFIG, BRIDGE_MULTISIG_SPECIFIC_ADDRESSES, } from '@pezkuwi/lib/multisig'; import { listPendingOperations, decodeAndVerifyCallData, type PendingOperation, } from '@pezkuwi/lib/multisig-operations'; interface MultisigOperationsProps { specificAddresses?: Record; } export const MultisigOperations: React.FC = ({ specificAddresses = BRIDGE_MULTISIG_SPECIFIC_ADDRESSES, }) => { const { t } = useTranslation(); const { api, isApiReady, selectedAccount, walletSource } = usePezkuwi(); const [multisigAddress, setMultisigAddress] = useState(''); const [otherSignatories, setOtherSignatories] = useState([]); const [operations, setOperations] = useState([]); const [loading, setLoading] = useState(true); const [processingHash, setProcessingHash] = useState(null); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const [manualCallData, setManualCallData] = useState>({}); const [decodeErrors, setDecodeErrors] = useState>({}); const load = useCallback(async () => { if (!api || !isApiReady) return; setLoading(true); setError(null); try { const allMembers = await getMultisigMembers(api, specificAddresses); const multisigAddr = calculateMultisigAddress(allMembers); setMultisigAddress(multisigAddr); if (selectedAccount) { setOtherSignatories(allMembers.filter((addr) => addr !== selectedAccount.address)); } const ops = await listPendingOperations(api, multisigAddr); setOperations(ops); } catch (err) { if (import.meta.env.DEV) console.error('Error loading pending multisig operations:', err); setError(err instanceof Error ? err.message : 'Failed to load pending operations'); } finally { setLoading(false); } }, [api, isApiReady, selectedAccount, specificAddresses]); useEffect(() => { load(); }, [load]); const handleDecode = (callHash: string) => { if (!api) return; const hex = manualCallData[callHash]; if (!hex) return; try { const { call, description } = decodeAndVerifyCallData(api, hex, callHash); setOperations((prev) => prev.map((op) => op.callHash === callHash ? { ...op, resolvedCall: call, description, isAutoMatched: false } : op ) ); setDecodeErrors((prev) => ({ ...prev, [callHash]: '' })); } catch (err) { setDecodeErrors((prev) => ({ ...prev, [callHash]: err instanceof Error ? err.message : 'Failed to decode call data', })); } }; const runExtrinsic = async ( callHash: string, tx: ReturnType | ReturnType, successMessage: string ) => { if (!api || !selectedAccount) return; setProcessingHash(callHash); setError(null); setSuccess(null); try { const injector = await getSigner(selectedAccount.address, walletSource, api); await new Promise((resolve, reject) => { tx.signAndSend(selectedAccount.address, { signer: injector.signer }, ({ status, dispatchError }) => { if (status.isInBlock || status.isFinalized) { if (dispatchError) { if (dispatchError.isModule) { const decoded = api.registry.findMetaError(dispatchError.asModule); reject(new Error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`)); } else { reject(new Error(dispatchError.toString())); } } else { resolve(); } } }).catch(reject); }); setSuccess(successMessage); await load(); } catch (err) { if (import.meta.env.DEV) console.error('Multisig operation submission failed:', err); setError(err instanceof Error ? err.message : 'Transaction failed'); } finally { setProcessingHash(null); } }; const handleApprove = (op: PendingOperation) => { if (!api || !op.resolvedCall) return; const tx = approveMultisigTx(api, op.resolvedCall, otherSignatories, op.when, op.threshold); runExtrinsic(op.callHash, tx, 'Approval submitted successfully.'); }; const handleReject = (op: PendingOperation) => { if (!api) return; const tx = cancelMultisigTx(api, op.callHash, otherSignatories, op.when, op.threshold); runExtrinsic(op.callHash, tx, 'Operation cancelled successfully.'); }; if (loading) { return (
); } return (

{t('multisigOps.title')}

{t('multisigOps.subtitle', { count: operations.length })}

{error && ( {error} )} {success && ( {success} )} {operations.length === 0 ? (
{t('multisigOps.empty')}
) : (
{operations.map((op) => { const alreadyApproved = selectedAccount ? op.approvals.includes(selectedAccount.address) : false; const canReject = selectedAccount ? op.depositor === selectedAccount.address : false; const isProcessing = processingHash === op.callHash; return (

{op.description}

{op.callHash.slice(0, 10)}...{op.callHash.slice(-8)}
= op.threshold ? 'default' : 'outline'} className="whitespace-nowrap" > {op.approvalsCount}/{op.threshold} {t('multisigOps.approvals')}

{t('multisigOps.proposedBy')}:{' '} {op.depositor.slice(0, 8)}...{op.depositor.slice(-6)}

{alreadyApproved && (

{t('multisigOps.youApproved')}

)}
{!op.resolvedCall && (

{t('multisigOps.unknownCallHint')}