Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 6 additions & 71 deletions lifecycle/config.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,18 @@
import { NetworkConfig } from "@colibri/core";
import type { StellarNetworkId } from "@moonlight/moonlight-sdk";

const WASM_DIR = new URL("../e2e/wasms", import.meta.url).pathname;

export interface LifecycleConfig {
networkPassphrase: string;
rpcUrl: string;
horizonUrl: string;
friendbotUrl: string;
allowHttp: boolean;
channelAuthWasmPath: string;
privacyChannelWasmPath: string;
providerPlatformPath: string;
providerUrl?: string;
networkConfig: NetworkConfig;
networkId: StellarNetworkId;
}

export function loadConfig(): LifecycleConfig {
const network = Deno.env.get("NETWORK") ?? "local";

const channelAuthWasmPath = Deno.env.get("CHANNEL_AUTH_WASM") ??
`${WASM_DIR}/channel_auth_contract.wasm`;
const privacyChannelWasmPath = Deno.env.get("PRIVACY_CHANNEL_WASM") ??
`${WASM_DIR}/privacy_channel.wasm`;
const providerPlatformPath = Deno.env.get("PROVIDER_PLATFORM_PATH") ??
`${Deno.env.get("HOME")}/repos/provider-platform`;
const providerUrl = Deno.env.get("PROVIDER_URL");

if (network === "testnet") {
const networkPassphrase = "Test SDF Network ; September 2015";
const rpcUrl = Deno.env.get("STELLAR_RPC_URL") ??
"https://soroban-testnet.stellar.org";
const horizonUrl = Deno.env.get("HORIZON_URL") ??
"https://horizon-testnet.stellar.org";
const friendbotUrl = Deno.env.get("FRIENDBOT_URL") ??
"https://friendbot.stellar.org";

return {
networkPassphrase,
rpcUrl,
horizonUrl,
friendbotUrl,
allowHttp: false,
channelAuthWasmPath,
privacyChannelWasmPath,
providerPlatformPath,
providerUrl,
networkConfig: NetworkConfig.TestNet({ allowHttp: false }),
networkId: networkPassphrase as StellarNetworkId,
};
}

// Local
const networkPassphrase = Deno.env.get("STELLAR_NETWORK_PASSPHRASE") ??
"Standalone Network ; February 2017";
const rpcUrl = Deno.env.get("STELLAR_RPC_URL") ??
"http://localhost:8000/soroban/rpc";
const horizonUrl = rpcUrl.replace("/soroban/rpc", "");
const friendbotUrl = Deno.env.get("FRIENDBOT_URL") ??
"http://localhost:8000/friendbot";

return {
networkPassphrase,
rpcUrl,
horizonUrl,
friendbotUrl,
allowHttp: true,
channelAuthWasmPath,
privacyChannelWasmPath,
providerPlatformPath,
providerUrl,
networkConfig: NetworkConfig.CustomNet({
networkPassphrase,
rpcUrl,
horizonUrl,
friendbotUrl,
allowHttp: true,
}),
networkId: networkPassphrase as StellarNetworkId,
channelAuthWasmPath: Deno.env.get("CHANNEL_AUTH_WASM") ??
`${WASM_DIR}/channel_auth_contract.wasm`,
privacyChannelWasmPath: Deno.env.get("PRIVACY_CHANNEL_WASM") ??
`${WASM_DIR}/privacy_channel.wasm`,
providerPlatformPath: Deno.env.get("PROVIDER_PLATFORM_PATH") ??
`${Deno.env.get("HOME")}/repos/provider-platform`,
};
}
178 changes: 79 additions & 99 deletions lifecycle/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Keypair } from "stellar-sdk";
import type { ContractId } from "@colibri/core";
import { NetworkConfig, type ContractId } from "@colibri/core";
import type { StellarNetworkId } from "@moonlight/moonlight-sdk";
import type { Config } from "../e2e/config.ts";
import { loadConfig } from "./config.ts";
import { createServer } from "./soroban.ts";
Expand All @@ -11,7 +12,7 @@ import {
} from "./deploy.ts";
import { addProvider, removeProvider } from "./admin.ts";
import { extractEvents, verifyEvent } from "./events.ts";
import { startProvider, type ProviderInstance } from "./provider.ts";
import { startProvider, startStellar } from "./provider.ts";

// Existing E2E modules for the payment flow
import { authenticate } from "../e2e/auth.ts";
Expand Down Expand Up @@ -41,126 +42,118 @@ async function fundAccount(
async function main() {
const startTime = Date.now();
const config = loadConfig();
let providerInstance: ProviderInstance | null = null;

// Cleanup functions accumulated during setup
const cleanups: (() => Promise<void>)[] = [];

console.log("\n=== Moonlight Protocol — Full Lifecycle E2E ===\n");

try {
// ── Setup ──────────────────────────────────────────────────────
console.log("[setup] Initializing...");
const server = createServer(config.rpcUrl, config.allowHttp);
console.log(` Network: ${config.networkPassphrase}`);
console.log(` RPC: ${config.rpcUrl}`);
// ── Start Stellar node ────────────────────────────────────────
console.log("[infra] Starting Stellar node (--limits unlimited)...");
const stellar = await startStellar();
cleanups.push(stellar.stopStellar);
const { rpcUrl, friendbotUrl, horizonUrl, networkPassphrase } = stellar;
console.log(` RPC: ${rpcUrl}`);
console.log(` Friendbot: ${friendbotUrl}`);

const server = createServer(rpcUrl);

// ── Generate & fund accounts ──────────────────────────────────
const admin = Keypair.random();
const provider = Keypair.random();
const treasury = Keypair.random();
console.log(`\n[setup] Accounts`);
console.log(` Admin: ${admin.publicKey()}`);
console.log(` Provider: ${provider.publicKey()}`);
console.log(` Treasury: ${treasury.publicKey()}`);

console.log("\n[setup] Funding accounts...");
await fundAccount(config.friendbotUrl, admin.publicKey());
await fundAccount(friendbotUrl, admin.publicKey());
console.log(" Admin funded");
await fundAccount(config.friendbotUrl, treasury.publicKey());
await fundAccount(friendbotUrl, treasury.publicKey());
console.log(" Treasury funded");

// ── Step 1: Deploy Council (Channel Auth) ──────────────────────
// ── Step 1: Deploy Council (Channel Auth) ────────────────────
console.log("\n[1/7] Deploy Council (Channel Auth)");
const channelAuthWasm = await Deno.readFile(config.channelAuthWasmPath);
const channelAuthHash = await uploadWasm(
server,
admin,
config.networkPassphrase,
channelAuthWasm,
server, admin, networkPassphrase, channelAuthWasm,
);
const { contractId: channelAuthId, txResponse: authDeployTx } =
await deployChannelAuth(
server,
admin,
config.networkPassphrase,
channelAuthHash,
server, admin, networkPassphrase, channelAuthHash,
);

const deployEvents = extractEvents(authDeployTx);
const initResult = verifyEvent(deployEvents, "contract_initialized", true);
if (initResult.found) console.log(" ContractInitialized event verified");
const initResult = verifyEvent(
deployEvents, "contract_initialized", true,
);
if (initResult.found) console.log(" contract_initialized event verified");

// ── Step 2: Deploy Channel (Privacy Channel) ─────────────────
// ── Step 2: Deploy Channel (Privacy Channel) ─────────────────
console.log("\n[2/7] Deploy Channel (Privacy Channel)");
console.log(" Deploying native XLM SAC...");
const assetContractId = await getOrDeployNativeSac(
server,
admin,
config.networkPassphrase,
server, admin, networkPassphrase,
);

const privacyChannelWasm = await Deno.readFile(
config.privacyChannelWasmPath,
);
const privacyChannelHash = await uploadWasm(
server,
admin,
config.networkPassphrase,
privacyChannelWasm,
server, admin, networkPassphrase, privacyChannelWasm,
);
const channelContractId = await deployPrivacyChannel(
server,
admin,
config.networkPassphrase,
privacyChannelHash,
channelAuthId,
assetContractId,
server, admin, networkPassphrase,
privacyChannelHash, channelAuthId, assetContractId,
);

// ── Step 3: Add Privacy Provider ─────────────────────────────
// ── Step 3: Add Privacy Provider ─────────────────────────────
console.log("\n[3/7] Add Privacy Provider");
const addTx = await addProvider(
server,
admin,
config.networkPassphrase,
channelAuthId,
provider.publicKey(),
server, admin, networkPassphrase,
channelAuthId, provider.publicKey(),
);

const addEvents = extractEvents(addTx);
const addResult = verifyEvent(addEvents, "provider_added", true);
if (addResult.found) console.log(" ProviderAdded event verified");

// ── Start provider-platform for payment flow ──────────────────
let providerUrl: string;
if (config.providerUrl) {
// Use externally-managed provider
providerUrl = config.providerUrl;
console.log(`\n[provider] Using existing provider at ${providerUrl}`);
} else {
// Start a fresh provider configured for these contracts
console.log("\n[provider] Starting provider-platform...");
providerInstance = await startProvider({
providerPlatformPath: config.providerPlatformPath,
rpcUrl: config.rpcUrl,
channelContractId,
channelAuthId,
assetContractId,
providerSecretKey: provider.secret(),
treasuryPublicKey: treasury.publicKey(),
treasurySecretKey: treasury.secret(),
});
providerUrl = providerInstance.url;
}
if (addResult.found) console.log(" provider_added event verified");

// Build a Config compatible with the existing E2E modules
// ── Start provider-platform ──────────────────────────────────
console.log("\n[infra] Starting provider-platform...");
const providerInfra = await startProvider({
providerPlatformPath: config.providerPlatformPath,
rpcUrl,
channelContractId,
channelAuthId,
assetContractId,
providerSecretKey: provider.secret(),
treasuryPublicKey: treasury.publicKey(),
treasurySecretKey: treasury.secret(),
});
cleanups.push(providerInfra.stopProvider);
const { providerUrl } = providerInfra;

// Build config for existing E2E modules
const networkConfig = NetworkConfig.CustomNet({
networkPassphrase,
rpcUrl,
horizonUrl,
friendbotUrl,
allowHttp: true,
});
const e2eConfig: Config = {
networkPassphrase: config.networkPassphrase,
rpcUrl: config.rpcUrl,
horizonUrl: config.horizonUrl,
friendbotUrl: config.friendbotUrl,
networkPassphrase,
rpcUrl,
horizonUrl,
friendbotUrl,
providerUrl,
channelContractId: channelContractId as ContractId,
channelAuthId: channelAuthId as ContractId,
channelAssetContractId: assetContractId as ContractId,
networkConfig: config.networkConfig,
networkId: config.networkId,
networkConfig,
networkId: networkPassphrase as StellarNetworkId,
providerSecretKey: provider.secret(),
};

Expand All @@ -169,8 +162,8 @@ async function main() {
console.log(`\n Alice: ${alice.publicKey()}`);
console.log(` Bob: ${bob.publicKey()}`);

await fundAccount(config.friendbotUrl, alice.publicKey());
await fundAccount(config.friendbotUrl, bob.publicKey());
await fundAccount(friendbotUrl, alice.publicKey());
await fundAccount(friendbotUrl, bob.publicKey());
console.log(" Users funded");

// ── Step 4: Deposit ──────────────────────────────────────────
Expand All @@ -185,43 +178,30 @@ async function main() {
const bobJwt = await authenticate(bob, e2eConfig);
console.log(" Bob authenticated");
const receiverOps = await prepareReceive(
bob.secret(),
SEND_AMOUNT,
e2eConfig,
bob.secret(), SEND_AMOUNT, e2eConfig,
);
await send(
alice.secret(),
receiverOps,
SEND_AMOUNT,
aliceJwt,
e2eConfig,
alice.secret(), receiverOps, SEND_AMOUNT, aliceJwt, e2eConfig,
);
console.log(" Send complete");

// ── Step 6: Withdraw ─────────────────────────────────────────
console.log(`\n[6/7] Withdraw (${WITHDRAW_AMOUNT} XLM)`);
await withdraw(
bob.secret(),
bob.publicKey(),
WITHDRAW_AMOUNT,
bobJwt,
e2eConfig,
bob.secret(), bob.publicKey(), WITHDRAW_AMOUNT, bobJwt, e2eConfig,
);
console.log(" Withdraw complete");

// ── Step 7: Remove Privacy Provider ──────────────────────────
console.log("\n[7/7] Remove Privacy Provider");
const removeTx = await removeProvider(
server,
admin,
config.networkPassphrase,
channelAuthId,
provider.publicKey(),
server, admin, networkPassphrase,
channelAuthId, provider.publicKey(),
);

const removeEvents = extractEvents(removeTx);
const removeResult = verifyEvent(removeEvents, "provider_removed", true);
if (removeResult.found) console.log(" ProviderRemoved event verified");
if (removeResult.found) console.log(" provider_removed event verified");

// ── Summary ──────────────────────────────────────────────────
const deployment = {
Expand All @@ -233,8 +213,7 @@ async function main() {
treasuryPublicKey: treasury.publicKey(),
};
await Deno.writeTextFile(
DEPLOYMENT_PATH,
JSON.stringify(deployment, null, 2),
DEPLOYMENT_PATH, JSON.stringify(deployment, null, 2),
);

const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
Expand All @@ -247,12 +226,13 @@ async function main() {
console.log(` Config written to: ${DEPLOYMENT_PATH}`);
console.log(`\n=== Lifecycle E2E passed in ${elapsed}s ===`);
} finally {
// Always clean up the provider
if (providerInstance) {
console.log("\n[cleanup] Stopping provider-platform...");
await providerInstance.stop();
console.log(" Cleaned up");
console.log("\n[cleanup] Stopping infrastructure...");
for (const cleanup of cleanups.reverse()) {
try {
await cleanup();
} catch { /* best effort */ }
}
console.log(" Done");
}
}

Expand Down
Loading
Loading