/** * @file settlement-generator.ts * @notice Settlement file generator combining blockchain and banking data */ import { ethers } from 'ethers'; import { TokenizationWorkflow, SettlementFile } from './tokenization-workflow'; export interface SettlementRequest { besuTxHash: string; fabricTxHash?: string; swiftReference?: string; target2Code?: string; regulatoryData?: Record; identityData?: { did?: string; vcType?: string; claims?: Record; }; } export class SettlementGenerator { private provider: ethers.Provider; private fireflyApiUrl: string; constructor(rpcUrl: string, fireflyApiUrl: string) { this.provider = new ethers.JsonRpcProvider(rpcUrl); this.fireflyApiUrl = fireflyApiUrl; } /** * Generate settlement file from transaction */ async generateSettlementFile(request: SettlementRequest): Promise { // Get blockchain transaction data const tx = await this.provider.getTransaction(request.besuTxHash); if (!tx) { throw new Error('Transaction not found'); } const receipt = await this.provider.getTransactionReceipt(request.besuTxHash); if (!receipt) { throw new Error('Transaction receipt not found'); } // Get token transfer details from event logs const transferEvent = this.parseTransferEvent(receipt.logs); // Generate traditional banking data const traditional = await this.generateTraditionalBankingData(request, tx, receipt); return { blockchain: { hash: tx.hash, from: tx.from, to: tx.to || transferEvent?.to || '', value: tx.value.toString(), gas: tx.gasLimit.toString(), gasPrice: tx.gasPrice?.toString() || '0', nonce: tx.nonce.toString(), blockNumber: receipt.blockNumber.toString(), transactionIndex: receipt.index.toString(), input: tx.data, chainId: tx.chainId?.toString() || '138', usdtErc20: transferEvent ? 'token' : 'native' }, traditional }; } /** * Parse ERC-20 transfer event from logs */ private parseTransferEvent(logs: ethers.Log[]): { to: string; value: string } | null { // ERC-20 Transfer event signature: Transfer(address,address,uint256) const transferTopic = ethers.id('Transfer(address,address,uint256)'); for (const log of logs) { if (log.topics[0] === transferTopic && log.topics.length === 3) { const to = ethers.getAddress('0x' + log.topics[2].slice(26)); const value = BigInt(log.data).toString(); return { to, value }; } } return null; } /** * Generate traditional banking data */ private async generateTraditionalBankingData( request: SettlementRequest, tx: ethers.TransactionResponse, receipt: ethers.TransactionReceipt ): Promise { // Get SWIFT reference (generate if not provided) const swiftReference = request.swiftReference || this.generateSwiftReference(); // Get TARGET2 code (generate if not provided) const target2Code = request.target2Code || this.generateTarget2Code(); // Get identity data from Indy (if provided) let identityCode: string | undefined; let permitCode: string | undefined; let accessCode: string | undefined; if (request.identityData) { identityCode = await this.getIdentityCode(request.identityData); permitCode = await this.getPermitCode(request.identityData); accessCode = await this.getAccessCode(request.identityData); } // Get regulatory flags const regulatoryFlags = request.regulatoryData || {}; return { swiftReference, target2Code, regulatoryFlags, identityCode, permitCode, accessCode }; } /** * Generate SWIFT reference */ private generateSwiftReference(): string { const timestamp = Date.now(); const random = Math.random().toString(36).substr(2, 9).toUpperCase(); return `SWIFT-${timestamp}-${random}`; } /** * Generate TARGET2 code */ private generateTarget2Code(): string { return `T2-${Date.now()}`; } /** * Get identity code from Indy credentials */ private async getIdentityCode(identityData: SettlementRequest['identityData']): Promise { if (!identityData?.did) { return '42Q GB DD GB 42FOP 36F'; // Default format } // In production, query Indy ledger for credential // For now, return formatted DID return identityData.did; } /** * Get permit code from Indy credentials */ private async getPermitCode(identityData: SettlementRequest['identityData']): Promise { if (!identityData?.claims?.permitCode) { return `PERMIT-${Date.now()}`; } return identityData.claims.permitCode; } /** * Get access code from Indy credentials */ private async getAccessCode(identityData: SettlementRequest['identityData']): Promise { if (!identityData?.claims?.accessCode) { return `ACCESS-${Date.now()}`; } return identityData.claims.accessCode; } /** * Generate ISO-20022 message from settlement file */ async generateISO20022Message(settlementFile: SettlementFile): Promise { // Generate pacs.008 (Payment Initiation) message const iso20022 = { Document: { '@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10', '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', 'FIToFICstmrCdtTrf': { 'GrpHdr': { 'MsgId': settlementFile.traditional.swiftReference, 'CreDtTm': new Date().toISOString(), 'NbOfTxs': '1', 'CtrlSum': settlementFile.blockchain.value }, 'CdtTrfTxInf': { 'PmtId': { 'EndToEndId': settlementFile.blockchain.hash, 'TxId': settlementFile.blockchain.hash }, 'PmtTpInf': { 'SvcLvl': { 'Cd': 'SEPA' } }, 'IntrBkSttlmAmt': { '@Ccy': 'EUR', '#text': settlementFile.blockchain.value }, 'ChrgBr': 'DEBT', 'Dbtr': { 'Nm': settlementFile.blockchain.from }, 'DbtrAcct': { 'Id': { 'IBAN': settlementFile.blockchain.from } }, 'Cdtr': { 'Nm': settlementFile.blockchain.to }, 'CdtrAcct': { 'Id': { 'IBAN': settlementFile.blockchain.to } } } } } }; // Convert to XML (simplified - use proper XML library in production) return JSON.stringify(iso20022, null, 2); } /** * Generate SWIFT FIN message (MT103) */ async generateSWIFTMessage(settlementFile: SettlementFile): Promise { // MT103 format const swift = { 'Basic Header': { 'Application Identifier': 'F', 'Service Identifier': '01', 'LT Address': 'DBISGB2LXXXX', 'Session Number': '0000', 'Sequence Number': '000000' }, 'Application Header': { 'Input/Output': 'I', 'Message Type': '103', 'Receiver Address': settlementFile.blockchain.to, 'Message Priority': 'N', 'Delivery Monitoring': '1', 'Obsolescence Period': '003' }, 'User Header': { 'Service Identifier': '103', 'Banking Priority': 'N' }, 'Text': { '20': settlementFile.traditional.swiftReference, '23B': 'CRED', '32A': `${new Date().toISOString().split('T')[0].replace(/-/g, '')}EUR${settlementFile.blockchain.value}`, '50K': settlementFile.blockchain.from, '59': settlementFile.blockchain.to, '71A': 'OUR', '72': `/TOKEN/${settlementFile.blockchain.hash}` } }; return JSON.stringify(swift, null, 2); } }