Consolidate webapp structure by merging nested components into the main repository
This commit is contained in:
159
orchestrator/src/api/plans.ts
Normal file
159
orchestrator/src/api/plans.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import type { Request, Response } from "express";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { createHash } from "crypto";
|
||||
import { validatePlan, checkStepDependencies } from "../services/planValidation";
|
||||
import { storePlan, getPlanById, updatePlanSignature } from "../db/plans";
|
||||
import type { Plan, PlanStep } from "../types/plan";
|
||||
|
||||
/**
|
||||
* POST /api/plans
|
||||
* Create a new execution plan
|
||||
*/
|
||||
export async function createPlan(req: Request, res: Response) {
|
||||
try {
|
||||
const plan: Plan = req.body;
|
||||
|
||||
// Validate plan structure
|
||||
const validation = validatePlan(plan);
|
||||
if (!validation.valid) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid plan",
|
||||
errors: validation.errors,
|
||||
});
|
||||
}
|
||||
|
||||
// Check step dependencies
|
||||
const dependencyCheck = checkStepDependencies(plan.steps);
|
||||
if (!dependencyCheck.valid) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid step dependencies",
|
||||
errors: dependencyCheck.errors,
|
||||
});
|
||||
}
|
||||
|
||||
// Generate plan ID and hash
|
||||
const planId = uuidv4();
|
||||
const planHash = createHash("sha256")
|
||||
.update(JSON.stringify(plan))
|
||||
.digest("hex");
|
||||
|
||||
// Store plan
|
||||
const storedPlan = {
|
||||
...plan,
|
||||
plan_id: planId,
|
||||
plan_hash: planHash,
|
||||
created_at: new Date().toISOString(),
|
||||
status: "pending",
|
||||
};
|
||||
|
||||
await storePlan(storedPlan);
|
||||
|
||||
res.status(201).json({
|
||||
plan_id: planId,
|
||||
plan_hash: planHash,
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: "Failed to create plan",
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/plans/:planId
|
||||
* Get plan details
|
||||
*/
|
||||
export async function getPlan(req: Request, res: Response) {
|
||||
try {
|
||||
const { planId } = req.params;
|
||||
const plan = await getPlanById(planId);
|
||||
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
error: "Plan not found",
|
||||
});
|
||||
}
|
||||
|
||||
res.json(plan);
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: "Failed to get plan",
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/plans/:planId/signature
|
||||
* Add user signature to plan
|
||||
*/
|
||||
export async function addSignature(req: Request, res: Response) {
|
||||
try {
|
||||
const { planId } = req.params;
|
||||
const { signature, messageHash, signerAddress } = req.body;
|
||||
|
||||
if (!signature || !messageHash || !signerAddress) {
|
||||
return res.status(400).json({
|
||||
error: "Missing required fields: signature, messageHash, signerAddress",
|
||||
});
|
||||
}
|
||||
|
||||
const plan = await getPlanById(planId);
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
error: "Plan not found",
|
||||
});
|
||||
}
|
||||
|
||||
// Update plan with signature
|
||||
await updatePlanSignature(planId, {
|
||||
signature,
|
||||
messageHash,
|
||||
signerAddress,
|
||||
signedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
planId,
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: "Failed to add signature",
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/plans/:planId/validate
|
||||
* Validate plan structure and dependencies
|
||||
*/
|
||||
export async function validatePlanEndpoint(req: Request, res: Response) {
|
||||
try {
|
||||
const { planId } = req.params;
|
||||
const plan = await getPlanById(planId);
|
||||
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
error: "Plan not found",
|
||||
});
|
||||
}
|
||||
|
||||
const validation = validatePlan(plan);
|
||||
const dependencyCheck = checkStepDependencies(plan.steps);
|
||||
|
||||
res.json({
|
||||
valid: validation.valid && dependencyCheck.valid,
|
||||
validation: validation,
|
||||
dependencies: dependencyCheck,
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: "Failed to validate plan",
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
45
orchestrator/src/api/sse.ts
Normal file
45
orchestrator/src/api/sse.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { Request, Response } from "express";
|
||||
import { executionCoordinator } from "../services/execution";
|
||||
|
||||
/**
|
||||
* GET /api/plans/:planId/status/stream
|
||||
* Server-Sent Events stream for real-time execution status
|
||||
*/
|
||||
export function streamPlanStatus(req: Request, res: Response) {
|
||||
const { planId } = req.params;
|
||||
|
||||
// Set SSE headers
|
||||
res.setHeader("Content-Type", "text/event-stream");
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
res.setHeader("X-Accel-Buffering", "no"); // Disable nginx buffering
|
||||
|
||||
// Send initial connection message
|
||||
res.write(`data: ${JSON.stringify({ type: "connected", planId })}\n\n`);
|
||||
|
||||
// Listen for status updates
|
||||
const statusHandler = (executionId: string, event: any) => {
|
||||
// Only send events for this plan
|
||||
if (event.planId === planId || executionId.includes(planId)) {
|
||||
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
||||
}
|
||||
};
|
||||
|
||||
executionCoordinator.onStatus(statusHandler);
|
||||
|
||||
// Handle client disconnect
|
||||
req.on("close", () => {
|
||||
executionCoordinator.off("status", statusHandler);
|
||||
res.end();
|
||||
});
|
||||
|
||||
// Keep connection alive with ping
|
||||
const pingInterval = setInterval(() => {
|
||||
res.write(`: ping\n\n`);
|
||||
}, 30000);
|
||||
|
||||
req.on("close", () => {
|
||||
clearInterval(pingInterval);
|
||||
});
|
||||
}
|
||||
|
||||
29
orchestrator/src/db/plans.ts
Normal file
29
orchestrator/src/db/plans.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// In-memory database for plans (mock implementation)
|
||||
// In production, replace with actual database (PostgreSQL, MongoDB, etc.)
|
||||
|
||||
const plans: Map<string, any> = new Map();
|
||||
|
||||
export async function storePlan(plan: any): Promise<void> {
|
||||
plans.set(plan.plan_id, plan);
|
||||
}
|
||||
|
||||
export async function getPlanById(planId: string): Promise<any | null> {
|
||||
return plans.get(planId) || null;
|
||||
}
|
||||
|
||||
export async function updatePlanSignature(planId: string, signature: any): Promise<void> {
|
||||
const plan = plans.get(planId);
|
||||
if (plan) {
|
||||
plan.signature = signature;
|
||||
plans.set(planId, plan);
|
||||
}
|
||||
}
|
||||
|
||||
export async function updatePlanStatus(planId: string, status: string): Promise<void> {
|
||||
const plan = plans.get(planId);
|
||||
if (plan) {
|
||||
plan.status = status;
|
||||
plans.set(planId, plan);
|
||||
}
|
||||
}
|
||||
|
||||
127
orchestrator/src/integrations/bank/index.ts
Normal file
127
orchestrator/src/integrations/bank/index.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Bank Connector Integration
|
||||
* Supports multiple banking rails: SWIFT, SEPA, FedNow, ISO-20022
|
||||
*/
|
||||
|
||||
export interface BankConnector {
|
||||
name: string;
|
||||
type: "SWIFT" | "SEPA" | "FEDNOW" | "ISO20022";
|
||||
sendMessage(message: string): Promise<{ success: boolean; messageId?: string; error?: string }>;
|
||||
getStatus(messageId: string): Promise<{ status: string; details?: any }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* SWIFT Connector
|
||||
*/
|
||||
export class SwiftConnector implements BankConnector {
|
||||
name = "SWIFT";
|
||||
type: "SWIFT" = "SWIFT";
|
||||
|
||||
async sendMessage(message: string): Promise<{ success: boolean; messageId?: string; error?: string }> {
|
||||
// Mock implementation
|
||||
// In production, this would integrate with SWIFT API
|
||||
console.log("[SWIFT] Sending message:", message);
|
||||
return {
|
||||
success: true,
|
||||
messageId: `SWIFT-${Date.now()}`,
|
||||
};
|
||||
}
|
||||
|
||||
async getStatus(messageId: string): Promise<{ status: string; details?: any }> {
|
||||
// Mock implementation
|
||||
return {
|
||||
status: "ACCEPTED",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SEPA Connector
|
||||
*/
|
||||
export class SepaConnector implements BankConnector {
|
||||
name = "SEPA";
|
||||
type: "SEPA" = "SEPA";
|
||||
|
||||
async sendMessage(message: string): Promise<{ success: boolean; messageId?: string; error?: string }> {
|
||||
// Mock implementation
|
||||
// In production, this would integrate with SEPA API
|
||||
console.log("[SEPA] Sending message:", message);
|
||||
return {
|
||||
success: true,
|
||||
messageId: `SEPA-${Date.now()}`,
|
||||
};
|
||||
}
|
||||
|
||||
async getStatus(messageId: string): Promise<{ status: string; details?: any }> {
|
||||
return {
|
||||
status: "ACCEPTED",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FedNow Connector
|
||||
*/
|
||||
export class FedNowConnector implements BankConnector {
|
||||
name = "FedNow";
|
||||
type: "FEDNOW" = "FEDNOW";
|
||||
|
||||
async sendMessage(message: string): Promise<{ success: boolean; messageId?: string; error?: string }> {
|
||||
// Mock implementation
|
||||
// In production, this would integrate with FedNow API
|
||||
console.log("[FedNow] Sending message:", message);
|
||||
return {
|
||||
success: true,
|
||||
messageId: `FEDNOW-${Date.now()}`,
|
||||
};
|
||||
}
|
||||
|
||||
async getStatus(messageId: string): Promise<{ status: string; details?: any }> {
|
||||
return {
|
||||
status: "ACCEPTED",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ISO-20022 Generic Connector
|
||||
*/
|
||||
export class Iso20022Connector implements BankConnector {
|
||||
name = "ISO-20022";
|
||||
type: "ISO20022" = "ISO20022";
|
||||
|
||||
async sendMessage(message: string): Promise<{ success: boolean; messageId?: string; error?: string }> {
|
||||
// Mock implementation
|
||||
// In production, this would parse ISO-20022 message and route to appropriate bank
|
||||
console.log("[ISO-20022] Sending message:", message);
|
||||
return {
|
||||
success: true,
|
||||
messageId: `ISO-${Date.now()}`,
|
||||
};
|
||||
}
|
||||
|
||||
async getStatus(messageId: string): Promise<{ status: string; details?: any }> {
|
||||
return {
|
||||
status: "ACCEPTED",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connector for a specific rail type
|
||||
*/
|
||||
export function getConnector(type: "SWIFT" | "SEPA" | "FEDNOW" | "ISO20022"): BankConnector {
|
||||
switch (type) {
|
||||
case "SWIFT":
|
||||
return new SwiftConnector();
|
||||
case "SEPA":
|
||||
return new SepaConnector();
|
||||
case "FEDNOW":
|
||||
return new FedNowConnector();
|
||||
case "ISO20022":
|
||||
return new Iso20022Connector();
|
||||
default:
|
||||
return new Iso20022Connector();
|
||||
}
|
||||
}
|
||||
|
||||
80
orchestrator/src/integrations/compliance/index.ts
Normal file
80
orchestrator/src/integrations/compliance/index.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Compliance Provider Integration
|
||||
* Supports KYC/AML providers: Onfido, Chainalysis, Entra Verified ID
|
||||
*/
|
||||
|
||||
export interface KYCResult {
|
||||
level: number;
|
||||
verified: boolean;
|
||||
expiresAt?: string;
|
||||
}
|
||||
|
||||
export interface AMLResult {
|
||||
passed: boolean;
|
||||
lastCheck?: string;
|
||||
riskLevel?: string;
|
||||
}
|
||||
|
||||
export interface IdentityData {
|
||||
lei?: string;
|
||||
did?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KYC status with Onfido
|
||||
*/
|
||||
export async function checkKYC(userId: string): Promise<KYCResult | null> {
|
||||
// Mock implementation
|
||||
// In production, this would:
|
||||
// 1. Call Onfido API to check KYC status
|
||||
// 2. Parse response and return structured data
|
||||
|
||||
console.log(`[Onfido] Checking KYC for user ${userId}`);
|
||||
|
||||
// Mock: return verified KYC
|
||||
return {
|
||||
level: 2,
|
||||
verified: true,
|
||||
expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check AML status with Chainalysis
|
||||
*/
|
||||
export async function checkAML(userId: string): Promise<AMLResult | null> {
|
||||
// Mock implementation
|
||||
// In production, this would:
|
||||
// 1. Call Chainalysis API to check AML status
|
||||
// 2. Perform sanctions screening
|
||||
// 3. Return risk assessment
|
||||
|
||||
console.log(`[Chainalysis] Checking AML for user ${userId}`);
|
||||
|
||||
// Mock: return passed AML
|
||||
return {
|
||||
passed: true,
|
||||
lastCheck: new Date().toISOString(),
|
||||
riskLevel: "LOW",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identity data (LEI, DID) from Entra Verified ID
|
||||
*/
|
||||
export async function getIdentityData(userId: string): Promise<IdentityData | null> {
|
||||
// Mock implementation
|
||||
// In production, this would:
|
||||
// 1. Call Entra Verified ID API
|
||||
// 2. Retrieve LEI and DID credentials
|
||||
// 3. Verify credentials
|
||||
|
||||
console.log(`[Entra] Getting identity for user ${userId}`);
|
||||
|
||||
// Mock: return identity data
|
||||
return {
|
||||
lei: "1234567890ABCDEF123456",
|
||||
did: "did:web:example.com:user:" + userId,
|
||||
};
|
||||
}
|
||||
|
||||
72
orchestrator/src/services/bank.ts
Normal file
72
orchestrator/src/services/bank.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { Plan } from "../types/plan";
|
||||
import { generatePacs008 } from "./iso20022";
|
||||
|
||||
/**
|
||||
* Prepare bank instruction (2PC prepare phase)
|
||||
* Sends provisional ISO-20022 message
|
||||
*/
|
||||
export async function prepareBankInstruction(plan: Plan): Promise<boolean> {
|
||||
console.log(`[Bank] Preparing instruction for plan ${plan.plan_id}`);
|
||||
|
||||
// Mock: In real implementation, this would:
|
||||
// 1. Generate provisional ISO-20022 message (pacs.008 with conditional settlement)
|
||||
// 2. Send to bank connector
|
||||
// 3. Receive provisional acceptance
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit bank instruction (2PC commit phase)
|
||||
* Confirms final settlement
|
||||
*/
|
||||
export async function commitBankInstruction(plan: Plan): Promise<{
|
||||
success: boolean;
|
||||
isoMessageId?: string;
|
||||
error?: string;
|
||||
}> {
|
||||
console.log(`[Bank] Committing instruction for plan ${plan.plan_id}`);
|
||||
|
||||
try {
|
||||
// Generate final ISO-20022 message
|
||||
const isoMessage = await generatePacs008(plan);
|
||||
|
||||
// Mock: In real implementation, this would:
|
||||
// 1. Send ISO message to bank connector
|
||||
// 2. Receive confirmation and message ID
|
||||
// 3. Store message ID for audit trail
|
||||
|
||||
const isoMessageId = `MSG-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
// Simulate processing delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
isoMessageId,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort bank instruction (2PC abort phase)
|
||||
* Cancels provisional instruction
|
||||
*/
|
||||
export async function abortBankInstruction(planId: string): Promise<void> {
|
||||
console.log(`[Bank] Aborting instruction for plan ${planId}`);
|
||||
|
||||
// Mock: In real implementation, this would:
|
||||
// 1. Generate cancellation message (camt.056)
|
||||
// 2. Send to bank connector
|
||||
// 3. Confirm cancellation
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
102
orchestrator/src/services/compliance.ts
Normal file
102
orchestrator/src/services/compliance.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import type { Plan } from "../types/plan";
|
||||
import { checkKYC, checkAML, getIdentityData } from "../integrations/compliance";
|
||||
|
||||
export interface ComplianceStatus {
|
||||
lei?: string;
|
||||
did?: string;
|
||||
kyc?: {
|
||||
level: number;
|
||||
verified: boolean;
|
||||
expiresAt?: string;
|
||||
};
|
||||
aml?: {
|
||||
passed: boolean;
|
||||
lastCheck?: string;
|
||||
riskLevel?: string;
|
||||
};
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get compliance status for a user/creator
|
||||
*/
|
||||
export async function getComplianceStatus(creator: string): Promise<ComplianceStatus> {
|
||||
try {
|
||||
const identity = await getIdentityData(creator);
|
||||
|
||||
const kyc = await checkKYC(creator);
|
||||
const aml = await checkAML(creator);
|
||||
|
||||
return {
|
||||
lei: identity?.lei,
|
||||
did: identity?.did,
|
||||
kyc: {
|
||||
level: kyc?.level || 0,
|
||||
verified: kyc?.verified || false,
|
||||
expiresAt: kyc?.expiresAt,
|
||||
},
|
||||
aml: {
|
||||
passed: aml?.passed || false,
|
||||
lastCheck: aml?.lastCheck,
|
||||
riskLevel: aml?.riskLevel || "LOW",
|
||||
},
|
||||
valid: !!(identity?.lei && identity?.did && kyc?.verified && aml?.passed),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Compliance check failed:", error);
|
||||
return {
|
||||
valid: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get compliance data for ISO message injection
|
||||
*/
|
||||
export async function getComplianceData(creator: string): Promise<ComplianceStatus> {
|
||||
return await getComplianceStatus(creator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate workflow compliance requirements
|
||||
*/
|
||||
export async function validateWorkflowCompliance(plan: Plan): Promise<{
|
||||
valid: boolean;
|
||||
required: string[];
|
||||
missing: string[];
|
||||
warnings: string[];
|
||||
}> {
|
||||
const status = await getComplianceStatus(plan.creator);
|
||||
|
||||
const hasFiatSteps = plan.steps.some((s) => s.type === "pay" || s.type === "repay");
|
||||
const required: string[] = [];
|
||||
const missing: string[] = [];
|
||||
const warnings: string[] = [];
|
||||
|
||||
if (hasFiatSteps) {
|
||||
required.push("LEI", "DID", "KYC", "AML");
|
||||
|
||||
if (!status.lei) missing.push("LEI");
|
||||
if (!status.did) missing.push("DID");
|
||||
if (!status.kyc?.verified) missing.push("KYC");
|
||||
if (!status.aml?.passed) missing.push("AML");
|
||||
|
||||
// Check KYC expiration
|
||||
if (status.kyc?.expiresAt) {
|
||||
const expiresAt = new Date(status.kyc.expiresAt);
|
||||
if (expiresAt < new Date()) {
|
||||
warnings.push("KYC has expired");
|
||||
} else if (expiresAt < new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)) {
|
||||
warnings.push("KYC expires within 30 days");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: missing.length === 0,
|
||||
required,
|
||||
missing,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
|
||||
77
orchestrator/src/services/dlt.ts
Normal file
77
orchestrator/src/services/dlt.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { Plan } from "../types/plan";
|
||||
|
||||
/**
|
||||
* Prepare DLT execution (2PC prepare phase)
|
||||
* Reserves collateral and locks amounts
|
||||
*/
|
||||
export async function prepareDLTExecution(plan: Plan): Promise<boolean> {
|
||||
// Mock: In real implementation, this would call the handler contract's prepare() function
|
||||
// For now, simulate preparation
|
||||
console.log(`[DLT] Preparing execution for plan ${plan.plan_id}`);
|
||||
|
||||
// Simulate async preparation
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit DLT execution (2PC commit phase)
|
||||
* Executes all DLT steps atomically
|
||||
*/
|
||||
export async function commitDLTExecution(plan: Plan): Promise<{
|
||||
success: boolean;
|
||||
txHash?: string;
|
||||
error?: string;
|
||||
}> {
|
||||
console.log(`[DLT] Committing execution for plan ${plan.plan_id}`);
|
||||
|
||||
try {
|
||||
// Mock: In real implementation, this would:
|
||||
// 1. Call handler contract's executeCombo() function
|
||||
// 2. Wait for transaction confirmation
|
||||
// 3. Return transaction hash
|
||||
|
||||
const txHash = `0x${Math.random().toString(16).substr(2, 64)}`;
|
||||
|
||||
// Simulate execution delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
txHash,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort DLT execution (2PC abort phase)
|
||||
* Releases reserved collateral and unlocks amounts
|
||||
*/
|
||||
export async function abortDLTExecution(planId: string): Promise<void> {
|
||||
console.log(`[DLT] Aborting execution for plan ${planId}`);
|
||||
|
||||
// Mock: In real implementation, this would call handler contract's abort() function
|
||||
// to release any reserved resources
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DLT execution status
|
||||
*/
|
||||
export async function getDLTStatus(planId: string): Promise<{
|
||||
status: string;
|
||||
txHash?: string;
|
||||
blockNumber?: number;
|
||||
}> {
|
||||
// Mock implementation
|
||||
return {
|
||||
status: "pending",
|
||||
};
|
||||
}
|
||||
|
||||
202
orchestrator/src/services/execution.ts
Normal file
202
orchestrator/src/services/execution.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import { EventEmitter } from "events";
|
||||
import { getPlanById, updatePlanStatus } from "../db/plans";
|
||||
import { prepareDLTExecution, commitDLTExecution, abortDLTExecution } from "./dlt";
|
||||
import { prepareBankInstruction, commitBankInstruction, abortBankInstruction } from "./bank";
|
||||
import { registerPlan, finalizePlan } from "./notary";
|
||||
import type { PlanStatusEvent } from "../types/execution";
|
||||
|
||||
export class ExecutionCoordinator extends EventEmitter {
|
||||
private executions: Map<string, {
|
||||
planId: string;
|
||||
status: string;
|
||||
phase: string;
|
||||
startedAt: Date;
|
||||
error?: string;
|
||||
}> = new Map();
|
||||
|
||||
/**
|
||||
* Execute a plan using 2PC (two-phase commit) pattern
|
||||
*/
|
||||
async executePlan(planId: string): Promise<{ executionId: string }> {
|
||||
const executionId = `exec-${Date.now()}`;
|
||||
|
||||
this.executions.set(executionId, {
|
||||
planId,
|
||||
status: "pending",
|
||||
phase: "prepare",
|
||||
startedAt: new Date(),
|
||||
});
|
||||
|
||||
this.emitStatus(executionId, {
|
||||
phase: "prepare",
|
||||
status: "in_progress",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
try {
|
||||
// Get plan
|
||||
const plan = await getPlanById(planId);
|
||||
if (!plan) {
|
||||
throw new Error("Plan not found");
|
||||
}
|
||||
|
||||
// PHASE 1: PREPARE
|
||||
await this.preparePhase(executionId, plan);
|
||||
|
||||
// PHASE 2: EXECUTE DLT
|
||||
await this.executeDLTPhase(executionId, plan);
|
||||
|
||||
// PHASE 3: BANK INSTRUCTION
|
||||
await this.bankInstructionPhase(executionId, plan);
|
||||
|
||||
// PHASE 4: COMMIT
|
||||
await this.commitPhase(executionId, plan);
|
||||
|
||||
this.emitStatus(executionId, {
|
||||
phase: "complete",
|
||||
status: "complete",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await updatePlanStatus(planId, "complete");
|
||||
|
||||
return { executionId };
|
||||
} catch (error: any) {
|
||||
// Rollback on error
|
||||
await this.abortExecution(executionId, planId, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async preparePhase(executionId: string, plan: any) {
|
||||
this.emitStatus(executionId, {
|
||||
phase: "prepare",
|
||||
status: "in_progress",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Prepare DLT execution
|
||||
const dltPrepared = await prepareDLTExecution(plan);
|
||||
if (!dltPrepared) {
|
||||
throw new Error("DLT preparation failed");
|
||||
}
|
||||
|
||||
// Prepare bank instruction (provisional)
|
||||
const bankPrepared = await prepareBankInstruction(plan);
|
||||
if (!bankPrepared) {
|
||||
await abortDLTExecution(plan.plan_id);
|
||||
throw new Error("Bank preparation failed");
|
||||
}
|
||||
|
||||
// Register plan with notary
|
||||
await registerPlan(plan);
|
||||
|
||||
this.emitStatus(executionId, {
|
||||
phase: "prepare",
|
||||
status: "complete",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
private async executeDLTPhase(executionId: string, plan: any) {
|
||||
this.emitStatus(executionId, {
|
||||
phase: "execute_dlt",
|
||||
status: "in_progress",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const result = await commitDLTExecution(plan);
|
||||
if (!result.success) {
|
||||
await abortDLTExecution(plan.plan_id);
|
||||
await abortBankInstruction(plan.plan_id);
|
||||
throw new Error("DLT execution failed: " + result.error);
|
||||
}
|
||||
|
||||
this.emitStatus(executionId, {
|
||||
phase: "execute_dlt",
|
||||
status: "complete",
|
||||
dltTxHash: result.txHash,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
private async bankInstructionPhase(executionId: string, plan: any) {
|
||||
this.emitStatus(executionId, {
|
||||
phase: "bank_instruction",
|
||||
status: "in_progress",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const result = await commitBankInstruction(plan);
|
||||
if (!result.success) {
|
||||
// DLT already committed, need to handle rollback
|
||||
throw new Error("Bank instruction failed: " + result.error);
|
||||
}
|
||||
|
||||
this.emitStatus(executionId, {
|
||||
phase: "bank_instruction",
|
||||
status: "complete",
|
||||
isoMessageId: result.isoMessageId,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
private async commitPhase(executionId: string, plan: any) {
|
||||
this.emitStatus(executionId, {
|
||||
phase: "commit",
|
||||
status: "in_progress",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Finalize with notary
|
||||
await finalizePlan(plan.plan_id, {
|
||||
dltTxHash: "mock-tx-hash",
|
||||
isoMessageId: "mock-iso-id",
|
||||
});
|
||||
|
||||
this.emitStatus(executionId, {
|
||||
phase: "commit",
|
||||
status: "complete",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
async abortExecution(executionId: string, planId: string, error: string) {
|
||||
const execution = this.executions.get(executionId);
|
||||
if (!execution) return;
|
||||
|
||||
try {
|
||||
// Abort DLT
|
||||
await abortDLTExecution(planId);
|
||||
|
||||
// Abort bank
|
||||
await abortBankInstruction(planId);
|
||||
|
||||
await updatePlanStatus(planId, "aborted");
|
||||
|
||||
this.emitStatus(executionId, {
|
||||
phase: "aborted",
|
||||
status: "failed",
|
||||
error,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
} catch (abortError: any) {
|
||||
console.error("Abort failed:", abortError);
|
||||
}
|
||||
}
|
||||
|
||||
async getExecutionStatus(executionId: string) {
|
||||
return this.executions.get(executionId);
|
||||
}
|
||||
|
||||
private emitStatus(executionId: string, event: PlanStatusEvent) {
|
||||
this.emit("status", executionId, event);
|
||||
}
|
||||
|
||||
onStatus(callback: (executionId: string, event: PlanStatusEvent) => void) {
|
||||
this.on("status", callback);
|
||||
}
|
||||
}
|
||||
|
||||
export const executionCoordinator = new ExecutionCoordinator();
|
||||
|
||||
179
orchestrator/src/services/iso20022.ts
Normal file
179
orchestrator/src/services/iso20022.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import type { Plan } from "../types/plan";
|
||||
import { getComplianceData } from "./compliance";
|
||||
|
||||
/**
|
||||
* Generate ISO-20022 pacs.008 (Customer Credit Transfer) message
|
||||
*/
|
||||
export async function generatePacs008(plan: Plan): Promise<string> {
|
||||
const complianceData = await getComplianceData(plan.creator);
|
||||
|
||||
// Find pay step
|
||||
const payStep = plan.steps.find((s) => s.type === "pay");
|
||||
if (!payStep || payStep.type !== "pay") {
|
||||
throw new Error("Plan must contain a pay step");
|
||||
}
|
||||
|
||||
const isoMessage = {
|
||||
Document: {
|
||||
"@xmlns": "urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10",
|
||||
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||
CstmrCdtTrfInitn: {
|
||||
GrpHdr: {
|
||||
MsgId: `MSG-${plan.plan_id}`,
|
||||
CreDtTm: new Date().toISOString(),
|
||||
NbOfTxs: "1",
|
||||
CtrlSum: payStep.amount.toString(),
|
||||
InitgPty: {
|
||||
Nm: complianceData?.lei || "Unknown",
|
||||
Id: {
|
||||
OrgId: {
|
||||
Othr: {
|
||||
Id: complianceData?.lei || "",
|
||||
SchmeNm: {
|
||||
Cd: "LEI",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
PmtInf: {
|
||||
PmtInfId: `PMT-${plan.plan_id}`,
|
||||
PmtMtd: "TRF",
|
||||
NbOfTxs: "1",
|
||||
CtrlSum: payStep.amount.toString(),
|
||||
PmtTpInf: {
|
||||
SvcLvl: {
|
||||
Cd: "SEPA",
|
||||
},
|
||||
},
|
||||
ReqdExctnDt: new Date().toISOString().split("T")[0],
|
||||
Dbtr: {
|
||||
Nm: complianceData?.lei || "Unknown",
|
||||
Id: {
|
||||
OrgId: {
|
||||
Othr: {
|
||||
Id: complianceData?.lei || "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
DbtrAcct: {
|
||||
Id: {
|
||||
IBAN: "DE89370400440532013000", // Mock
|
||||
},
|
||||
},
|
||||
DbtrAgt: {
|
||||
FinInstnId: {
|
||||
BICFI: "DEUTDEFF", // Mock
|
||||
},
|
||||
},
|
||||
CdtTrfTxInf: {
|
||||
PmtId: {
|
||||
InstrId: `INSTR-${plan.plan_id}`,
|
||||
EndToEndId: plan.plan_id,
|
||||
},
|
||||
Amt: {
|
||||
InstdAmt: {
|
||||
"@Ccy": payStep.asset,
|
||||
"#text": payStep.amount.toString(),
|
||||
},
|
||||
},
|
||||
CdtrAgt: {
|
||||
FinInstnId: {
|
||||
BICFI: payStep.beneficiary.BIC || "UNKNOWN",
|
||||
},
|
||||
},
|
||||
Cdtr: {
|
||||
Nm: payStep.beneficiary.name || "Unknown",
|
||||
},
|
||||
CdtrAcct: {
|
||||
Id: {
|
||||
IBAN: payStep.beneficiary.IBAN || "",
|
||||
},
|
||||
},
|
||||
RmtInf: {
|
||||
Ustrd: `Plan ID: ${plan.plan_id}, Plan Hash: ${plan.plan_hash}`,
|
||||
},
|
||||
SplmtryData: {
|
||||
PlcAndNm: "ComplianceData",
|
||||
Envlp: {
|
||||
Compl: {
|
||||
LEI: complianceData?.lei || "",
|
||||
DID: complianceData?.did || "",
|
||||
KYC: {
|
||||
Level: complianceData?.kyc?.level || 0,
|
||||
Verified: complianceData?.kyc?.verified || false,
|
||||
},
|
||||
AML: {
|
||||
Passed: complianceData?.aml?.passed || false,
|
||||
RiskLevel: complianceData?.aml?.riskLevel || "UNKNOWN",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Convert to XML string (simplified - in production use proper XML builder)
|
||||
return JSON.stringify(isoMessage, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate ISO-20022 camt.052 (Bank Statement) message
|
||||
*/
|
||||
export async function generateCamt052(planId: string, accountId: string): Promise<string> {
|
||||
// Mock implementation
|
||||
return JSON.stringify({
|
||||
Document: {
|
||||
"@xmlns": "urn:iso:std:iso:20022:tech:xsd:camt.052.001.10",
|
||||
BkToCstmrAcctRpt: {
|
||||
GrpHdr: {
|
||||
MsgId: `MSG-${planId}`,
|
||||
CreDtTm: new Date().toISOString(),
|
||||
},
|
||||
Rpt: {
|
||||
Id: `RPT-${planId}`,
|
||||
Acct: {
|
||||
Id: {
|
||||
IBAN: accountId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate ISO-20022 camt.056 (Cancellation Request) message
|
||||
*/
|
||||
export async function generateCamt056(planId: string, originalMessageId: string): Promise<string> {
|
||||
// Mock implementation
|
||||
return JSON.stringify({
|
||||
Document: {
|
||||
"@xmlns": "urn:iso:std:iso:20022:tech:xsd:camt.056.001.10",
|
||||
CstmrPmtCxlReq: {
|
||||
Assgnmt: {
|
||||
Id: `ASSGN-${planId}`,
|
||||
},
|
||||
Case: {
|
||||
Id: `CASE-${planId}`,
|
||||
Cretr: {
|
||||
Nm: "Orchestrator",
|
||||
},
|
||||
},
|
||||
Undrlyg: {
|
||||
TxInf: {
|
||||
OrgnlInstrId: originalMessageId,
|
||||
OrgnlEndToEndId: planId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
78
orchestrator/src/services/notary.ts
Normal file
78
orchestrator/src/services/notary.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { createHash } from "crypto";
|
||||
import type { Plan } from "../types/plan";
|
||||
|
||||
/**
|
||||
* Register plan with notary service
|
||||
* Stores plan hash and metadata for audit trail
|
||||
*/
|
||||
export async function registerPlan(plan: Plan): Promise<{
|
||||
notaryProof: string;
|
||||
registeredAt: string;
|
||||
}> {
|
||||
console.log(`[Notary] Registering plan ${plan.plan_id}`);
|
||||
|
||||
// Compute plan hash
|
||||
const planHash = createHash("sha256")
|
||||
.update(JSON.stringify(plan))
|
||||
.digest("hex");
|
||||
|
||||
// Mock: In real implementation, this would:
|
||||
// 1. Call NotaryRegistry contract's registerPlan() function
|
||||
// 2. Store plan hash, metadata, timestamp
|
||||
// 3. Get notary signature/proof
|
||||
|
||||
const notaryProof = `0x${createHash("sha256")
|
||||
.update(planHash + "notary-secret")
|
||||
.digest("hex")}`;
|
||||
|
||||
return {
|
||||
notaryProof,
|
||||
registeredAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize plan with execution results
|
||||
* Records final execution state and receipts
|
||||
*/
|
||||
export async function finalizePlan(
|
||||
planId: string,
|
||||
results: {
|
||||
dltTxHash?: string;
|
||||
isoMessageId?: string;
|
||||
}
|
||||
): Promise<{
|
||||
receiptId: string;
|
||||
finalizedAt: string;
|
||||
}> {
|
||||
console.log(`[Notary] Finalizing plan ${planId}`);
|
||||
|
||||
// Mock: In real implementation, this would:
|
||||
// 1. Call NotaryRegistry contract's finalizePlan() function
|
||||
// 2. Store execution results, receipts
|
||||
// 3. Get final notary proof
|
||||
|
||||
const receiptId = `receipt-${planId}-${Date.now()}`;
|
||||
|
||||
return {
|
||||
receiptId,
|
||||
finalizedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notary proof for a plan
|
||||
*/
|
||||
export async function getNotaryProof(planId: string): Promise<{
|
||||
planHash: string;
|
||||
notaryProof: string;
|
||||
registeredAt: string;
|
||||
} | null> {
|
||||
// Mock implementation
|
||||
return {
|
||||
planHash: `0x${Math.random().toString(16).substr(2, 64)}`,
|
||||
notaryProof: `0x${Math.random().toString(16).substr(2, 64)}`,
|
||||
registeredAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
125
orchestrator/src/services/planValidation.ts
Normal file
125
orchestrator/src/services/planValidation.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import type { Plan, PlanStep } from "../types/plan";
|
||||
|
||||
export interface ValidationResult {
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
const MAX_RECURSION_DEPTH = 3;
|
||||
const MAX_LTV = 0.6;
|
||||
|
||||
/**
|
||||
* Validate plan structure
|
||||
*/
|
||||
export function validatePlan(plan: Plan): ValidationResult {
|
||||
const errors: string[] = [];
|
||||
|
||||
// Check required fields
|
||||
if (!plan.steps || plan.steps.length === 0) {
|
||||
errors.push("Plan must contain at least one step");
|
||||
}
|
||||
|
||||
// Check recursion depth
|
||||
const borrowSteps = plan.steps.filter((s) => s.type === "borrow");
|
||||
const recursionDepth = borrowSteps.length - 1;
|
||||
if (recursionDepth > MAX_RECURSION_DEPTH) {
|
||||
errors.push(`Recursion depth ${recursionDepth} exceeds maximum ${MAX_RECURSION_DEPTH}`);
|
||||
}
|
||||
|
||||
// Check LTV
|
||||
if (plan.maxLTV && plan.maxLTV > MAX_LTV) {
|
||||
errors.push(`Max LTV ${plan.maxLTV} exceeds maximum ${MAX_LTV}`);
|
||||
}
|
||||
|
||||
// Validate each step
|
||||
plan.steps.forEach((step, index) => {
|
||||
const stepErrors = validateStep(step, index);
|
||||
errors.push(...stepErrors);
|
||||
});
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate individual step
|
||||
*/
|
||||
function validateStep(step: PlanStep, index: number): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
switch (step.type) {
|
||||
case "borrow":
|
||||
if (!step.asset || step.amount <= 0) {
|
||||
errors.push(`Step ${index + 1}: Invalid borrow step (asset or amount missing)`);
|
||||
}
|
||||
break;
|
||||
case "swap":
|
||||
if (!step.from || !step.to || step.amount <= 0) {
|
||||
errors.push(`Step ${index + 1}: Invalid swap step (from/to/amount missing)`);
|
||||
}
|
||||
break;
|
||||
case "repay":
|
||||
if (!step.asset || step.amount <= 0) {
|
||||
errors.push(`Step ${index + 1}: Invalid repay step (asset or amount missing)`);
|
||||
}
|
||||
break;
|
||||
case "pay":
|
||||
if (!step.asset || step.amount <= 0 || !step.beneficiary?.IBAN) {
|
||||
errors.push(`Step ${index + 1}: Invalid pay step (asset/amount/IBAN missing)`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check step dependencies
|
||||
*/
|
||||
export function checkStepDependencies(steps: PlanStep[]): ValidationResult {
|
||||
const errors: string[] = [];
|
||||
|
||||
for (let i = 1; i < steps.length; i++) {
|
||||
const prevStep = steps[i - 1];
|
||||
const currentStep = steps[i];
|
||||
|
||||
// Check if current step depends on previous step output
|
||||
if (currentStep.type === "swap") {
|
||||
// Swap should receive from previous step
|
||||
const prevOutput = getStepOutput(prevStep);
|
||||
if (prevOutput && currentStep.from !== prevOutput.asset) {
|
||||
errors.push(`Step ${i + 1}: Swap expects ${currentStep.from} but previous step outputs ${prevOutput.asset}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentStep.type === "repay") {
|
||||
// Repay should use same asset as previous step
|
||||
const prevOutput = getStepOutput(prevStep);
|
||||
if (prevOutput && currentStep.asset !== prevOutput.asset) {
|
||||
errors.push(`Step ${i + 1}: Repay expects ${currentStep.asset} but previous step outputs ${prevOutput.asset}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get step output (what asset/amount this step produces)
|
||||
*/
|
||||
function getStepOutput(step: PlanStep): { asset: string; amount: number } | null {
|
||||
switch (step.type) {
|
||||
case "borrow":
|
||||
return { asset: step.asset, amount: step.amount };
|
||||
case "swap":
|
||||
return { asset: step.to, amount: step.amount };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
79
orchestrator/src/services/receipts.ts
Normal file
79
orchestrator/src/services/receipts.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { Plan } from "../types/plan";
|
||||
import { getNotaryProof } from "./notary";
|
||||
import { getDLTStatus } from "./dlt";
|
||||
|
||||
export interface Receipt {
|
||||
receiptId: string;
|
||||
planId: string;
|
||||
planHash: string;
|
||||
dltTransaction?: {
|
||||
txHash: string;
|
||||
blockNumber: number;
|
||||
timestamp: string;
|
||||
};
|
||||
isoMessage?: {
|
||||
messageId: string;
|
||||
messageType: string;
|
||||
timestamp: string;
|
||||
};
|
||||
notaryProof?: {
|
||||
proof: string;
|
||||
registeredAt: string;
|
||||
finalizedAt?: string;
|
||||
};
|
||||
status: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate receipt for a plan execution
|
||||
*/
|
||||
export async function generateReceipt(plan: Plan): Promise<Receipt> {
|
||||
const notaryProof = await getNotaryProof(plan.plan_id);
|
||||
const dltStatus = await getDLTStatus(plan.plan_id);
|
||||
|
||||
const receipt: Receipt = {
|
||||
receiptId: `receipt-${plan.plan_id}-${Date.now()}`,
|
||||
planId: plan.plan_id,
|
||||
planHash: plan.plan_hash || "",
|
||||
status: "complete",
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
if (dltStatus.txHash) {
|
||||
receipt.dltTransaction = {
|
||||
txHash: dltStatus.txHash,
|
||||
blockNumber: dltStatus.blockNumber || 0,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
// Find pay step to get ISO message ID
|
||||
const payStep = plan.steps.find((s) => s.type === "pay");
|
||||
if (payStep) {
|
||||
receipt.isoMessage = {
|
||||
messageId: `MSG-${plan.plan_id}`,
|
||||
messageType: "pacs.008",
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
if (notaryProof) {
|
||||
receipt.notaryProof = {
|
||||
proof: notaryProof.notaryProof,
|
||||
registeredAt: notaryProof.registeredAt,
|
||||
};
|
||||
}
|
||||
|
||||
return receipt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all receipts for a plan
|
||||
*/
|
||||
export async function getPlanReceipts(planId: string): Promise<Receipt[]> {
|
||||
// Mock: In real implementation, this would query database
|
||||
// For now, return empty array or mock data
|
||||
return [];
|
||||
}
|
||||
|
||||
10
orchestrator/src/types/execution.ts
Normal file
10
orchestrator/src/types/execution.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface PlanStatusEvent {
|
||||
phase: string;
|
||||
status: "pending" | "in_progress" | "complete" | "failed";
|
||||
planId?: string;
|
||||
dltTxHash?: string;
|
||||
isoMessageId?: string;
|
||||
error?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
26
orchestrator/src/types/plan.ts
Normal file
26
orchestrator/src/types/plan.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export interface Plan {
|
||||
plan_id?: string;
|
||||
creator: string;
|
||||
steps: PlanStep[];
|
||||
maxRecursion?: number;
|
||||
maxLTV?: number;
|
||||
signature?: string;
|
||||
plan_hash?: string;
|
||||
created_at?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface PlanStep {
|
||||
type: "borrow" | "swap" | "repay" | "pay";
|
||||
asset?: string;
|
||||
amount: number;
|
||||
from?: string;
|
||||
to?: string;
|
||||
collateralRef?: string;
|
||||
beneficiary?: {
|
||||
IBAN?: string;
|
||||
BIC?: string;
|
||||
name?: string;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user