Description
Cloning an existing invoice to create a new one with the same structure can fail silently if the source invoice contains expired trustlines, removed recipients, or assets that are no longer available. This issue adds an InvoiceCloneabilityValidator that runs a comprehensive pre-flight check on a source invoice before cloning, identifying every field that would produce an invalid clone and providing field-level remediation hints.
Technical Context
- New file:
src/preflight/InvoiceCloneabilityValidator.ts; result type: CloneabilityReport { invoiceId: string; cloneable: boolean; fieldReports: FieldReport[] }
FieldReport: { field: string; valid: boolean; reason?: string; suggestedFix?: string }
- Checks performed: (1) all recipient accounts still exist and have required trustlines, (2) the invoice asset is still active on the network, (3) the cloned deadline would be in the future (relative to current ledger time +
minDeadlineBufferMs), (4) no CANCELLED/DISPUTED status that prevents cloning
- Uses
RecipientBalancePreCheck (Issue 45) for recipient checks and SorobanRpc.Server.getAssetStats() for asset validity
SplitClient.cloneInvoice(invoiceId, overrides?) calls the validator and throws InvoiceNotCloneableError listing all failing fields
Acceptance Criteria
Description
Cloning an existing invoice to create a new one with the same structure can fail silently if the source invoice contains expired trustlines, removed recipients, or assets that are no longer available. This issue adds an
InvoiceCloneabilityValidatorthat runs a comprehensive pre-flight check on a source invoice before cloning, identifying every field that would produce an invalid clone and providing field-level remediation hints.Technical Context
src/preflight/InvoiceCloneabilityValidator.ts; result type:CloneabilityReport { invoiceId: string; cloneable: boolean; fieldReports: FieldReport[] }FieldReport:{ field: string; valid: boolean; reason?: string; suggestedFix?: string }minDeadlineBufferMs), (4) noCANCELLED/DISPUTEDstatus that prevents cloningRecipientBalancePreCheck(Issue 45) for recipient checks andSorobanRpc.Server.getAssetStats()for asset validitySplitClient.cloneInvoice(invoiceId, overrides?)calls the validator and throwsInvoiceNotCloneableErrorlisting all failing fieldsAcceptance Criteria
CloneabilityReportwith two failingFieldReportentries{ cloneable: true, fieldReports: [] }andcloneInvoice()proceedsgetLatestLedger()) notDate.now()to avoid local clock skewInvoiceNotCloneableErrorincludes the fullCloneabilityReportin itsdetailsfieldskipValidation: trueoption incloneInvoice()bypasses the validator entirely for advanced users