From 294d33712d75c300aa241780b71a5f889d5a040e Mon Sep 17 00:00:00 2001 From: solidsnakedev Date: Fri, 10 Apr 2026 16:48:21 -0600 Subject: [PATCH 1/3] chore: parallelize test packages and disable retries Remove test task dependency on ^test so evolution and evolution-devnet run in parallel. Set retry: 0 in root vitest config to stop re-running failing tests up to 3 times. --- turbo.json | 2 +- vitest.config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/turbo.json b/turbo.json index 960ca0b7..876894df 100644 --- a/turbo.json +++ b/turbo.json @@ -18,7 +18,7 @@ "outputs": [] }, "test": { - "dependsOn": ["^test"], + "dependsOn": [], "inputs": ["$TURBO_DEFAULT$", ".env*", "vitest.config.*"], "outputs": ["coverage/**"] }, diff --git a/vitest.config.ts b/vitest.config.ts index e5986bb8..2706ab00 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -15,7 +15,7 @@ export default defineConfig(({ mode }) => ({ hookTimeout: 30000, teardownTimeout: 30000, pool: "threads", - retry: 2, + retry: 0, bail: 1, exclude: ["**/node_modules/**", "**/dist/**", "**/temp/**", "**/.direnv/**", "**/.{idea,git,cache,output,temp}/**"], coverage: { From f4015bf37699abc8515cc09cf29ceb10526c5eab Mon Sep 17 00:00:00 2001 From: solidsnakedev Date: Fri, 10 Apr 2026 16:48:40 -0600 Subject: [PATCH 2/3] feat(devnet): add shared cluster infrastructure for faster tests Introduce globalSetup that starts a single Docker cluster shared across 17 test files. Each file gets dedicated account indices so UTxOs and credentials never collide. Reduces cluster startups from 20 to 4. --- .../evolution-devnet/test/global-setup.ts | 149 ++++++++++ .../test/utils/shared-cluster.ts | 254 ++++++++++++++++++ packages/evolution-devnet/vitest.config.ts | 5 +- 3 files changed, 406 insertions(+), 2 deletions(-) create mode 100644 packages/evolution-devnet/test/global-setup.ts create mode 100644 packages/evolution-devnet/test/utils/shared-cluster.ts diff --git a/packages/evolution-devnet/test/global-setup.ts b/packages/evolution-devnet/test/global-setup.ts new file mode 100644 index 00000000..71e6220f --- /dev/null +++ b/packages/evolution-devnet/test/global-setup.ts @@ -0,0 +1,149 @@ +/** + * Global setup for devnet tests. + * + * Starts a single shared Docker cluster (Cardano node + Kupo + Ogmios) that + * most test files connect to. Each test file uses dedicated account indices + * so they don't interfere with each other's UTxOs or credentials. + * + * Tests that need custom genesis configs (Devnet.Genesis, Devnet.integration, + * TxBuilder.VoteValidators) still create their own isolated clusters. + */ + +import type { GlobalSetupContext } from "vitest/node" + +import * as Cluster from "@evolution-sdk/devnet/Cluster" +import * as Config from "@evolution-sdk/devnet/Config" +import * as Genesis from "@evolution-sdk/devnet/Genesis" +import { Client, preprod } from "@evolution-sdk/evolution" +import * as Address from "@evolution-sdk/evolution/Address" +import * as Bytes from "@evolution-sdk/evolution/Bytes" +import * as TransactionHash from "@evolution-sdk/evolution/TransactionHash" + +const TEST_MNEMONIC = + "test test test test test test test test test test test test test test test test test test test test test test test sauce" + +/** Shared cluster ports — no test file should reuse these */ +const SHARED_PORTS = { + node: 5555, + submit: 5556, + kupo: 5557, + ogmios: 5558 +} + +/** All account indices used by shared-cluster tests */ +const ALL_ACCOUNT_INDICES = Array.from({ length: 37 }, (_, i) => i) + +export default async function setup({ provide }: GlobalSetupContext) { + console.log("[global-setup] Starting shared devnet cluster...") + + // Resolve all account addresses for genesis funding + const clients = ALL_ACCOUNT_INDICES.map((accountIndex) => + Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) + ) + const addresses = await Promise.all(clients.map((c) => c.address())) + const addressHexes = addresses.map((addr) => Address.toHex(addr)) + + // Build initial funds — 1T lovelace per account (must be number, not bigint, for JSON.stringify in Cluster.make) + const initialFunds: Record = {} + for (const hex of addressHexes) { + initialFunds[hex] = 1_000_000_000_000 + } + + // Superset shelley genesis + const shelleyGenesis = { + ...Config.DEFAULT_SHELLEY_GENESIS, + slotLength: 0.02, + epochLength: 50, + activeSlotsCoeff: 1.0, + protocolParams: { + ...Config.DEFAULT_SHELLEY_GENESIS.protocolParams, + keyDeposit: 2_000_000, + poolDeposit: 500_000_000 + }, + initialFunds + } as Config.ShelleyGenesis + + // Resolve committee member key hashes for Governance test (accounts 34-35) + // authCommitteeHot uses account 34, resignCommitteeCold uses account 35 + const committeeKeyHash34 = Bytes.toHex(addresses[34].paymentCredential.hash) + const committeeKeyHash35 = Bytes.toHex(addresses[35].paymentCredential.hash) + + // Superset conway genesis + const conwayGenesis = { + ...Config.DEFAULT_CONWAY_GENESIS, + govActionLifetime: 30, + committee: { + members: { + [`keyHash-${committeeKeyHash34}`]: 1000, + [`keyHash-${committeeKeyHash35}`]: 1000 + }, + threshold: 0.66 + } + } + + // Pre-calculate genesis UTxOs + const genesisUtxos = await Genesis.calculateUtxosFromConfig(shelleyGenesis) + + // Build genesis UTxO map: accountIndex -> serialized UTxO + const genesisUtxoMap: Record = {} + for (let i = 0; i < addresses.length; i++) { + const bech32 = Address.toBech32(addresses[i]) + const utxo = genesisUtxos.find((u) => Address.toBech32(u.address) === bech32) + if (utxo) { + genesisUtxoMap[ALL_ACCOUNT_INDICES[i]] = JSON.stringify({ + transactionId: TransactionHash.toHex(utxo.transactionId), + index: utxo.index.toString(), + address: Address.toBech32(utxo.address), + lovelace: utxo.assets.lovelace.toString() + }) + } + } + + // Create and start cluster + const cluster = await Cluster.make({ + clusterName: "shared-devnet", + ports: { node: SHARED_PORTS.node, submit: SHARED_PORTS.submit }, + shelleyGenesis, + conwayGenesis, + kupo: { enabled: true, port: SHARED_PORTS.kupo, logLevel: "Info" }, + ogmios: { enabled: true, port: SHARED_PORTS.ogmios, logLevel: "info" } + }) + + await Cluster.start(cluster) + await new Promise((resolve) => setTimeout(resolve, 5_000)) + + // Store cluster for teardown (globalThis persists in the main process) + ;(globalThis as any).__sharedCluster = cluster + + // Provide serializable data to test files via vitest inject + const chainConfig = Cluster.getChain(cluster) + + provide("sharedCluster" as any, JSON.stringify({ + ports: SHARED_PORTS, + chain: { + id: chainConfig.id, + name: chainConfig.name, + networkMagic: chainConfig.networkMagic, + epochLength: chainConfig.epochLength, + slotConfig: { + zeroTime: chainConfig.slotConfig.zeroTime.toString(), + zeroSlot: chainConfig.slotConfig.zeroSlot.toString(), + slotLength: chainConfig.slotConfig.slotLength + } + }, + genesisUtxoMap + })) + + console.log(`[global-setup] Shared cluster ready (${ALL_ACCOUNT_INDICES.length} accounts funded)`) + + // Return teardown function + return async () => { + const storedCluster = (globalThis as any).__sharedCluster as Cluster.Cluster | undefined + if (storedCluster) { + console.log("[global-teardown] Stopping shared devnet cluster...") + await Cluster.stop(storedCluster) + await Cluster.remove(storedCluster) + console.log("[global-teardown] Shared cluster removed") + } + } +} diff --git a/packages/evolution-devnet/test/utils/shared-cluster.ts b/packages/evolution-devnet/test/utils/shared-cluster.ts new file mode 100644 index 00000000..dd15e2aa --- /dev/null +++ b/packages/evolution-devnet/test/utils/shared-cluster.ts @@ -0,0 +1,254 @@ +/** + * Shared cluster utilities for devnet tests. + * + * Provides helpers to reduce boilerplate across test files and enable + * progressive migration to shared clusters. + * + * Two modes: + * 1. `setupCluster()` — creates a dedicated cluster per test file (legacy) + * 2. `useSharedCluster()` — connects to the shared cluster from globalSetup + */ + +import * as Cluster from "@evolution-sdk/devnet/Cluster" +import * as Config from "@evolution-sdk/devnet/Config" +import * as Genesis from "@evolution-sdk/devnet/Genesis" +import { Cardano, Client, preprod } from "@evolution-sdk/evolution" +import * as Address from "@evolution-sdk/evolution/Address" +import * as TransactionHash from "@evolution-sdk/evolution/TransactionHash" +import * as UTxO from "@evolution-sdk/evolution/UTxO" + +export const TEST_MNEMONIC = + "test test test test test test test test test test test test test test test test test test test test test test test sauce" + +export const FAST_SHELLEY_GENESIS: Partial = { + slotLength: 0.02, + epochLength: 50, + activeSlotsCoeff: 1.0 +} + +// --------------------------------------------------------------------------- +// Shared cluster (Phase 2+) +// --------------------------------------------------------------------------- + +/** Shape of the serialized data from globalSetup's provide() */ +export type SharedClusterInfo = { + readonly ports: { + readonly node: number + readonly submit: number + readonly kupo: number + readonly ogmios: number + } + readonly chain: { + readonly id: number + readonly name: string + readonly networkMagic: number + readonly epochLength: number + readonly slotConfig: { + readonly zeroTime: string + readonly zeroSlot: string + readonly slotLength: number + } + } + readonly genesisUtxoMap: Record +} + +export type SharedClusterResult = { + readonly makeClient: (accountIndex: number) => ReturnType + readonly getGenesisUtxo: (accountIndex: number) => Cardano.UTxO.UTxO + readonly genesisUtxos: ReadonlyArray +} + +/** + * Connects to the shared cluster started by globalSetup. + * Call this in beforeAll with the JSON string from inject('sharedCluster'). + */ +export const useSharedCluster = async ( + injectData: string, + accountIndices: ReadonlyArray +): Promise => { + const info: SharedClusterInfo = JSON.parse(injectData) + + const chain = { + id: info.chain.id, + name: info.chain.name, + networkMagic: info.chain.networkMagic, + epochLength: info.chain.epochLength, + slotConfig: { + zeroTime: BigInt(info.chain.slotConfig.zeroTime), + zeroSlot: BigInt(info.chain.slotConfig.zeroSlot), + slotLength: info.chain.slotConfig.slotLength + } + } + + const makeClient = (accountIndex: number) => + Client.make(chain) + .withKupmios({ + kupoUrl: `http://localhost:${info.ports.kupo}`, + ogmiosUrl: `http://localhost:${info.ports.ogmios}` + }) + .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) + + // Deserialize genesis UTxOs for requested accounts + const utxoList: Array = [] + const utxoMap = new Map() + + for (const idx of accountIndices) { + const raw = info.genesisUtxoMap[idx] + if (!raw) throw new Error(`No genesis UTxO for account ${idx} in shared cluster`) + const parsed = JSON.parse(raw) + const utxo = new UTxO.UTxO({ + transactionId: TransactionHash.fromHex(parsed.transactionId), + index: BigInt(parsed.index), + address: Address.fromBech32(parsed.address), + assets: Cardano.Assets.fromLovelace(BigInt(parsed.lovelace)) + }) + utxoList.push(utxo) + utxoMap.set(idx, utxo) + } + + const getGenesisUtxo = (accountIndex: number): Cardano.UTxO.UTxO => { + const utxo = utxoMap.get(accountIndex) + if (!utxo) throw new Error(`No genesis UTxO for account ${accountIndex}`) + return utxo + } + + return { makeClient, getGenesisUtxo, genesisUtxos: utxoList } +} + +// --------------------------------------------------------------------------- +// Per-file cluster (legacy, for isolated tests) +// --------------------------------------------------------------------------- + +/** + * Resolves addresses and their hex representations for the given account indices. + */ +export const resolveAccounts = async ( + accountIndices: ReadonlyArray +): Promise<{ + addresses: ReadonlyArray + addressHexes: ReadonlyArray +}> => { + const clients = accountIndices.map((accountIndex) => + Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) + ) + const addresses = await Promise.all(clients.map((c) => c.address())) + const addressHexes = addresses.map((addr) => Address.toHex(addr)) + return { addresses, addressHexes } +} + +/** + * Builds an initialFunds map from account addresses and a funding amount. + */ +export const buildInitialFunds = ( + addressHexes: ReadonlyArray, + amount: number = 1_000_000_000_000 +): Record => { + const funds: Record = {} + for (const hex of addressHexes) { + funds[hex] = amount + } + return funds +} + +/** + * Creates a signing client connected to a running cluster. + */ +export const createTestClient = ( + cluster: Cluster.Cluster, + kupoPort: number, + ogmiosPort: number, + accountIndex: number = 0 +) => + Client.make(Cluster.getChain(cluster)) + .withKupmios({ kupoUrl: `http://localhost:${kupoPort}`, ogmiosUrl: `http://localhost:${ogmiosPort}` }) + .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) + +/** + * Finds the genesis UTxO for a specific address. + */ +export const getGenesisUtxo = ( + genesisUtxos: ReadonlyArray, + address: Address.Address +): Cardano.UTxO.UTxO => { + const bech32 = Address.toBech32(address) + const utxo = genesisUtxos.find((u) => Address.toBech32(u.address) === bech32) + if (!utxo) throw new Error(`No genesis UTxO found for address ${bech32}`) + return utxo +} + +export type ClusterSetupOptions = { + readonly clusterName: string + readonly accountIndices: ReadonlyArray + readonly ports: { + readonly node: number + readonly submit: number + readonly kupo: number + readonly ogmios: number + } + readonly initialFundAmount?: number + readonly shelleyGenesisOverrides?: Partial + readonly conwayGenesis?: Partial +} + +export type ClusterSetupResult = { + readonly cluster: Cluster.Cluster + readonly genesisConfig: Config.ShelleyGenesis + readonly genesisUtxos: ReadonlyArray + readonly genesisUtxosByAccount: ReadonlyMap + readonly makeClient: (accountIndex: number) => ReturnType +} + +/** + * Sets up a dedicated devnet cluster for a test file. + * Use this for tests that need custom genesis configs or full isolation. + */ +export const setupCluster = async (options: ClusterSetupOptions): Promise => { + const { addresses, addressHexes } = await resolveAccounts(options.accountIndices) + const initialFunds = buildInitialFunds(addressHexes, options.initialFundAmount) + + const genesisConfig = { + ...Config.DEFAULT_SHELLEY_GENESIS, + ...FAST_SHELLEY_GENESIS, + ...options.shelleyGenesisOverrides, + initialFunds + } as Config.ShelleyGenesis + + const genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) + + const genesisUtxosByAccount = new Map() + for (let i = 0; i < addresses.length; i++) { + const utxo = genesisUtxos.find((u) => Address.toBech32(u.address) === Address.toBech32(addresses[i])) + if (utxo) genesisUtxosByAccount.set(options.accountIndices[i], utxo) + } + + const clusterConfig: Config.DevNetConfig = { + clusterName: options.clusterName, + ports: { node: options.ports.node, submit: options.ports.submit }, + shelleyGenesis: genesisConfig, + kupo: { enabled: true, port: options.ports.kupo, logLevel: "Info" }, + ogmios: { enabled: true, port: options.ports.ogmios, logLevel: "info" } + } + + if (options.conwayGenesis) { + ;(clusterConfig as any).conwayGenesis = options.conwayGenesis + } + + const cluster = await Cluster.make(clusterConfig) + await Cluster.start(cluster) + await new Promise((resolve) => setTimeout(resolve, 3_000)) + + const makeClient = (accountIndex: number) => + createTestClient(cluster, options.ports.kupo, options.ports.ogmios, accountIndex) + + return { cluster, genesisConfig, genesisUtxos, genesisUtxosByAccount, makeClient } +} + +/** + * Tears down a cluster (stop + remove). + */ +export const teardownCluster = async (cluster: Cluster.Cluster | undefined): Promise => { + if (cluster) { + await Cluster.stop(cluster) + await Cluster.remove(cluster) + } +} diff --git a/packages/evolution-devnet/vitest.config.ts b/packages/evolution-devnet/vitest.config.ts index 00640b0a..3ad48a44 100644 --- a/packages/evolution-devnet/vitest.config.ts +++ b/packages/evolution-devnet/vitest.config.ts @@ -8,8 +8,9 @@ export default defineConfig({ testTimeout: 120_000, hookTimeout: 90_000, teardownTimeout: 60_000, - // Serialize all test files — each file creates its own cluster (cardano-node + kupo + ogmios) - // Running them concurrently exhausts Docker resources on CI / local machines + // globalSetup starts a shared cluster once; most tests connect to it. + // Isolated tests (Genesis, integration, VoteValidators) still create their own clusters. + globalSetup: ["./test/global-setup.ts"], pool: "forks", poolOptions: { forks: { From 0135c50822bf557c40c3c130989223e474bef40a Mon Sep 17 00:00:00 2001 From: solidsnakedev Date: Fri, 10 Apr 2026 16:48:59 -0600 Subject: [PATCH 3/3] refactor(devnet): migrate 17 test files to shared cluster Each test file now connects to the shared cluster from globalSetup instead of creating its own Docker containers. Removes ~880 lines of duplicated boilerplate (cluster setup, mnemonic, port config). --- .../test/Client.Devnet.test.ts | 78 ++------ .../test/TxBuilder.AddSigner.test.ts | 65 ++----- .../test/TxBuilder.Chain.test.ts | 64 ++----- .../test/TxBuilder.Compose.test.ts | 75 ++------ .../test/TxBuilder.Governance.test.ts | 135 +++----------- .../test/TxBuilder.Metadata.test.ts | 68 ++----- .../test/TxBuilder.Mint.test.ts | 71 ++------ .../test/TxBuilder.NativeScript.test.ts | 115 ++++-------- .../test/TxBuilder.PlutusMint.test.ts | 80 ++------- .../test/TxBuilder.Pool.test.ts | 91 +++------- .../test/TxBuilder.RedeemerBuilder.test.ts | 71 +------- .../test/TxBuilder.ScriptStake.test.ts | 92 +++------- .../test/TxBuilder.Scripts.test.ts | 87 +++------ .../test/TxBuilder.SpendScriptRef.test.ts | 74 ++------ .../test/TxBuilder.Stake.test.ts | 161 ++++------------- .../test/TxBuilder.Validity.test.ts | 76 ++------ .../test/TxBuilder.Vote.test.ts | 169 +++++------------- 17 files changed, 347 insertions(+), 1225 deletions(-) diff --git a/packages/evolution-devnet/test/Client.Devnet.test.ts b/packages/evolution-devnet/test/Client.Devnet.test.ts index 62495550..f37f73ea 100644 --- a/packages/evolution-devnet/test/Client.Devnet.test.ts +++ b/packages/evolution-devnet/test/Client.Devnet.test.ts @@ -1,10 +1,8 @@ -import { describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import { Cardano, Client, preprod } from "@evolution-sdk/evolution" +import { beforeAll, describe, expect, it } from "@effect/vitest" +import { Cardano } from "@evolution-sdk/evolution" import * as CoreAddress from "@evolution-sdk/evolution/Address" -import { afterAll, beforeAll } from "vitest" +import { inject } from "vitest" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" // Alias for Cardano.Assets const CoreAssets = Cardano.Assets @@ -13,69 +11,26 @@ const CoreAssets = Cardano.Assets * Client integration tests with local Devnet */ describe("Client with Devnet", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisUtxos: Array = [] - let genesisConfig: Config.ShelleyGenesis - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" - - const createTestClient = () => - Client.make(Cluster.getChain(devnetCluster!)) - .withKupmios({ kupoUrl: "http://localhost:1443", ogmiosUrl: "http://localhost:1338" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0 }) + let shared: SharedClusterResult beforeAll(async () => { - const testClient = Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0 }) - - const testAddress = await testClient.address() - const testAddressHex = CoreAddress.toHex(testAddress) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { [testAddressHex]: 900_000_000_000 } - } - - devnetCluster = await Cluster.make({ - clusterName: "client-kupmios-wallet-test", - ports: { node: 6001, submit: 9002 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1443, logLevel: "Info" }, - ogmios: { enabled: true, port: 1338, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) + shared = await useSharedCluster(inject("sharedCluster" as any), [0]) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) - - it("should calculate genesis UTxOs from config", { timeout: 10_000 }, async () => { - const calculatedUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) + it("should verify genesis UTxOs have expected shape", { timeout: 10_000 }, async () => { + expect(shared.genesisUtxos).toBeDefined() + expect(shared.genesisUtxos.length).toBe(1) - expect(calculatedUtxos).toBeDefined() - expect(calculatedUtxos.length).toBe(1) - - const utxo = calculatedUtxos[0] + const utxo = shared.genesisUtxos[0] expect(utxo.transactionId).toBeDefined() expect(Cardano.TransactionHash.toHex(utxo.transactionId).length).toBe(64) - expect(utxo.index).toBe(0n) + expect(utxo.index).toBeDefined() expect(CoreAddress.toBech32(utxo.address)).toMatch(/^addr_test/) - expect(utxo.assets.lovelace).toBe(900_000_000_000n) - - genesisUtxos = [...calculatedUtxos] + expect(utxo.assets.lovelace).toBeGreaterThan(0n) }) it("should create signing client and query wallet address", { timeout: 30_000 }, async () => { - const client = createTestClient() + const client = shared.makeClient(0) const address = await client.address() expect(address).toBeDefined() @@ -84,14 +39,14 @@ describe("Client with Devnet", () => { }) it("should query wallet UTxOs", { timeout: 30_000 }, async () => { - const client = createTestClient() + const client = shared.makeClient(0) const utxos = await client.getWalletUtxos() expect(utxos).toEqual([]) }) it("should query protocol parameters", { timeout: 10_000 }, async () => { - const client = createTestClient() + const client = shared.makeClient(0) const params = await client.getProtocolParameters() expect(params).toBeDefined() @@ -105,11 +60,12 @@ describe("Client with Devnet", () => { }) it("should build and submit transaction", { timeout: 30_000 }, async () => { + const genesisUtxos = shared.genesisUtxos if (genesisUtxos.length === 0) { throw new Error("Genesis UTxOs not loaded") } - const client = createTestClient() + const client = shared.makeClient(0) const genesisAddress = await client.address() const genesisAddressBech32 = CoreAddress.toBech32(genesisAddress) const genesisUtxo = genesisUtxos.find((u) => CoreAddress.toBech32(u.address) === genesisAddressBech32) diff --git a/packages/evolution-devnet/test/TxBuilder.AddSigner.test.ts b/packages/evolution-devnet/test/TxBuilder.AddSigner.test.ts index 93751ecb..1621535d 100644 --- a/packages/evolution-devnet/test/TxBuilder.AddSigner.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.AddSigner.test.ts @@ -5,67 +5,22 @@ * to the transaction body's requiredSigners field. */ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import { Cardano, Client, preprod } from "@evolution-sdk/evolution" -import * as Address from "@evolution-sdk/evolution/Address" +import { beforeAll, describe, expect, it } from "@effect/vitest" +import { Cardano } from "@evolution-sdk/evolution" import * as KeyHash from "@evolution-sdk/evolution/KeyHash" import * as TransactionHash from "@evolution-sdk/evolution/TransactionHash" +import { inject } from "vitest" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" describe("TxBuilder addSigner (Devnet Submit)", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let genesisUtxos: ReadonlyArray = [] - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" - - const createTestClient = (accountIndex: number = 0) => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1449", ogmiosUrl: "http://localhost:1344" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - } + let shared: SharedClusterResult beforeAll(async () => { - const tempClient = Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0, addressType: "Base" }) - - const testAddress = await tempClient.address() - const testAddressHex = Address.toHex(testAddress) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { [testAddressHex]: 500_000_000_000 } - } - - genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - devnetCluster = await Cluster.make({ - clusterName: "addsigner-test", - ports: { node: 6007, submit: 9008 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1449, logLevel: "Info" }, - ogmios: { enabled: true, port: 1344, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) + shared = await useSharedCluster(inject("sharedCluster" as any), [1, 2]) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) - it("should include requiredSigners in transaction body and submit successfully", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(1) const myAddress = await client.address() // Extract payment key hash from address credential @@ -81,7 +36,7 @@ describe("TxBuilder addSigner (Devnet Submit)", () => { address: myAddress, assets: Cardano.Assets.fromLovelace(5_000_000n) }) - .build({ availableUtxos: [...genesisUtxos] }) + .build({ availableUtxos: [...shared.genesisUtxos.filter((u) => u === shared.getGenesisUtxo(1))] }) const tx = await signBuilder.toTransaction() @@ -103,8 +58,8 @@ describe("TxBuilder addSigner (Devnet Submit)", () => { it("should support multi-sig with partial signing and assembly", { timeout: 60_000 }, async () => { // Create two clients with different account indices (different key pairs) - const client1 = createTestClient(0) - const client2 = createTestClient(1) + const client1 = shared.makeClient(1) + const client2 = shared.makeClient(2) const address1 = await client1.address() const address2 = await client2.address() diff --git a/packages/evolution-devnet/test/TxBuilder.Chain.test.ts b/packages/evolution-devnet/test/TxBuilder.Chain.test.ts index e41d9a63..c1924a87 100644 --- a/packages/evolution-devnet/test/TxBuilder.Chain.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.Chain.test.ts @@ -1,69 +1,25 @@ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import { Cardano, Client, preprod } from "@evolution-sdk/evolution" -import * as Address from "@evolution-sdk/evolution/Address" +import { beforeAll, describe, expect, it } from "@effect/vitest" +import { Cardano } from "@evolution-sdk/evolution" import type { SignBuilder } from "@evolution-sdk/evolution/sdk/builders/SignBuilder" import * as TransactionHash from "@evolution-sdk/evolution/TransactionHash" +import { inject } from "vitest" -describe("TxBuilder.chainResult", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let genesisUtxos: ReadonlyArray = [] - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" - const createTestClient = (accountIndex: number = 0) => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1456", ogmiosUrl: "http://localhost:1348" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - } +describe("TxBuilder.chainResult", () => { + let shared: SharedClusterResult beforeAll(async () => { - const tempClient = Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0, addressType: "Base" }) - - const testAddress = await tempClient.address() - const testAddressHex = Address.toHex(testAddress) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { [testAddressHex]: 500_000_000_000 } - } - - genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - devnetCluster = await Cluster.make({ - clusterName: "chain-test", - ports: { node: 6013, submit: 9013 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1456, logLevel: "Info" }, - ogmios: { enabled: true, port: 1348, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 5_000)) - }, 180_000) - - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) + shared = await useSharedCluster(inject("sharedCluster" as any), [3]) + }) it("should chain multiple transactions and submit them all", { timeout: 90_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(3) const address = await client.address() const TX_COUNT = 5 // Build chained transactions using build() + chainResult - let available = [...genesisUtxos] + let available = [...shared.genesisUtxos] const txs: Array = [] for (let i = 0; i < TX_COUNT; i++) { diff --git a/packages/evolution-devnet/test/TxBuilder.Compose.test.ts b/packages/evolution-devnet/test/TxBuilder.Compose.test.ts index 7f078a0a..74951879 100644 --- a/packages/evolution-devnet/test/TxBuilder.Compose.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.Compose.test.ts @@ -5,68 +5,23 @@ * into a single transaction, enabling modular and reusable transaction patterns. */ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import { Cardano, Client, preprod } from "@evolution-sdk/evolution" -import * as Address from "@evolution-sdk/evolution/Address" +import { beforeAll, describe, expect, it } from "@effect/vitest" +import { Cardano } from "@evolution-sdk/evolution" +import { inject } from "vitest" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" // Alias for readability const Time = Cardano.Time describe("TxBuilder compose (Devnet Submit)", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let genesisUtxos: ReadonlyArray = [] - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" - - const createTestClient = (accountIndex: number = 0) => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1458", ogmiosUrl: "http://localhost:1350" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - } + let shared: SharedClusterResult beforeAll(async () => { - const tempClient = Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0, addressType: "Base" }) - - const testAddress = await tempClient.address() - const testAddressHex = Address.toHex(testAddress) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { [testAddressHex]: 500_000_000_000 } - } - - genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - devnetCluster = await Cluster.make({ - clusterName: "compose-test", - ports: { node: 6015, submit: 9015 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1458, logLevel: "Info" }, - ogmios: { enabled: true, port: 1350, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) + shared = await useSharedCluster(inject("sharedCluster" as any), [4, 5]) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) - it("should compose payment with validity constraints", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(4) const myAddress = await client.address() // Create a payment builder @@ -85,7 +40,7 @@ describe("TxBuilder compose (Devnet Submit)", () => { .newTx() .compose(paymentBuilder) .compose(validityBuilder) - .build({ availableUtxos: [...genesisUtxos] }) + .build({ availableUtxos: [shared.getGenesisUtxo(4)] }) const tx = await signBuilder.toTransaction() @@ -103,8 +58,8 @@ describe("TxBuilder compose (Devnet Submit)", () => { }) it("should compose multiple payment builders to different addresses", { timeout: 60_000 }, async () => { - const client1 = createTestClient(0) - const client2 = createTestClient(1) + const client1 = shared.makeClient(4) + const client2 = shared.makeClient(5) const address1 = await client1.address() const address2 = await client2.address() @@ -143,7 +98,7 @@ describe("TxBuilder compose (Devnet Submit)", () => { }) it("should compose builder with addSigner + metadata + payment", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(4) const myAddress = await client.address() // Extract payment credential @@ -190,7 +145,7 @@ describe("TxBuilder compose (Devnet Submit)", () => { }) it("should compose stake registration with payment and metadata", { timeout: 90_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(4) const myAddress = await client.address() // Get stake credential from address @@ -239,7 +194,7 @@ describe("TxBuilder compose (Devnet Submit)", () => { }) it("should verify getPrograms returns accumulated operations", { timeout: 30_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(4) const myAddress = await client.address() // Build a transaction with multiple operations @@ -276,8 +231,8 @@ describe("TxBuilder compose (Devnet Submit)", () => { }) it("should compose builders created from different clients", { timeout: 60_000 }, async () => { - const client1 = createTestClient(0) - const client2 = createTestClient(1) + const client1 = shared.makeClient(4) + const client2 = shared.makeClient(5) const address1 = await client1.address() const address2 = await client2.address() diff --git a/packages/evolution-devnet/test/TxBuilder.Governance.test.ts b/packages/evolution-devnet/test/TxBuilder.Governance.test.ts index e717c763..e9d4e257 100644 --- a/packages/evolution-devnet/test/TxBuilder.Governance.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.Governance.test.ts @@ -3,109 +3,28 @@ * Tests DRep registration, updates, and Constitutional Committee operations. */ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import type { Cardano } from "@evolution-sdk/evolution" -import { Client, preprod } from "@evolution-sdk/evolution" -import * as Address from "@evolution-sdk/evolution/Address" +import { beforeAll, describe, expect, it } from "@effect/vitest" import * as Anchor from "@evolution-sdk/evolution/Anchor" -import * as Bytes from "@evolution-sdk/evolution/Bytes" import * as Bytes32 from "@evolution-sdk/evolution/Bytes32" import * as Credential from "@evolution-sdk/evolution/Credential" import * as KeyHash from "@evolution-sdk/evolution/KeyHash" import * as Url from "@evolution-sdk/evolution/Url" +import { inject } from "vitest" -describe("TxBuilder Governance Operations", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let conwayGenesis: Config.ConwayGenesis - const genesisUtxosByAccount: Map = new Map() - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" - const createTestClient = (accountIndex: number = 0) => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1457", ogmiosUrl: "http://localhost:1349" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - } +describe("TxBuilder Governance Operations", () => { + let shared: SharedClusterResult beforeAll(async () => { - // Create clients for governance tests - const accounts = [0, 1, 2, 3, 4].map((accountIndex) => - Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - ) - - const addresses = await Promise.all(accounts.map((client) => client.address())) - const addressHexes = addresses.map((addr) => Address.toHex(addr)) - - // Extract committee member key hashes from payment credentials - const committeeKeyHash3 = Bytes.toHex(addresses[3].paymentCredential.hash) - const committeeKeyHash4 = Bytes.toHex(addresses[4].paymentCredential.hash) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { - [addressHexes[0]]: 300_000_000_000, - [addressHexes[1]]: 300_000_000_000, - [addressHexes[2]]: 300_000_000_000, - [addressHexes[3]]: 300_000_000_000, - [addressHexes[4]]: 300_000_000_000 - } - } - - conwayGenesis = { - ...Config.DEFAULT_CONWAY_GENESIS, - committee: { - members: { - [`keyHash-${committeeKeyHash3}`]: 1000, - [`keyHash-${committeeKeyHash4}`]: 1000 - }, - threshold: 0.66 - } - } - - const genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - for (let i = 0; i < addresses.length; i++) { - const utxo = genesisUtxos.find((u) => Address.toBech32(u.address) === Address.toBech32(addresses[i])) - if (utxo) genesisUtxosByAccount.set(i, utxo) - } - - devnetCluster = await Cluster.make({ - clusterName: "governance-ops-test", - ports: { node: 6014, submit: 9014 }, - shelleyGenesis: genesisConfig, - conwayGenesis, - kupo: { enabled: true, port: 1457, logLevel: "Info" }, - ogmios: { enabled: true, port: 1349, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) + shared = await useSharedCluster(inject("sharedCluster" as any), [31, 32, 33, 34, 35]) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) - it("registerDRep - registers a DRep with anchor", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 0 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 31 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const drepCredential = walletAddress.paymentCredential @@ -124,13 +43,10 @@ describe("TxBuilder Governance Operations", () => { }) it("updateDRep - updates DRep metadata anchor", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 1 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 32 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const drepCredential = walletAddress.paymentCredential @@ -166,13 +82,10 @@ describe("TxBuilder Governance Operations", () => { }) it("deregisterDRep - deregisters a DRep and reclaims deposit", { timeout: 180_000, retry: 0 }, async () => { - const ACCOUNT_INDEX = 2 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 33 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const drepCredential = walletAddress.paymentCredential @@ -198,13 +111,10 @@ describe("TxBuilder Governance Operations", () => { }) it("authCommitteeHot - authorizes hot credential for committee", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 3 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 34 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const coldCredential = walletAddress.paymentCredential @@ -221,13 +131,10 @@ describe("TxBuilder Governance Operations", () => { }) it("resignCommitteeCold - resigns from constitutional committee", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 4 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 35 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const coldCredential = walletAddress.paymentCredential diff --git a/packages/evolution-devnet/test/TxBuilder.Metadata.test.ts b/packages/evolution-devnet/test/TxBuilder.Metadata.test.ts index 3abea749..e3bf90c4 100644 --- a/packages/evolution-devnet/test/TxBuilder.Metadata.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.Metadata.test.ts @@ -5,67 +5,23 @@ * to the auxiliary data following CIP-10 standard labels. */ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import { Cardano, Client, preprod } from "@evolution-sdk/evolution" -import * as Address from "@evolution-sdk/evolution/Address" +import { beforeAll, describe, expect, it } from "@effect/vitest" +import { Cardano } from "@evolution-sdk/evolution" import * as TransactionHash from "@evolution-sdk/evolution/TransactionHash" import { fromEntries } from "@evolution-sdk/evolution/TransactionMetadatum" +import { inject } from "vitest" -describe("TxBuilder attachMetadata (Devnet Submit)", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let genesisUtxos: ReadonlyArray = [] - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" - const createTestClient = (accountIndex: number = 0) => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1450", ogmiosUrl: "http://localhost:1345" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - } +describe("TxBuilder attachMetadata (Devnet Submit)", () => { + let shared: SharedClusterResult beforeAll(async () => { - const tempClient = Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0, addressType: "Base" }) - - const testAddress = await tempClient.address() - const testAddressHex = Address.toHex(testAddress) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { [testAddressHex]: 500_000_000_000 } - } - - genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - devnetCluster = await Cluster.make({ - clusterName: "metadata-test", - ports: { node: 6008, submit: 9009 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1450, logLevel: "Info" }, - ogmios: { enabled: true, port: 1345, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) - }, 180_000) - - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) + shared = await useSharedCluster(inject("sharedCluster" as any), [6]) + }) it("should attach simple text metadata (CIP-20 message) and submit successfully", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(6) const myAddress = await client.address() const signBuilder = await client @@ -78,7 +34,7 @@ describe("TxBuilder attachMetadata (Devnet Submit)", () => { address: myAddress, assets: Cardano.Assets.fromLovelace(5_000_000n) }) - .build({ availableUtxos: [...genesisUtxos] }) + .build({ availableUtxos: [...shared.genesisUtxos] }) const tx = await signBuilder.toTransaction() @@ -105,7 +61,7 @@ describe("TxBuilder attachMetadata (Devnet Submit)", () => { }) it("should attach multiple metadata entries with different labels", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(6) const myAddress = await client.address() const signBuilder = await client @@ -158,7 +114,7 @@ describe("TxBuilder attachMetadata (Devnet Submit)", () => { }) it("should attach complex NFT-like metadata (CIP-25 style)", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(6) const myAddress = await client.address() // CIP-25 style NFT metadata diff --git a/packages/evolution-devnet/test/TxBuilder.Mint.test.ts b/packages/evolution-devnet/test/TxBuilder.Mint.test.ts index 90ebe9ab..2fc23911 100644 --- a/packages/evolution-devnet/test/TxBuilder.Mint.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.Mint.test.ts @@ -1,8 +1,5 @@ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import { Cardano, Client, preprod } from "@evolution-sdk/evolution" +import { beforeAll, describe, expect, it } from "@effect/vitest" +import { Cardano } from "@evolution-sdk/evolution" import * as CoreAddress from "@evolution-sdk/evolution/Address" import * as AssetName from "@evolution-sdk/evolution/AssetName" import * as NativeScripts from "@evolution-sdk/evolution/NativeScripts" @@ -10,6 +7,8 @@ import * as PolicyId from "@evolution-sdk/evolution/PolicyId" import * as ScriptHash from "@evolution-sdk/evolution/ScriptHash" import * as Text from "@evolution-sdk/evolution/Text" import * as TransactionHash from "@evolution-sdk/evolution/TransactionHash" +import { inject } from "vitest" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" const CoreAssets = Cardano.Assets @@ -18,79 +17,36 @@ describe("TxBuilder Minting (Devnet Submit)", () => { // Devnet Setup // ============================================================================ - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let genesisUtxos: ReadonlyArray = [] + let shared: SharedClusterResult let nativeScript: NativeScripts.NativeScript let policyId: string - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" const ASSET_NAME = "TestToken" - const createTestClient = () => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1443", ogmiosUrl: "http://localhost:1338" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0 }) - } - beforeAll(async () => { - const testClient = Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0 }) - - const testAddress = await testClient.address() - const testAddressHex = CoreAddress.toHex(testAddress) + shared = await useSharedCluster(inject("sharedCluster" as any), [7]) - // Get payment key hash from client's address for native script - const paymentKeyHash = testAddress.paymentCredential.hash + // Derive native script and policy ID from account 7's payment key + const client = shared.makeClient(7) + const address = await client.address() + const paymentKeyHash = address.paymentCredential.hash - // Create native script requiring signature from payment key nativeScript = NativeScripts.makeScriptPubKey(paymentKeyHash) - - // Calculate policy ID from script hash using core module const scriptHash = ScriptHash.fromScript(nativeScript) policyId = ScriptHash.toHex(scriptHash) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { [testAddressHex]: 900_000_000_000 } - } - - // Pre-calculate genesis UTxOs (same pattern as Client.Devnet.test.ts) - genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - devnetCluster = await Cluster.make({ - clusterName: "client-minting-test", - ports: { node: 6001, submit: 9002 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1443, logLevel: "Info" }, - ogmios: { enabled: true, port: 1338, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) - // ============================================================================ // Submit Tests // ============================================================================ it("should mint, submit and find asset in UTxO", { timeout: 30_000 }, async () => { + const genesisUtxos = shared.genesisUtxos if (genesisUtxos.length === 0) { throw new Error("Genesis UTxOs not calculated") } - const client = createTestClient() + const client = shared.makeClient(7) const address = await client.address() // Use pre-calculated genesis UTxOs (Kupo may not have synced yet) @@ -153,11 +109,12 @@ describe("TxBuilder Minting (Devnet Submit)", () => { }) it("should handle burning (negative amounts) with submit", { timeout: 60_000 }, async () => { + const genesisUtxos = shared.genesisUtxos if (genesisUtxos.length === 0) { throw new Error("Genesis UTxOs not calculated") } - const client = createTestClient() + const client = shared.makeClient(7) const address = await client.address() const assetNameHex = Text.toHex(ASSET_NAME) diff --git a/packages/evolution-devnet/test/TxBuilder.NativeScript.test.ts b/packages/evolution-devnet/test/TxBuilder.NativeScript.test.ts index a20123be..988e8be6 100644 --- a/packages/evolution-devnet/test/TxBuilder.NativeScript.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.NativeScript.test.ts @@ -7,83 +7,54 @@ * - Multi-sig native scripts */ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import { Cardano, Client, preprod } from "@evolution-sdk/evolution" +import { beforeAll, describe, expect, it } from "@effect/vitest" +import { Cardano, Client } from "@evolution-sdk/evolution" import * as Address from "@evolution-sdk/evolution/Address" import * as NativeScripts from "@evolution-sdk/evolution/NativeScripts" import * as ScriptHash from "@evolution-sdk/evolution/ScriptHash" import * as Text from "@evolution-sdk/evolution/Text" import * as TransactionHash from "@evolution-sdk/evolution/TransactionHash" import * as UTxO from "@evolution-sdk/evolution/UTxO" +import { inject } from "vitest" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" + +// Slot config type (replaces Cluster.SlotConfig) +type SlotConfig = { + readonly zeroTime: bigint + readonly zeroSlot: bigint + readonly slotLength: number +} -// Time utility functions (duplicated from core since Time module is not externally accessible) +// Time utility functions const now = (): bigint => BigInt(Date.now()) -const unixTimeToSlot = (unixTime: bigint, slotConfig: Cluster.SlotConfig): bigint => { +const unixTimeToSlot = (unixTime: bigint, slotConfig: SlotConfig): bigint => { const timePassed = unixTime - slotConfig.zeroTime const slotsPassed = timePassed / BigInt(slotConfig.slotLength) return slotsPassed + slotConfig.zeroSlot } -const slotToUnixTime = (slot: bigint, slotConfig: Cluster.SlotConfig): bigint => { +const slotToUnixTime = (slot: bigint, slotConfig: SlotConfig): bigint => { const msAfterBegin = (slot - slotConfig.zeroSlot) * BigInt(slotConfig.slotLength) return slotConfig.zeroTime + msAfterBegin } describe("TxBuilder NativeScript (Devnet Submit)", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let genesisUtxos: ReadonlyArray = [] - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" - - const createTestClient = (accountIndex: number = 0) => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1449", ogmiosUrl: "http://localhost:1344" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - } + let shared: SharedClusterResult + let slotConfig: SlotConfig beforeAll(async () => { - const tempClient = Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0, addressType: "Base" }) - - const testAddress = await tempClient.address() - const testAddressHex = Address.toHex(testAddress) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { [testAddressHex]: 500_000_000_000 } + const injectData = inject("sharedCluster" as any) + const info = JSON.parse(injectData) + slotConfig = { + zeroTime: BigInt(info.chain.slotConfig.zeroTime), + zeroSlot: BigInt(info.chain.slotConfig.zeroSlot), + slotLength: info.chain.slotConfig.slotLength } - - genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - devnetCluster = await Cluster.make({ - clusterName: "nativescript-test", - ports: { node: 6007, submit: 9008 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1449, logLevel: "Info" }, - ogmios: { enabled: true, port: 1344, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 5_000)) + shared = await useSharedCluster(injectData, [8, 9, 10]) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) - it("should handle multi-sig native script (all)", { timeout: 60_000 }, async () => { - const client1 = createTestClient(0) - const client2 = createTestClient(1) + const client1 = shared.makeClient(8) + const client2 = shared.makeClient(9) const address1 = await client1.address() const address2 = await client2.address() @@ -116,7 +87,7 @@ describe("TxBuilder NativeScript (Devnet Submit)", () => { address: address1, assets: Cardano.Assets.fromLovelace(2_000_000n) }) - .build({ availableUtxos: [...genesisUtxos] }) + .build({ availableUtxos: [...shared.genesisUtxos] }) const tx = await signBuilder.toTransaction() @@ -143,8 +114,8 @@ describe("TxBuilder NativeScript (Devnet Submit)", () => { }) it("should handle multi-sig native script (any - 1 of N)", { timeout: 60_000 }, async () => { - const client1 = createTestClient(0) - const client2 = createTestClient(1) + const client1 = shared.makeClient(8) + const client2 = shared.makeClient(9) const address1 = await client1.address() const address2 = await client2.address() @@ -196,9 +167,9 @@ describe("TxBuilder NativeScript (Devnet Submit)", () => { }) it("should handle N-of-K native script (2 of 3)", { timeout: 60_000 }, async () => { - const client1 = createTestClient(0) - const client2 = createTestClient(1) - const client3 = createTestClient(2) + const client1 = shared.makeClient(8) + const client2 = shared.makeClient(9) + const client3 = shared.makeClient(10) const address1 = await client1.address() const address2 = await client2.address() @@ -256,9 +227,7 @@ describe("TxBuilder NativeScript (Devnet Submit)", () => { }) it("should handle time-locked native script (invalidHereafter)", { timeout: 60_000 }, async () => { - if (!devnetCluster) throw new Error("Cluster not initialized") - - const client = createTestClient(0) + const client = shared.makeClient(8) const myAddress = await client.address() const paymentCredential = myAddress.paymentCredential @@ -266,8 +235,6 @@ describe("TxBuilder NativeScript (Devnet Submit)", () => { throw new Error("Expected KeyHash credential") } - // Use the same slot config that the client uses - const slotConfig = Cluster.getSlotConfig(devnetCluster) const currentTime = now() const currentSlot = unixTimeToSlot(currentTime, slotConfig) @@ -314,9 +281,7 @@ describe("TxBuilder NativeScript (Devnet Submit)", () => { }) it("should handle complex nested native script (sig AND (any of time conditions))", { timeout: 60_000 }, async () => { - if (!devnetCluster) throw new Error("Cluster not initialized") - - const client = createTestClient(0) + const client = shared.makeClient(8) const myAddress = await client.address() const paymentCredential = myAddress.paymentCredential @@ -324,8 +289,6 @@ describe("TxBuilder NativeScript (Devnet Submit)", () => { throw new Error("Expected KeyHash credential") } - // Use the same slot config that the client uses - const slotConfig = Cluster.getSlotConfig(devnetCluster) const currentTime = now() const currentSlot = unixTimeToSlot(currentTime, slotConfig) @@ -383,8 +346,8 @@ describe("TxBuilder NativeScript (Devnet Submit)", () => { }) it("should spend from a 2-of-2 multi-sig script address", { timeout: 60_000 }, async () => { - const client1 = createTestClient(0) - const client2 = createTestClient(1) + const client1 = shared.makeClient(8) + const client2 = shared.makeClient(9) const address1 = await client1.address() const address2 = await client2.address() @@ -460,8 +423,8 @@ describe("TxBuilder NativeScript (Devnet Submit)", () => { }) it("should use native script as reference script for minting", { timeout: 60_000 }, async () => { - const client1 = createTestClient(0) - const client2 = createTestClient(1) + const client1 = shared.makeClient(8) + const client2 = shared.makeClient(9) const address1 = await client1.address() const address2 = await client2.address() @@ -540,8 +503,8 @@ describe("TxBuilder NativeScript (Devnet Submit)", () => { }) it("should spend from script address using native script as reference input", { timeout: 60_000 }, async () => { - const client1 = createTestClient(0) - const client2 = createTestClient(1) + const client1 = shared.makeClient(8) + const client2 = shared.makeClient(9) const address1 = await client1.address() const address2 = await client2.address() diff --git a/packages/evolution-devnet/test/TxBuilder.PlutusMint.test.ts b/packages/evolution-devnet/test/TxBuilder.PlutusMint.test.ts index 1b6782b0..a2678bb9 100644 --- a/packages/evolution-devnet/test/TxBuilder.PlutusMint.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.PlutusMint.test.ts @@ -6,11 +6,8 @@ * - MintRedeemer: Constr(0, [idx: Int]) where idx == 1 to succeed */ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import { Cardano, Client, preprod } from "@evolution-sdk/evolution" +import { beforeAll, describe, expect, it } from "@effect/vitest" +import { Cardano, Client } from "@evolution-sdk/evolution" import * as CoreAddress from "@evolution-sdk/evolution/Address" import * as AssetName from "@evolution-sdk/evolution/AssetName" import * as Bytes from "@evolution-sdk/evolution/Bytes" @@ -20,6 +17,8 @@ import * as PolicyId from "@evolution-sdk/evolution/PolicyId" import * as ScriptHash from "@evolution-sdk/evolution/ScriptHash" import * as Text from "@evolution-sdk/evolution/Text" import * as TransactionHash from "@evolution-sdk/evolution/TransactionHash" +import { inject } from "vitest" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" const CoreAssets = Cardano.Assets @@ -28,12 +27,7 @@ describe("TxBuilder Plutus Minting (Devnet Submit)", () => { // Devnet Setup // ============================================================================ - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let genesisUtxos: ReadonlyArray = [] - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" + let shared: SharedClusterResult /** * simple_mint.simple_mint.mint validator from plutus.json @@ -63,70 +57,24 @@ describe("TxBuilder Plutus Minting (Devnet Submit)", () => { const scriptHash = ScriptHash.fromScript(simpleMintScript) const calculatedPolicyId = ScriptHash.toHex(scriptHash) - const createTestClient = () => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1444", ogmiosUrl: "http://localhost:1339" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0 }) - } - beforeAll(async () => { // Verify our script hash calculation matches the blueprint expect(calculatedPolicyId).toBe(SIMPLE_MINT_POLICY_ID_HEX) - const testClient = Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0 }) - - const testAddress = await testClient.address() - const testAddressHex = CoreAddress.toHex(testAddress) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { [testAddressHex]: 900_000_000_000 } - } - - // Pre-calculate genesis UTxOs - genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - devnetCluster = await Cluster.make({ - clusterName: "plutus-minting-test", - ports: { node: 6002, submit: 9003 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1444, logLevel: "Info" }, - ogmios: { enabled: true, port: 1339, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) + shared = await useSharedCluster(inject("sharedCluster" as any), [11]) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) - // ============================================================================ // Submit Tests // ============================================================================ it("should mint tokens with PlutusV3 simple_mint script", { timeout: 60_000 }, async () => { - if (genesisUtxos.length === 0) { - throw new Error("Genesis UTxOs not calculated") - } + const genesisUtxo = shared.getGenesisUtxo(11) - const client = createTestClient() + const client = shared.makeClient(11) const address = await client.address() // Use pre-calculated genesis UTxOs (Kupo may not have synced yet) - const genesisUtxo = genesisUtxos.find((u) => CoreAddress.toBech32(u.address) === CoreAddress.toBech32(address)) - if (!genesisUtxo) { - throw new Error("Genesis UTxO not found for wallet address") - } - const assetNameHex = Text.toHex("PlutusToken") const unit = SIMPLE_MINT_POLICY_ID_HEX + assetNameHex @@ -199,7 +147,7 @@ describe("TxBuilder Plutus Minting (Devnet Submit)", () => { }) it("should mint then burn tokens with PlutusV3 simple_mint script", { timeout: 60_000 }, async () => { - const client = createTestClient() + const client = shared.makeClient(11) const address = await client.address() const assetNameHex = Text.toHex("BurnToken") @@ -209,11 +157,7 @@ describe("TxBuilder Plutus Minting (Devnet Submit)", () => { let availableUtxos = await client.getWalletUtxos() if (availableUtxos.length === 0) { // Fall back to genesis UTxOs if Kupo hasn't synced - const genesisUtxo = genesisUtxos.find((u) => CoreAddress.toBech32(u.address) === CoreAddress.toBech32(address)) - if (!genesisUtxo) { - throw new Error("Genesis UTxO not found for wallet address") - } - availableUtxos = [genesisUtxo] + availableUtxos = [shared.getGenesisUtxo(11)] } // Step 1: First mint tokens @@ -242,7 +186,7 @@ describe("TxBuilder Plutus Minting (Devnet Submit)", () => { const mintSubmitBuilder = await mintBuilder.sign() const mintTxHash = await mintSubmitBuilder.submit() // eslint-disable-next-line no-console - console.log(`✓ Submitted Plutus mint tx (for burn test): ${mintTxHash}`) + console.log(`Submitted Plutus mint tx (for burn test): ${mintTxHash}`) const mintConfirmed = await client.awaitTx(mintTxHash, 1000) expect(mintConfirmed).toBe(true) @@ -301,7 +245,7 @@ describe("TxBuilder Plutus Minting (Devnet Submit)", () => { expect(TransactionHash.toHex(burnTxHash).length).toBe(64) // eslint-disable-next-line no-console - console.log(`✓ Submitted Plutus burn tx: ${burnTxHash}`) + console.log(`Submitted Plutus burn tx: ${burnTxHash}`) const burnConfirmed = await client.awaitTx(burnTxHash, 1000) expect(burnConfirmed).toBe(true) diff --git a/packages/evolution-devnet/test/TxBuilder.Pool.test.ts b/packages/evolution-devnet/test/TxBuilder.Pool.test.ts index b1d63eb6..4519a484 100644 --- a/packages/evolution-devnet/test/TxBuilder.Pool.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.Pool.test.ts @@ -1,6 +1,9 @@ /** * Devnet tests for TxBuilder pool operations. * Tests stake pool registration and retirement. + * + * Uses a dedicated cluster (not the shared cluster) because the retirePool + * test requires Genesis.queryCurrentEpoch which needs a Cluster object. */ import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" @@ -8,7 +11,6 @@ import * as Cluster from "@evolution-sdk/devnet/Cluster" import * as Config from "@evolution-sdk/devnet/Config" import * as Genesis from "@evolution-sdk/devnet/Genesis" import type { Cardano } from "@evolution-sdk/evolution" -import { Client, preprod } from "@evolution-sdk/evolution" import * as Address from "@evolution-sdk/evolution/Address" import * as Bytes32 from "@evolution-sdk/evolution/Bytes32" import type * as EpochNo from "@evolution-sdk/evolution/EpochNo" @@ -22,81 +24,42 @@ import * as SingleHostAddr from "@evolution-sdk/evolution/SingleHostAddr" import * as UnitInterval from "@evolution-sdk/evolution/UnitInterval" import * as Url from "@evolution-sdk/evolution/Url" import * as VrfKeyHash from "@evolution-sdk/evolution/VrfKeyHash" +import { + type ClusterSetupResult, + setupCluster, + teardownCluster +} from "./utils/shared-cluster.js" describe("TxBuilder Pool Operations", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - const genesisUtxosByAccount: Map = new Map() - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" - - const createTestClient = (accountIndex: number = 0) => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1453", ogmiosUrl: "http://localhost:1343" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - } + let setup: ClusterSetupResult beforeAll(async () => { - // Create clients for pool tests - const accounts = [0, 1].map((accountIndex) => - Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - ) - - const addresses = await Promise.all(accounts.map((client) => client.address())) - const addressHexes = addresses.map((addr) => Address.toHex(addr)) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { - [addressHexes[0]]: 1_000_000_000_000, - [addressHexes[1]]: 1_000_000_000_000 - }, - protocolParams: { - ...Config.DEFAULT_SHELLEY_GENESIS.protocolParams, - keyDeposit: 2_000_000, - poolDeposit: 500_000_000 - } - } - - const genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - for (let i = 0; i < addresses.length; i++) { - const utxo = genesisUtxos.find((u) => Address.toBech32(u.address) === Address.toBech32(addresses[i])) - if (utxo) genesisUtxosByAccount.set(i, utxo) - } - - devnetCluster = await Cluster.make({ + setup = await setupCluster({ clusterName: "pool-ops-test", - ports: { node: 6006, submit: 9007 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1453, logLevel: "Info" }, - ogmios: { enabled: true, port: 1343, logLevel: "info" } + accountIndices: [12, 13], + ports: { node: 6006, submit: 9007, kupo: 1453, ogmios: 1343 }, + shelleyGenesisOverrides: { + protocolParams: { + ...Config.DEFAULT_SHELLEY_GENESIS.protocolParams, + keyDeposit: 2_000_000, + poolDeposit: 500_000_000 + } + } }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) }, 180_000) afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } + await teardownCluster(setup?.cluster) }, 60_000) it("registerPool - registers a new stake pool", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 0 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) + const ACCOUNT_INDEX = 12 + const genesisUtxo = setup.genesisUtxosByAccount.get(ACCOUNT_INDEX) if (!genesisUtxo) { throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) } - const client = createTestClient(ACCOUNT_INDEX) + const client = setup.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const poolKeyHash = @@ -152,13 +115,13 @@ describe("TxBuilder Pool Operations", () => { }) it("retirePool - retires a stake pool", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 1 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) + const ACCOUNT_INDEX = 13 + const genesisUtxo = setup.genesisUtxosByAccount.get(ACCOUNT_INDEX) if (!genesisUtxo) { throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) } - const client = createTestClient(ACCOUNT_INDEX) + const client = setup.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const poolKeyHash = @@ -212,7 +175,7 @@ describe("TxBuilder Pool Operations", () => { await new Promise((resolve) => setTimeout(resolve, 2000)) // Query current epoch and retire in future epoch - const currentEpoch = await Genesis.queryCurrentEpoch(devnetCluster!) + const currentEpoch = await Genesis.queryCurrentEpoch(setup.cluster) const retirementEpoch: EpochNo.EpochNo = currentEpoch + 5n const retireTxHash = await client .newTx() diff --git a/packages/evolution-devnet/test/TxBuilder.RedeemerBuilder.test.ts b/packages/evolution-devnet/test/TxBuilder.RedeemerBuilder.test.ts index 74bb81dd..4a7299cf 100644 --- a/packages/evolution-devnet/test/TxBuilder.RedeemerBuilder.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.RedeemerBuilder.test.ts @@ -5,11 +5,8 @@ * This requires reading the datum from each UTxO and combining it with the resolved index. */ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import { Cardano, Client, preprod } from "@evolution-sdk/evolution" +import { beforeAll, describe, expect, it } from "@effect/vitest" +import { Cardano, Client } from "@evolution-sdk/evolution" import * as CoreAddress from "@evolution-sdk/evolution/Address" import * as AssetName from "@evolution-sdk/evolution/AssetName" import * as Bytes from "@evolution-sdk/evolution/Bytes" @@ -21,6 +18,8 @@ import * as ScriptHash from "@evolution-sdk/evolution/ScriptHash" import * as Text from "@evolution-sdk/evolution/Text" import * as TransactionHash from "@evolution-sdk/evolution/TransactionHash" import { Schema } from "effect" +import { inject } from "vitest" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" import plutusJson from "../../evolution/test/spec/plutus.json" @@ -42,12 +41,7 @@ const getMintMultiValidator = () => { const { compiledCode: MINT_MULTI_COMPILED_CODE, hash: MINT_MULTI_POLICY_ID_HEX } = getMintMultiValidator() describe("TxBuilder RedeemerBuilder", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let genesisUtxos: ReadonlyArray = [] - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" + let shared: SharedClusterResult /** SpendRedeemer: Constr(0, [value: Int]) where value = datum.counter + input_index */ const makeSpendRedeemer = (value: bigint): Data.Data => Data.constr(0n, [Data.int(value)]) @@ -82,68 +76,19 @@ describe("TxBuilder RedeemerBuilder", () => { const scriptHashValue = ScriptHash.fromScript(mintMultiScript) const calculatedPolicyId = ScriptHash.toHex(scriptHashValue) - const createTestClient = () => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1445", ogmiosUrl: "http://localhost:1340" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0 }) - } - beforeAll(async () => { // Verify our script hash calculation matches the blueprint expect(calculatedPolicyId).toBe(MINT_MULTI_POLICY_ID_HEX) - const testClient = Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0 }) - - const testAddress = await testClient.address() - const testAddressHex = CoreAddress.toHex(testAddress) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { [testAddressHex]: 900_000_000_000 } - } - - // Pre-calculate genesis UTxOs - genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - devnetCluster = await Cluster.make({ - clusterName: "redeemer-builder-test", - ports: { node: 6003, submit: 9004 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1445, logLevel: "Info" }, - ogmios: { enabled: true, port: 1340, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) + shared = await useSharedCluster(inject("sharedCluster" as any), [14]) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) - it("resolves redeemers using datum + index from multiple script UTxOs", { timeout: 120_000 }, async () => { - if (genesisUtxos.length === 0) { - throw new Error("Genesis UTxOs not calculated") - } + const genesisUtxo = shared.getGenesisUtxo(14) - const client = createTestClient() + const client = shared.makeClient(14) const walletAddress = await client.address() - // Use pre-calculated genesis UTxOs - const genesisUtxo = genesisUtxos.find( - (u) => CoreAddress.toBech32(u.address) === CoreAddress.toBech32(walletAddress) - ) - if (!genesisUtxo) { - throw new Error("Genesis UTxO not found for wallet address") - } - // Use unique token name with timestamp to avoid UTxO accumulation from retries const timestamp = Date.now().toString(36) const assetNameHex = Text.toHex(`Batch${timestamp}`) diff --git a/packages/evolution-devnet/test/TxBuilder.ScriptStake.test.ts b/packages/evolution-devnet/test/TxBuilder.ScriptStake.test.ts index adc4b8ac..209f5768 100644 --- a/packages/evolution-devnet/test/TxBuilder.ScriptStake.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.ScriptStake.test.ts @@ -13,19 +13,19 @@ * 4. Deregister the script stake credential (publish) */ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import { Cardano, Client, preprod } from "@evolution-sdk/evolution" +import { beforeAll, describe, expect, it } from "@effect/vitest" +import { Cardano } from "@evolution-sdk/evolution" import * as CoreAddress from "@evolution-sdk/evolution/Address" import * as Bytes from "@evolution-sdk/evolution/Bytes" import * as Data from "@evolution-sdk/evolution/Data" import * as InlineDatum from "@evolution-sdk/evolution/InlineDatum" import * as PlutusV3 from "@evolution-sdk/evolution/PlutusV3" import * as ScriptHash from "@evolution-sdk/evolution/ScriptHash" +import * as TransactionHash from "@evolution-sdk/evolution/TransactionHash" +import { inject } from "vitest" import plutusJson from "../../evolution/test/spec/plutus.json" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" const getStakeMultiValidator = () => { const validator = plutusJson.validators.find((v) => v.title === "stake_multivalidator.stake_multivalidator.spend") @@ -43,12 +43,7 @@ const getStakeMultiValidator = () => { const { compiledCode: STAKE_MULTI_COMPILED_CODE, hash: STAKE_MULTI_SCRIPT_HASH } = getStakeMultiValidator() describe("TxBuilder Script Stake Operations", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let genesisUtxos: ReadonlyArray = [] - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" + let shared: SharedClusterResult // Redeemer types for the stake_multivalidator /** PublishRedeemer: Constr(0, [placeholder: Int]) */ @@ -80,59 +75,17 @@ describe("TxBuilder Script Stake Operations", () => { }) } - const createTestClient = (accountIndex: number = 0) => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1447", ogmiosUrl: "http://localhost:1342" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - } - beforeAll(async () => { // Verify our script hash calculation matches the blueprint expect(calculatedScriptHash).toBe(STAKE_MULTI_SCRIPT_HASH) - const testClient = Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0, addressType: "Base" }) - - const testAddress = await testClient.address() - const testAddressHex = CoreAddress.toHex(testAddress) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { [testAddressHex]: 500_000_000_000 } - } - - // Pre-calculate genesis UTxOs - genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - devnetCluster = await Cluster.make({ - clusterName: "script-stake-test", - ports: { node: 6005, submit: 9006 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1447, logLevel: "Info" }, - ogmios: { enabled: true, port: 1342, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) + shared = await useSharedCluster(inject("sharedCluster" as any), [36]) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) - it("runs full script stake coordination pattern", { timeout: 180_000 }, async () => { - if (genesisUtxos.length === 0) { - throw new Error("Genesis UTxOs not calculated") - } - - const client = createTestClient(0) + const client = shared.makeClient(36) const scriptPaymentAddress = getScriptPaymentAddress() + const genesisUtxo = shared.getGenesisUtxo(36) // Step 1: Register the script stake credential @@ -145,7 +98,7 @@ describe("TxBuilder Script Stake Operations", () => { redeemer: makePublishRedeemer(0n) }) .attachScript({ script: stakeScript }) - .build({ availableUtxos: [...genesisUtxos] }) + .build({ availableUtxos: [genesisUtxo] }) const registerSubmitBuilder = await registerSignBuilder.sign() registerTxHash = await registerSubmitBuilder.submit() @@ -188,7 +141,13 @@ describe("TxBuilder Script Stake Operations", () => { await new Promise((resolve) => setTimeout(resolve, 2000)) // Step 3: Coordination transaction (spend + withdraw) - const scriptUtxos = await client.getUtxos(scriptPaymentAddress) + const allScriptUtxos = await client.getUtxos(scriptPaymentAddress) + + // Filter by fundTxHash to isolate our UTxOs from other test runs + const fundTxHashHex = TransactionHash.toHex(fundTxHash) + const scriptUtxos = allScriptUtxos.filter( + (u) => TransactionHash.toHex(u.transactionId) === fundTxHashHex + ) expect(scriptUtxos.length).toBeGreaterThanOrEqual(2) const utxosToSpend = scriptUtxos.slice(0, 2) @@ -256,11 +215,7 @@ describe("TxBuilder Script Stake Operations", () => { }) it("captures script failure with labeled redeemers", { timeout: 180_000 }, async () => { - if (genesisUtxos.length === 0) { - throw new Error("Genesis UTxOs not calculated") - } - - const client = createTestClient(0) + const client = shared.makeClient(36) const scriptPaymentAddress = getScriptPaymentAddress() // Ensure stake credential is registered @@ -287,11 +242,16 @@ describe("TxBuilder Script Stake Operations", () => { datum: unitDatum }) .build() - await client.awaitTx(await (await fundSignBuilder.sign()).submit(), 1000) + const fundTxHash = await (await fundSignBuilder.sign()).submit() + await client.awaitTx(fundTxHash, 1000) await new Promise((resolve) => setTimeout(resolve, 2000)) - // Get script UTxOs - const scriptUtxos = await client.getUtxos(scriptPaymentAddress) + // Get script UTxOs - filter by fundTxHash + const allScriptUtxos = await client.getUtxos(scriptPaymentAddress) + const fundTxHashHex = TransactionHash.toHex(fundTxHash) + const scriptUtxos = allScriptUtxos.filter( + (u) => TransactionHash.toHex(u.transactionId) === fundTxHashHex + ) expect(scriptUtxos.length).toBeGreaterThan(0) const utxoToSpend = scriptUtxos[0]! diff --git a/packages/evolution-devnet/test/TxBuilder.Scripts.test.ts b/packages/evolution-devnet/test/TxBuilder.Scripts.test.ts index ecd561a9..00ff2c61 100644 --- a/packages/evolution-devnet/test/TxBuilder.Scripts.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.Scripts.test.ts @@ -1,4 +1,4 @@ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" +import { beforeAll, describe, expect, it } from "@effect/vitest" import { createAikenEvaluator } from "@evolution-sdk/aiken-uplc" import { Cardano } from "@evolution-sdk/evolution" import * as CoreAddress from "@evolution-sdk/evolution/Address" @@ -13,9 +13,10 @@ import { makeTxBuilder } from "@evolution-sdk/evolution/sdk/builders/Transaction import { KupmiosProvider } from "@evolution-sdk/evolution/sdk/provider/Kupmios" import { createScalusEvaluator } from "@evolution-sdk/scalus-uplc" import { Schema } from "effect" +import { inject } from "vitest" import plutusJson from "../../evolution/test/spec/plutus.json" -import * as Cluster from "../src/Cluster.js" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" import { createCoreTestUtxo } from "./utils/utxo-helpers.js" // Alias for Cardano.Assets @@ -23,70 +24,27 @@ const CoreAssets = Cardano.Assets describe("TxBuilder Script Handling", () => { // ============================================================================ - // Devnet Setup (Ogmios for script evaluation) + // Shared Cluster Setup (Ogmios for script evaluation) // ============================================================================ - let devnetCluster: Cluster.Cluster | undefined + let shared: SharedClusterResult let kupmiosProvider: KupmiosProvider beforeAll(async () => { - try { - devnetCluster = await Cluster.make({ - clusterName: "txbuilder-plutus-script-eval", - ports: { - node: 5001, - submit: 9001 - }, - shelleyGenesis: { - slotLength: 0.02, // 20ms per slot (fast) - epochLength: 50, - activeSlotsCoeff: 1.0 - }, - ogmios: { - enabled: true, - port: 1337, - logLevel: "info" - } - }) - - await Cluster.start(devnetCluster) + shared = await useSharedCluster(inject("sharedCluster" as any), [15]) - // Wait for Ogmios to be ready - await new Promise((resolve) => setTimeout(resolve, 2_000)) + // Parse cluster info to get ogmios port + const info = JSON.parse(inject("sharedCluster" as any)) + const ogmiosUrl = `http://localhost:${info.ports.ogmios}` - // Ogmios serves both HTTP (for JSON-RPC) and WebSocket on the same port - const ogmiosUrl = "http://localhost:1337" - - // Create provider using local Ogmios - // Note: Kupo URL is required but not used in these tests (only Ogmios for evaluation) - kupmiosProvider = new KupmiosProvider( - "http://localhost:1442", // Kupo (not used) - ogmiosUrl // Ogmios for script evaluation via HTTP - ) - - // eslint-disable-next-line no-console - console.log(`✓ Devnet ready - Ogmios: ${ogmiosUrl}`) - } catch (error) { - // eslint-disable-next-line no-console - console.error("Failed to start devnet:", error) - throw error - } + // Create provider using shared cluster Ogmios + // Note: Kupo URL is required but not used in these tests (only Ogmios for evaluation) + kupmiosProvider = new KupmiosProvider( + `http://localhost:${info.ports.kupo}`, // Kupo (not used in script eval tests) + ogmiosUrl // Ogmios for script evaluation via HTTP + ) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - try { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - // eslint-disable-next-line no-console - console.log("✓ Devnet stopped") - } catch (error) { - // eslint-disable-next-line no-console - console.error("Failed to stop devnet:", error) - } - } - }, 60_000) - // ============================================================================ // Test Configuration // ============================================================================ @@ -106,10 +64,21 @@ describe("TxBuilder Script Handling", () => { const CHANGE_ADDRESS = TESTNET_ADDRESSES[0] const RECEIVER_ADDRESS = TESTNET_ADDRESSES[1] - // baseConfig will use kupmiosProvider and devnetCluster which are set in beforeAll + // baseConfig will use kupmiosProvider and shared cluster chain const baseConfig: TxBuilderConfig = { get chain() { - return Cluster.getChain(devnetCluster!) + const info = JSON.parse(inject("sharedCluster" as any)) + return { + id: info.chain.id, + name: info.chain.name, + networkMagic: info.chain.networkMagic, + epochLength: info.chain.epochLength, + slotConfig: { + zeroTime: BigInt(info.chain.slotConfig.zeroTime), + zeroSlot: BigInt(info.chain.slotConfig.zeroSlot), + slotLength: info.chain.slotConfig.slotLength + } + } }, get provider() { return kupmiosProvider diff --git a/packages/evolution-devnet/test/TxBuilder.SpendScriptRef.test.ts b/packages/evolution-devnet/test/TxBuilder.SpendScriptRef.test.ts index 6c15644e..3616d3e9 100644 --- a/packages/evolution-devnet/test/TxBuilder.SpendScriptRef.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.SpendScriptRef.test.ts @@ -3,27 +3,23 @@ * using only collectFrom() — no attachScript() or readFrom(). */ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import { Cardano, Client, preprod } from "@evolution-sdk/evolution" +import { beforeAll, describe, expect, it } from "@effect/vitest" +import { Cardano } from "@evolution-sdk/evolution" import * as CoreAddress from "@evolution-sdk/evolution/Address" import * as Bytes from "@evolution-sdk/evolution/Bytes" import * as Data from "@evolution-sdk/evolution/Data" import * as InlineDatum from "@evolution-sdk/evolution/InlineDatum" import * as PlutusV3 from "@evolution-sdk/evolution/PlutusV3" import * as ScriptHash from "@evolution-sdk/evolution/ScriptHash" +import * as TransactionHash from "@evolution-sdk/evolution/TransactionHash" +import { inject } from "vitest" + +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" const CoreAssets = Cardano.Assets describe("TxBuilder Spend ScriptRef (Devnet Submit)", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let genesisUtxos: ReadonlyArray = [] - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" + let shared: SharedClusterResult const ALWAYS_SUCCEED_COMPILED_CODE = "587e01010029800aba2aba1aab9eaab9dab9cab9a48888896600264653001300700198039804000cc01c0092225980099b8748008c020dd500144c8cc892898058009805980600098049baa0028a50401830070013004375400f149a2a660049211856616c696461746f722072657475726e65642066616c7365001365640041" @@ -36,64 +32,21 @@ describe("TxBuilder Spend ScriptRef (Devnet Submit)", () => { const makeScriptAddress = (): CoreAddress.Address => CoreAddress.Address.make({ networkId: 0, paymentCredential: alwaysSucceedScriptHash }) - const createTestClient = () => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1454", ogmiosUrl: "http://localhost:1346" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0 }) - } - beforeAll(async () => { expect(ScriptHash.toHex(alwaysSucceedScriptHash)).toBe(ALWAYS_SUCCEED_HASH) - const testClient = Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0 }) - - const testAddress = await testClient.address() - const testAddressHex = CoreAddress.toHex(testAddress) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { [testAddressHex]: 900_000_000_000 } - } - - genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - devnetCluster = await Cluster.make({ - clusterName: "spend-scriptref-test", - ports: { node: 6011, submit: 9011 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1454, logLevel: "Info" }, - ogmios: { enabled: true, port: 1346, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) + shared = await useSharedCluster(inject("sharedCluster" as any), [16]) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) - it( "should submit a tx spending from a script UTxO with inline scriptRef", { timeout: 120_000 }, async () => { - if (genesisUtxos.length === 0) throw new Error("Genesis UTxOs not calculated") - - const client = createTestClient() + const client = shared.makeClient(16) const walletAddress = await client.address() const scriptAddress = makeScriptAddress() - const genesisUtxo = genesisUtxos.find( - (u) => CoreAddress.toBech32(u.address) === CoreAddress.toBech32(walletAddress) - ) - if (!genesisUtxo) throw new Error("Genesis UTxO not found for wallet address") + const genesisUtxo = shared.getGenesisUtxo(16) // Phase 1: Deploy — pay to script address with inline datum + scriptRef const deploySignBuilder = await client @@ -116,7 +69,12 @@ describe("TxBuilder Spend ScriptRef (Devnet Submit)", () => { const scriptUtxos = await client.getUtxos(scriptAddress) expect(scriptUtxos.length).toBeGreaterThan(0) - const scriptUtxo = scriptUtxos[0]! + // Filter by deploy tx hash to isolate our UTxO from other test runs + const deployTxHashHex = TransactionHash.toHex(deployTxHash) + const scriptUtxo = scriptUtxos.find( + (u) => TransactionHash.toHex(u.transactionId) === deployTxHashHex + ) + if (!scriptUtxo) throw new Error("Script UTxO from deploy tx not found") expect(scriptUtxo.scriptRef?._tag).toBe("PlutusV3") const spendSignBuilder = await client diff --git a/packages/evolution-devnet/test/TxBuilder.Stake.test.ts b/packages/evolution-devnet/test/TxBuilder.Stake.test.ts index 69fd9ca2..a6b231fc 100644 --- a/packages/evolution-devnet/test/TxBuilder.Stake.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.Stake.test.ts @@ -11,99 +11,28 @@ * using simple key credentials that don't require script witnesses. */ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import type { Cardano } from "@evolution-sdk/evolution" -import { Client, preprod } from "@evolution-sdk/evolution" -import * as Address from "@evolution-sdk/evolution/Address" +import { beforeAll, describe, expect, it } from "@effect/vitest" import * as DRep from "@evolution-sdk/evolution/DRep" import * as PoolKeyHash from "@evolution-sdk/evolution/PoolKeyHash" +import { inject } from "vitest" + +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" // Default devnet stake pool ID from Config.ts const DEVNET_POOL_ID = "8a219b698d3b6e034391ae84cee62f1d76b6fbc45ddfe4e31e0d4b60" describe("TxBuilder Stake Operations", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - // Store genesis UTxOs per account index for independent tests - const genesisUtxosByAccount: Map = new Map() - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" - - // Create client for a specific account index (each test uses different account) - const createTestClient = (accountIndex: number = 0) => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1446", ogmiosUrl: "http://localhost:1341" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - } + let shared: SharedClusterResult beforeAll(async () => { - // Create clients for each account we'll use in tests - const accounts = [0, 1, 2, 3, 4, 5, 6, 7, 8].map((accountIndex) => - Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - ) - - const addresses = await Promise.all(accounts.map((client) => client.address())) - const addressHexes = addresses.map((addr) => Address.toHex(addr)) - - // Fund each account independently so tests don't share UTxOs - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { - [addressHexes[0]]: 300_000_000_000, // Test 1: Full flow (register + delegate separately) - [addressHexes[1]]: 300_000_000_000, // Test 2: Pool-only delegation (StakeDelegation) - [addressHexes[2]]: 300_000_000_000, // Test 3: DRep-only delegation (VoteDelegCert) - [addressHexes[3]]: 300_000_000_000, // Test 4: Combined register+delegate pool (StakeRegDelegCert) - [addressHexes[4]]: 300_000_000_000, // Test 5: Combined register+delegate DRep (VoteRegDelegCert) - [addressHexes[5]]: 300_000_000_000, // Test 6: Combined register+delegate both (StakeVoteRegDelegCert) - [addressHexes[6]]: 300_000_000_000, // Test 7: NEW API - delegateToPool - [addressHexes[7]]: 300_000_000_000, // Test 8: NEW API - delegateToDRep - [addressHexes[8]]: 300_000_000_000 // Test 9: NEW API - delegateToPoolAndDRep - } - } - - // Pre-calculate genesis UTxOs and map by account - const genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - for (let i = 0; i < addresses.length; i++) { - const utxo = genesisUtxos.find((u) => Address.toBech32(u.address) === Address.toBech32(addresses[i])) - if (utxo) genesisUtxosByAccount.set(i, utxo) - } - - devnetCluster = await Cluster.make({ - clusterName: "stake-ops-test", - ports: { node: 6004, submit: 9005 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1446, logLevel: "Info" }, - ogmios: { enabled: true, port: 1341, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) + shared = await useSharedCluster(inject("sharedCluster" as any), [17, 18, 19, 20, 21, 22, 23, 24, 25]) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) - it("registers, delegates, withdraws, and deregisters (key credential)", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 0 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 17 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() // Extract stake credential from wallet address @@ -162,13 +91,10 @@ describe("TxBuilder Stake Operations", () => { }) it("delegates to pool only (StakeDelegation)", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 1 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 18 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const addressStruct = walletAddress @@ -212,13 +138,10 @@ describe("TxBuilder Stake Operations", () => { }) it("delegates to DRep only (VoteDelegCert)", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 2 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 19 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const addressStruct = walletAddress @@ -262,13 +185,10 @@ describe("TxBuilder Stake Operations", () => { }) it("registers and delegates to pool in one cert (StakeRegDelegCert)", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 3 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 20 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const addressStruct = walletAddress @@ -301,13 +221,10 @@ describe("TxBuilder Stake Operations", () => { }) it("registers and delegates to DRep in one cert (VoteRegDelegCert)", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 4 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 21 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const addressStruct = walletAddress @@ -343,13 +260,10 @@ describe("TxBuilder Stake Operations", () => { "registers and delegates to both pool+DRep in one cert (StakeVoteRegDelegCert)", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 5 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 22 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const addressStruct = walletAddress @@ -388,13 +302,10 @@ describe("TxBuilder Stake Operations", () => { // ============================================================================ it("NEW API: delegateToPool - delegates stake to pool only", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 6 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 23 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const addressStruct = walletAddress @@ -438,13 +349,10 @@ describe("TxBuilder Stake Operations", () => { }) it("NEW API: delegateToDRep - delegates voting power to DRep only", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 7 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 24 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const addressStruct = walletAddress @@ -488,13 +396,10 @@ describe("TxBuilder Stake Operations", () => { }) it("NEW API: delegateToPoolAndDRep - delegates both stake and voting power", { timeout: 180_000 }, async () => { - const ACCOUNT_INDEX = 8 - const genesisUtxo = genesisUtxosByAccount.get(ACCOUNT_INDEX) - if (!genesisUtxo) { - throw new Error(`Genesis UTxO not found for account ${ACCOUNT_INDEX}`) - } + const ACCOUNT_INDEX = 25 + const genesisUtxo = shared.getGenesisUtxo(ACCOUNT_INDEX) - const client = createTestClient(ACCOUNT_INDEX) + const client = shared.makeClient(ACCOUNT_INDEX) const walletAddress = await client.address() const addressStruct = walletAddress diff --git a/packages/evolution-devnet/test/TxBuilder.Validity.test.ts b/packages/evolution-devnet/test/TxBuilder.Validity.test.ts index a59d8fcc..5b79be53 100644 --- a/packages/evolution-devnet/test/TxBuilder.Validity.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.Validity.test.ts @@ -11,70 +11,24 @@ * 3. Verify expired transaction is rejected */ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import { Cardano, Client, preprod } from "@evolution-sdk/evolution" -import * as Address from "@evolution-sdk/evolution/Address" +import { beforeAll, describe, expect, it } from "@effect/vitest" +import { Cardano } from "@evolution-sdk/evolution" +import { inject } from "vitest" + +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" // Alias for readability const Time = Cardano.Time describe("TxBuilder Validity Interval", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let genesisUtxos: ReadonlyArray = [] - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" - - // Creates a client with correct slot config for devnet - const createTestClient = (accountIndex: number = 0) => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1451", ogmiosUrl: "http://localhost:1351" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - } + let shared: SharedClusterResult beforeAll(async () => { - // Create a minimal client just to get the address (before cluster is ready) - const tempClient = Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex: 0, addressType: "Base" }) - - const testAddress = await tempClient.address() - const testAddressHex = Address.toHex(testAddress) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { [testAddressHex]: 500_000_000_000 } - } - - genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - devnetCluster = await Cluster.make({ - clusterName: "validity-test", - ports: { node: 6009, submit: 9016 }, - shelleyGenesis: genesisConfig, - kupo: { enabled: true, port: 1451, logLevel: "Info" }, - ogmios: { enabled: true, port: 1351, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) - }, 180_000) - - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) + shared = await useSharedCluster(inject("sharedCluster" as any), [26]) + }) it("should build and submit transaction with TTL", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(26) const myAddress = await client.address() // Set TTL to 5 minutes from now @@ -87,7 +41,7 @@ describe("TxBuilder Validity Interval", () => { address: myAddress, assets: Cardano.Assets.fromLovelace(5_000_000n) }) - .build({ availableUtxos: [...genesisUtxos] }) + .build({ availableUtxos: [...shared.genesisUtxos] }) const tx = await signBuilder.toTransaction() @@ -107,7 +61,7 @@ describe("TxBuilder Validity Interval", () => { }) it("should build and submit transaction with both validity bounds", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(26) const myAddress = await client.address() // Valid from now until 5 minutes from now @@ -147,7 +101,7 @@ describe("TxBuilder Validity Interval", () => { }) it("should reject expired transaction", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(26) const myAddress = await client.address() // Set TTL to 1 second ago (already expired) @@ -160,7 +114,7 @@ describe("TxBuilder Validity Interval", () => { address: myAddress, assets: Cardano.Assets.fromLovelace(5_000_000n) }) - .build({ availableUtxos: [...genesisUtxos] }) + .build({ availableUtxos: [...shared.genesisUtxos] }) const submitBuilder = await signBuilder.sign() @@ -169,7 +123,7 @@ describe("TxBuilder Validity Interval", () => { }) it("should reject transaction before validity start", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(26) const myAddress = await client.address() // Valid starting 5 minutes from now (not valid yet) @@ -183,7 +137,7 @@ describe("TxBuilder Validity Interval", () => { address: myAddress, assets: Cardano.Assets.fromLovelace(5_000_000n) }) - .build({ availableUtxos: [...genesisUtxos] }) + .build({ availableUtxos: [...shared.genesisUtxos] }) const submitBuilder = await signBuilder.sign() diff --git a/packages/evolution-devnet/test/TxBuilder.Vote.test.ts b/packages/evolution-devnet/test/TxBuilder.Vote.test.ts index 14bbce6c..88fd2538 100644 --- a/packages/evolution-devnet/test/TxBuilder.Vote.test.ts +++ b/packages/evolution-devnet/test/TxBuilder.Vote.test.ts @@ -3,13 +3,7 @@ * Tests governance voting and proposals using the SDK. */ -import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest" -import * as Cluster from "@evolution-sdk/devnet/Cluster" -import * as Config from "@evolution-sdk/devnet/Config" -import * as Genesis from "@evolution-sdk/devnet/Genesis" -import type { Cardano } from "@evolution-sdk/evolution" -import { Client, preprod } from "@evolution-sdk/evolution" -import * as Address from "@evolution-sdk/evolution/Address" +import { beforeAll, describe, expect, it } from "@effect/vitest" import * as Anchor from "@evolution-sdk/evolution/Anchor" import * as Bytes32 from "@evolution-sdk/evolution/Bytes32" import * as Constitution from "@evolution-sdk/evolution/Constitution" @@ -22,74 +16,17 @@ import * as RewardAccount from "@evolution-sdk/evolution/RewardAccount" import * as UnitInterval from "@evolution-sdk/evolution/UnitInterval" import * as Url from "@evolution-sdk/evolution/Url" import * as VotingProcedures from "@evolution-sdk/evolution/VotingProcedures" +import { inject } from "vitest" -describe("TxBuilder Vote Operations (script-free)", () => { - let devnetCluster: Cluster.Cluster | undefined - let genesisConfig: Config.ShelleyGenesis - let conwayGenesis: Config.ConwayGenesis - const genesisUtxosByAccount: Map = new Map() - - const TEST_MNEMONIC = - "test test test test test test test test test test test test test test test test test test test test test test test sauce" +import { type SharedClusterResult, useSharedCluster } from "./utils/shared-cluster.js" - const createTestClient = (accountIndex: number = 0) => { - if (!devnetCluster) throw new Error("Cluster not initialized") - return Client.make(Cluster.getChain(devnetCluster)) - .withKupmios({ kupoUrl: "http://localhost:1453", ogmiosUrl: "http://localhost:1343" }) - .withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - } +describe("TxBuilder Vote Operations (script-free)", () => { + let shared: SharedClusterResult beforeAll(async () => { - // Create clients for multiple test accounts - const accounts = [0, 1, 2, 3].map((accountIndex) => - Client.make(preprod).withSeed({ mnemonic: TEST_MNEMONIC, accountIndex, addressType: "Base" }) - ) - - const addresses = await Promise.all(accounts.map((client) => client.address())) - const addressHexes = addresses.map((addr) => Address.toHex(addr)) - - genesisConfig = { - ...Config.DEFAULT_SHELLEY_GENESIS, - slotLength: 0.02, - epochLength: 50, - activeSlotsCoeff: 1.0, - initialFunds: { - [addressHexes[0]]: 500_000_000_000, - [addressHexes[1]]: 500_000_000_000, - [addressHexes[2]]: 500_000_000_000, - [addressHexes[3]]: 500_000_000_000 - } - } - - conwayGenesis = Config.DEFAULT_CONWAY_GENESIS - - const genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig) - - for (let i = 0; i < addresses.length; i++) { - const utxo = genesisUtxos.find((u) => Address.toBech32(u.address) === Address.toBech32(addresses[i])) - if (utxo) genesisUtxosByAccount.set(i, utxo) - } - - devnetCluster = await Cluster.make({ - clusterName: "vote-test", - ports: { node: 6010, submit: 9010 }, - shelleyGenesis: genesisConfig, - conwayGenesis, - kupo: { enabled: true, port: 1453, logLevel: "Info" }, - ogmios: { enabled: true, port: 1343, logLevel: "info" } - }) - - await Cluster.start(devnetCluster) - await new Promise((resolve) => setTimeout(resolve, 3_000)) + shared = await useSharedCluster(inject("sharedCluster" as any), [27, 28, 29, 30]) }, 180_000) - afterAll(async () => { - if (devnetCluster) { - await Cluster.stop(devnetCluster) - await Cluster.remove(devnetCluster) - } - }, 60_000) - // Helper to create anchor for governance actions const createAnchor = ( path: string, @@ -101,15 +38,11 @@ describe("TxBuilder Vote Operations (script-free)", () => { }) it("registers key DRep, creates proposal, and tests vote operation structure", { timeout: 180_000 }, async () => { - const PROPOSER_ACCOUNT = 0 + const PROPOSER_ACCOUNT = 27 - const proposerUtxo = genesisUtxosByAccount.get(PROPOSER_ACCOUNT) + const proposerUtxo = shared.getGenesisUtxo(PROPOSER_ACCOUNT) - if (!proposerUtxo) { - throw new Error("Genesis UTxOs not found") - } - - const proposerClient = createTestClient(PROPOSER_ACCOUNT) + const proposerClient = shared.makeClient(PROPOSER_ACCOUNT) const proposerAddress = await proposerClient.address() const proposerCredential = proposerAddress.paymentCredential @@ -213,7 +146,7 @@ describe("TxBuilder Vote Operations (script-free)", () => { }) it("creates InfoAction proposal (type 6)", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(27) const address = await client.address() if (!address.stakingCredential) { @@ -245,13 +178,11 @@ describe("TxBuilder Vote Operations (script-free)", () => { govActionIndex: 0n }) - // Use account 0 as voter for this simple test - const VOTER_ACCOUNT = 0 - const voterClient = createTestClient(VOTER_ACCOUNT) + // Use account 27 as voter for this simple test + const VOTER_ACCOUNT = 27 + const voterClient = shared.makeClient(VOTER_ACCOUNT) const voterAddress = await voterClient.address() - const voterUtxo = genesisUtxosByAccount.get(VOTER_ACCOUNT) - - if (!voterUtxo) throw new Error("Voter genesis UTxO not found for InfoAction vote") + const voterUtxo = shared.getGenesisUtxo(VOTER_ACCOUNT) try { const drepRegHash = await voterClient @@ -290,7 +221,7 @@ describe("TxBuilder Vote Operations (script-free)", () => { }) it("creates NoConfidenceAction proposal (type 3)", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(27) const address = await client.address() if (!address.stakingCredential) { @@ -324,13 +255,11 @@ describe("TxBuilder Vote Operations (script-free)", () => { govActionIndex: 0n }) - // Use account 1 as voter for NoConfidenceAction - const VOTER_ACCOUNT = 1 - const voterClient = createTestClient(VOTER_ACCOUNT) + // Use account 28 as voter for NoConfidenceAction + const VOTER_ACCOUNT = 28 + const voterClient = shared.makeClient(VOTER_ACCOUNT) const voterAddress = await voterClient.address() - const voterUtxo = genesisUtxosByAccount.get(VOTER_ACCOUNT) - - if (!voterUtxo) throw new Error("Voter genesis UTxO not found for NoConfidenceAction vote") + const voterUtxo = shared.getGenesisUtxo(VOTER_ACCOUNT) try { const drepRegHash = await voterClient @@ -372,7 +301,7 @@ describe("TxBuilder Vote Operations (script-free)", () => { }) it("creates HardForkInitiationAction proposal (type 1)", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(27) const address = await client.address() if (!address.stakingCredential) { @@ -407,13 +336,11 @@ describe("TxBuilder Vote Operations (script-free)", () => { govActionIndex: 0n }) - // Use account 2 as voter for HardForkInitiationAction - const VOTER_ACCOUNT = 2 - const voterClient = createTestClient(VOTER_ACCOUNT) + // Use account 29 as voter for HardForkInitiationAction + const VOTER_ACCOUNT = 29 + const voterClient = shared.makeClient(VOTER_ACCOUNT) const voterAddress = await voterClient.address() - const voterUtxo = genesisUtxosByAccount.get(VOTER_ACCOUNT) - - if (!voterUtxo) throw new Error("Voter genesis UTxO not found for HardForkInitiationAction vote") + const voterUtxo = shared.getGenesisUtxo(VOTER_ACCOUNT) try { const drepRegHash = await voterClient @@ -455,7 +382,7 @@ describe("TxBuilder Vote Operations (script-free)", () => { }) it("creates TreasuryWithdrawalsAction proposal (type 2)", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(27) const address = await client.address() if (!address.stakingCredential) { @@ -493,13 +420,11 @@ describe("TxBuilder Vote Operations (script-free)", () => { govActionIndex: 0n }) - // Use account 3 as voter for TreasuryWithdrawalsAction - const VOTER_ACCOUNT = 3 - const voterClient = createTestClient(VOTER_ACCOUNT) + // Use account 30 as voter for TreasuryWithdrawalsAction + const VOTER_ACCOUNT = 30 + const voterClient = shared.makeClient(VOTER_ACCOUNT) const voterAddress = await voterClient.address() - const voterUtxo = genesisUtxosByAccount.get(VOTER_ACCOUNT) - - if (!voterUtxo) throw new Error("Voter genesis UTxO not found for TreasuryWithdrawalsAction vote") + const voterUtxo = shared.getGenesisUtxo(VOTER_ACCOUNT) try { const drepRegHash = await voterClient @@ -541,7 +466,7 @@ describe("TxBuilder Vote Operations (script-free)", () => { }) it("creates UpdateCommitteeAction proposal (type 4)", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(27) const address = await client.address() if (!address.stakingCredential) { @@ -578,13 +503,11 @@ describe("TxBuilder Vote Operations (script-free)", () => { govActionIndex: 0n }) - // Use account 0 as voter for UpdateCommitteeAction - const VOTER_ACCOUNT = 0 - const voterClient = createTestClient(VOTER_ACCOUNT) + // Use account 27 as voter for UpdateCommitteeAction + const VOTER_ACCOUNT = 27 + const voterClient = shared.makeClient(VOTER_ACCOUNT) const voterAddress = await voterClient.address() - const voterUtxo = genesisUtxosByAccount.get(VOTER_ACCOUNT) - - if (!voterUtxo) throw new Error("Voter genesis UTxO not found for UpdateCommitteeAction vote") + const voterUtxo = shared.getGenesisUtxo(VOTER_ACCOUNT) try { const drepRegHash = await voterClient @@ -626,7 +549,7 @@ describe("TxBuilder Vote Operations (script-free)", () => { }) it("creates NewConstitutionAction proposal (type 5)", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(27) const address = await client.address() if (!address.stakingCredential) { @@ -670,13 +593,11 @@ describe("TxBuilder Vote Operations (script-free)", () => { govActionIndex: 0n }) - // Use account 1 as voter for NewConstitutionAction - const VOTER_ACCOUNT = 1 - const voterClient = createTestClient(VOTER_ACCOUNT) + // Use account 28 as voter for NewConstitutionAction + const VOTER_ACCOUNT = 28 + const voterClient = shared.makeClient(VOTER_ACCOUNT) const voterAddress = await voterClient.address() - const voterUtxo = genesisUtxosByAccount.get(VOTER_ACCOUNT) - - if (!voterUtxo) throw new Error("Voter genesis UTxO not found for NewConstitutionAction vote") + const voterUtxo = shared.getGenesisUtxo(VOTER_ACCOUNT) try { const drepRegHash = await voterClient @@ -718,7 +639,7 @@ describe("TxBuilder Vote Operations (script-free)", () => { }) it("creates ParameterChangeAction proposal (type 0)", { timeout: 60_000 }, async () => { - const client = createTestClient(0) + const client = shared.makeClient(27) const address = await client.address() if (!address.stakingCredential) { @@ -758,13 +679,11 @@ describe("TxBuilder Vote Operations (script-free)", () => { govActionIndex: 0n }) - // Ensure a key-based DRep exists for account 1 (voter) - const VOTER_ACCOUNT = 1 - const voterClient = createTestClient(VOTER_ACCOUNT) + // Ensure a key-based DRep exists for account 28 (voter) + const VOTER_ACCOUNT = 28 + const voterClient = shared.makeClient(VOTER_ACCOUNT) const voterAddress = await voterClient.address() - const voterUtxo = genesisUtxosByAccount.get(VOTER_ACCOUNT) - - if (!voterUtxo) throw new Error("Voter genesis UTxO not found") + const voterUtxo = shared.getGenesisUtxo(VOTER_ACCOUNT) try { const drepRegHash = await voterClient