/** * @file workflow-engine.ts * @notice FireFly workflow engine for bridge transfer orchestration */ import { ethers } from 'ethers'; import { BridgeEscrowVault, BridgeRegistry } from '../../contracts/bridge/interop'; export enum TransferStatus { INITIATED = 'INITIATED', DEPOSIT_CONFIRMED = 'DEPOSIT_CONFIRMED', ROUTE_SELECTED = 'ROUTE_SELECTED', EXECUTING = 'EXECUTING', DESTINATION_SENT = 'DESTINATION_SENT', FINALITY_CONFIRMED = 'FINALITY_CONFIRMED', COMPLETED = 'COMPLETED', FAILED = 'FAILED', REFUND_PENDING = 'REFUND_PENDING', REFUNDED = 'REFUNDED' } export enum DestinationType { EVM = 0, XRPL = 1, FABRIC = 2 } export interface Transfer { transferId: string; depositor: string; asset: string; amount: string; destinationType: DestinationType; destinationData: string; timestamp: number; timeout: number; status: TransferStatus; refunded: boolean; route?: RouteInfo; executionData?: ExecutionData; } export interface RouteInfo { chainId: number; chainName: string; provider: string; estimatedTime: number; fee: string; healthScore: number; } export interface ExecutionData { txHash?: string; blockNumber?: number; finalityBlocks?: number; xrplTxHash?: string; fabricTxId?: string; error?: string; } export class WorkflowEngine { private provider: ethers.Provider; private escrowVault: ethers.Contract; private registry: ethers.Contract; private stateMachine: Map; constructor( rpcUrl: string, escrowVaultAddress: string, registryAddress: string, escrowAbi: any[], registryAbi: any[] ) { this.provider = new ethers.JsonRpcProvider(rpcUrl); this.escrowVault = new ethers.Contract(escrowVaultAddress, escrowAbi, this.provider); this.registry = new ethers.Contract(registryAddress, registryAbi, this.provider); this.stateMachine = new Map(); } /** * Initialize a new transfer workflow */ async initiateTransfer( transferId: string, depositor: string, asset: string, amount: string, destinationType: DestinationType, destinationData: string, timeout: number ): Promise { const transfer: Transfer = { transferId, depositor, asset, amount, destinationType, destinationData, timestamp: Date.now(), timeout, status: TransferStatus.INITIATED, refunded: false }; this.stateMachine.set(transferId, TransferStatus.INITIATED); return transfer; } /** * Confirm deposit on-chain */ async confirmDeposit(transferId: string): Promise { const currentStatus = this.stateMachine.get(transferId); if (currentStatus !== TransferStatus.INITIATED) { throw new Error(`Invalid status transition from ${currentStatus}`); } // Verify deposit on-chain const transfer = await this.escrowVault.getTransfer(transferId); if (!transfer || transfer.status !== 0) { // 0 = INITIATED throw new Error('Deposit not found or invalid status'); } this.stateMachine.set(transferId, TransferStatus.DEPOSIT_CONFIRMED); } /** * Select route for transfer */ async selectRoute(transferId: string, route: RouteInfo): Promise { const currentStatus = this.stateMachine.get(transferId); if (currentStatus !== TransferStatus.DEPOSIT_CONFIRMED) { throw new Error(`Invalid status transition from ${currentStatus}`); } // Validate route with registry const isValid = await this.registry.validateBridgeRequest( route.chainId === 0 ? ethers.ZeroAddress : route.chainId, route.chainId, route.chainId ); if (!isValid) { throw new Error('Invalid route'); } this.stateMachine.set(transferId, TransferStatus.ROUTE_SELECTED); } /** * Execute transfer */ async executeTransfer(transferId: string, executionData: ExecutionData): Promise { const currentStatus = this.stateMachine.get(transferId); if (currentStatus !== TransferStatus.ROUTE_SELECTED) { throw new Error(`Invalid status transition from ${currentStatus}`); } this.stateMachine.set(transferId, TransferStatus.EXECUTING); // Execution logic handled by connector layer } /** * Mark destination as sent */ async markDestinationSent(transferId: string, executionData: ExecutionData): Promise { const currentStatus = this.stateMachine.get(transferId); if (currentStatus !== TransferStatus.EXECUTING) { throw new Error(`Invalid status transition from ${currentStatus}`); } this.stateMachine.set(transferId, TransferStatus.DESTINATION_SENT); } /** * Confirm finality */ async confirmFinality(transferId: string): Promise { const currentStatus = this.stateMachine.get(transferId); if (currentStatus !== TransferStatus.DESTINATION_SENT) { throw new Error(`Invalid status transition from ${currentStatus}`); } this.stateMachine.set(transferId, TransferStatus.FINALITY_CONFIRMED); } /** * Complete transfer */ async completeTransfer(transferId: string): Promise { const currentStatus = this.stateMachine.get(transferId); if (currentStatus !== TransferStatus.FINALITY_CONFIRMED) { throw new Error(`Invalid status transition from ${currentStatus}`); } this.stateMachine.set(transferId, TransferStatus.COMPLETED); } /** * Mark transfer as failed */ async failTransfer(transferId: string, error: string): Promise { this.stateMachine.set(transferId, TransferStatus.FAILED); } /** * Initiate refund */ async initiateRefund(transferId: string): Promise { const currentStatus = this.stateMachine.get(transferId); if (currentStatus === TransferStatus.FAILED || currentStatus === TransferStatus.INITIATED || currentStatus === TransferStatus.DEPOSIT_CONFIRMED) { this.stateMachine.set(transferId, TransferStatus.REFUND_PENDING); } else { throw new Error(`Cannot refund from status ${currentStatus}`); } } /** * Execute refund */ async executeRefund(transferId: string): Promise { const currentStatus = this.stateMachine.get(transferId); if (currentStatus !== TransferStatus.REFUND_PENDING) { throw new Error(`Invalid status transition from ${currentStatus}`); } this.stateMachine.set(transferId, TransferStatus.REFUNDED); } /** * Get current status */ getStatus(transferId: string): TransferStatus | undefined { return this.stateMachine.get(transferId); } /** * Check if transfer is refundable */ async isRefundable(transferId: string): Promise { return await this.escrowVault.isRefundable(transferId); } }