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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions packages/evolution/src/sdk/builders/TransactionBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,12 +468,32 @@ export interface TxBuilderState {
*/
export interface BuildOptions {
/**
* Override protocol parameters for this specific transaction build.
* Override protocol parameters for fee calculation.
*
* @deprecated Use `fullProtocolParameters` instead — it covers all fee-calc fields
* (`minFeeA`/`minFeeB` → `minFeeCoefficient`/`minFeeConstant`, `coinsPerUtxoByte`,
* `maxTxSize`, `priceMem`, `priceStep`, `minFeeRefScriptCostPerByte`) and is derived
* automatically when `fullProtocolParameters` is present.
*
* @since 2.0.0
*/
readonly protocolParameters?: ProtocolParameters

/**
* Full protocol parameters override for all transaction build operations.
*
* When provided, ALL internal phases and operations will use these parameters
* instead of calling the provider's `getProtocolParameters` API. This prevents
* any network round-trips for protocol parameter fetching during the build.
*
* Includes all fields required for: script evaluation (cost models), stake/pool/DRep/
* governance action deposits, and script data hash computation. Fee-calc fields
* (`protocolParameters`) are also derived from this automatically.
*
* @since 2.0.0
*/
readonly fullProtocolParameters?: Provider.ProtocolParameters

/**
* Coin selection strategy for automatic input selection.
*
Expand Down Expand Up @@ -669,7 +689,7 @@ export class BuildOptionsTag extends Context.Tag("BuildOptions")<BuildOptionsTag
* @since 2.0.0
* @category model
*/
export type ProgramStep = Effect.Effect<void, TransactionBuilderError, TxContext | TxBuilderConfigTag>
export type ProgramStep = Effect.Effect<void, TransactionBuilderError, TxContext | TxBuilderConfigTag | BuildOptionsTag>

// ============================================================================
// Voter Key
Expand Down Expand Up @@ -1651,9 +1671,7 @@ export type TransactionBuilder = SigningTransactionBuilder | ReadOnlyTransaction
export function makeTxBuilder(
config: TxBuilderConfig & { wallet: Wallet.SigningWallet | Wallet.ApiWallet }
): SigningTransactionBuilder
export function makeTxBuilder(
config: TxBuilderConfig & { wallet: Wallet.ReadOnlyWallet }
): ReadOnlyTransactionBuilder
export function makeTxBuilder(config: TxBuilderConfig & { wallet: Wallet.ReadOnlyWallet }): ReadOnlyTransactionBuilder
export function makeTxBuilder(config: TxBuilderConfig & { wallet?: undefined }): ReadOnlyTransactionBuilder
export function makeTxBuilder(config: TxBuilderConfig): SigningTransactionBuilder | ReadOnlyTransactionBuilder {
return BuilderFactory.makeTxBuilder(config)
Expand Down
13 changes: 13 additions & 0 deletions packages/evolution/src/sdk/builders/internal/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ export const resolveProtocolParameters = (
return Effect.succeed(options.protocolParameters)
}

if (options?.fullProtocolParameters !== undefined) {
const p = options.fullProtocolParameters
return Effect.succeed({
minFeeCoefficient: BigInt(p.minFeeA),
minFeeConstant: BigInt(p.minFeeB),
coinsPerUtxoByte: p.coinsPerUtxoByte,
maxTxSize: p.maxTxSize,
priceMem: p.priceMem,
priceStep: p.priceStep,
minFeeRefScriptCostPerByte: p.minFeeRefScriptCostPerByte
})
}

const provider = config.provider
if (provider !== undefined) {
return Effect.map(
Expand Down
56 changes: 30 additions & 26 deletions packages/evolution/src/sdk/builders/internal/txBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ import * as CoreUTxO from "../../../UTxO.js"
import * as VKey from "../../../VKey.js"
import * as Withdrawals from "../../../Withdrawals.js"
import type { UnfrackOptions } from "../TransactionBuilder.js"
import { BuildOptionsTag, TransactionBuilderError, TxBuilderConfigTag, TxContext, voterToKey } from "../TransactionBuilder.js"
import {
BuildOptionsTag,
TransactionBuilderError,
TxBuilderConfigTag,
TxContext,
voterToKey
} from "../TransactionBuilder.js"
import * as Unfrack from "../Unfrack.js"

// ============================================================================
Expand Down Expand Up @@ -526,29 +532,28 @@ export const assembleTransaction = (
let scriptDataHash: ReturnType<typeof Redeemers.toScriptDataHash> | undefined
let redeemersConcrete: Redeemers.RedeemerMap | undefined
if (redeemers.length > 0) {
// Get config to access provider for full protocol parameters
const config = yield* TxBuilderConfigTag

if (!config.provider) {
return yield* Effect.fail(
new TransactionBuilderError({
message:
"Script transactions require a provider to fetch full protocol parameters for scriptDataHash calculation",
cause: { redeemerCount: redeemers.length }
})
)
}

// Fetch full protocol params from provider (includes cost models)
const fullProtocolParams = yield* config.provider.effect.getProtocolParameters().pipe(
Effect.mapError(
(providerError) =>
new TransactionBuilderError({
message: `Failed to fetch full protocol parameters for scriptDataHash calculation: ${providerError.message}`,
cause: providerError
})
)
)
const buildOptions = yield* BuildOptionsTag

const fullProtocolParams = yield* buildOptions.fullProtocolParameters
? Effect.succeed(buildOptions.fullProtocolParameters)
: !config.provider
? Effect.fail(
new TransactionBuilderError({
message:
"Script transactions require a provider to fetch full protocol parameters for scriptDataHash calculation",
cause: { redeemerCount: redeemers.length }
})
)
: config.provider.effect.getProtocolParameters().pipe(
Effect.mapError(
(providerError) =>
new TransactionBuilderError({
message: `Failed to fetch full protocol parameters for scriptDataHash calculation: ${providerError.message}`,
cause: providerError
})
)
)

// Only include cost models for Plutus versions actually used in the transaction
// The scriptDataHash must use the same languages as the node will compute
Expand Down Expand Up @@ -739,9 +744,8 @@ export const assembleTransaction = (
* @since 2.0.0
* @category fee-calculation
*/
export const calculateTransactionSize = (
transaction: Transaction.Transaction
): number => Transaction.toCBORBytes(transaction).length
export const calculateTransactionSize = (transaction: Transaction.Transaction): number =>
Transaction.toCBORBytes(transaction).length

/**
* Calculate minimum transaction fee based on protocol parameters.
Expand Down
78 changes: 37 additions & 41 deletions packages/evolution/src/sdk/builders/operations/Governance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Effect, Ref } from "effect"
import * as Bytes from "../../../Bytes.js"
import * as Certificate from "../../../Certificate.js"
import * as RedeemerBuilder from "../RedeemerBuilder.js"
import { TransactionBuilderError, TxBuilderConfigTag, TxContext } from "../TransactionBuilder.js"
import { BuildOptionsTag, TransactionBuilderError, TxBuilderConfigTag, TxContext } from "../TransactionBuilder.js"
import type {
AuthCommitteeHotParams,
DeregisterDRepParams,
Expand All @@ -33,16 +33,14 @@ import type {
*/
export const createRegisterDRepProgram = (
params: RegisterDRepParams
): Effect.Effect<void, TransactionBuilderError, TxContext | TxBuilderConfigTag> =>
): Effect.Effect<void, TransactionBuilderError, TxContext | TxBuilderConfigTag | BuildOptionsTag> =>
Effect.gen(function* () {
const ctx = yield* TxContext
const config = yield* TxBuilderConfigTag
const buildOptions = yield* BuildOptionsTag

// Check if script-controlled
const isScriptControlled = params.drepCredential._tag === "ScriptHash"

// Script-controlled DRep registration requires a redeemer (Publishing purpose).
// The script is invoked to authorize the registration.
if (isScriptControlled && !params.redeemer) {
return yield* Effect.fail(
new TransactionBuilderError({
Expand All @@ -51,23 +49,22 @@ export const createRegisterDRepProgram = (
)
}

// Get drepDeposit from protocol parameters via provider
if (!config.provider) {
return yield* Effect.fail(
new TransactionBuilderError({
message: "Provider required to fetch drepDeposit for DRep registration"
})
)
}

const protocolParams = yield* config.provider.effect.getProtocolParameters().pipe(
Effect.mapError(
(err) =>
new TransactionBuilderError({
message: `Failed to fetch protocol parameters: ${err.message}`
})
)
)
const protocolParams = yield* buildOptions.fullProtocolParameters
? Effect.succeed(buildOptions.fullProtocolParameters)
: !config.provider
? Effect.fail(
new TransactionBuilderError({
message: "Provider required to fetch drepDeposit for DRep registration"
})
)
: config.provider.effect.getProtocolParameters().pipe(
Effect.mapError(
(err) =>
new TransactionBuilderError({
message: `Failed to fetch protocol parameters: ${err.message}`
})
)
)
const drepDeposit = protocolParams.drepDeposit

// Create RegDrepCert certificate with deposit
Expand Down Expand Up @@ -196,21 +193,12 @@ export const createUpdateDRepProgram = (
*/
export const createDeregisterDRepProgram = (
params: DeregisterDRepParams
): Effect.Effect<void, TransactionBuilderError, TxContext | TxBuilderConfigTag> =>
): Effect.Effect<void, TransactionBuilderError, TxContext | TxBuilderConfigTag | BuildOptionsTag> =>
Effect.gen(function* () {
const ctx = yield* TxContext
const config = yield* TxBuilderConfigTag
const buildOptions = yield* BuildOptionsTag

// Get drepDeposit from protocol parameters via provider
if (!config.provider) {
return yield* Effect.fail(
new TransactionBuilderError({
message: "Provider required to fetch drepDeposit for DRep deregistration"
})
)
}

// Check if script-controlled
const isScriptControlled = params.drepCredential._tag === "ScriptHash"

if (isScriptControlled && !params.redeemer) {
Expand All @@ -221,14 +209,22 @@ export const createDeregisterDRepProgram = (
)
}

const protocolParams = yield* config.provider.effect.getProtocolParameters().pipe(
Effect.mapError(
(err) =>
new TransactionBuilderError({
message: `Failed to fetch protocol parameters: ${err.message}`
})
)
)
const protocolParams = yield* buildOptions.fullProtocolParameters
? Effect.succeed(buildOptions.fullProtocolParameters)
: !config.provider
? Effect.fail(
new TransactionBuilderError({
message: "Provider required to fetch drepDeposit for DRep deregistration"
})
)
: config.provider.effect.getProtocolParameters().pipe(
Effect.mapError(
(err) =>
new TransactionBuilderError({
message: `Failed to fetch protocol parameters: ${err.message}`
})
)
)
const drepDeposit = protocolParams.drepDeposit

// Create UnregDrepCert certificate with deposit refund
Expand Down
40 changes: 19 additions & 21 deletions packages/evolution/src/sdk/builders/operations/Pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Effect, Ref } from "effect"

import * as Certificate from "../../../Certificate.js"
import * as PoolKeyHash from "../../../PoolKeyHash.js"
import { TransactionBuilderError, TxBuilderConfigTag, TxContext } from "../TransactionBuilder.js"
import { BuildOptionsTag, TransactionBuilderError, TxBuilderConfigTag, TxContext } from "../TransactionBuilder.js"
import type { RegisterPoolParams, RetirePoolParams } from "./Operations.js"

// ============================================================================
Expand All @@ -26,30 +26,28 @@ import type { RegisterPoolParams, RetirePoolParams } from "./Operations.js"
*/
export const createRegisterPoolProgram = (
params: RegisterPoolParams
): Effect.Effect<void, TransactionBuilderError, TxContext | TxBuilderConfigTag> =>
): Effect.Effect<void, TransactionBuilderError, TxContext | TxBuilderConfigTag | BuildOptionsTag> =>
Effect.gen(function* () {
const ctx = yield* TxContext
const config = yield* TxBuilderConfigTag
const buildOptions = yield* BuildOptionsTag

// TODO: protocol param should be resolved earlier in builder phases, not here
// protocol param can come from the provider or the build options directly
// Get poolDeposit from protocol parameters via provider
if (!config.provider) {
return yield* Effect.fail(
new TransactionBuilderError({
message: "Provider required to fetch poolDeposit for pool registration"
})
)
}

const protocolParams = yield* config.provider.effect.getProtocolParameters().pipe(
Effect.mapError(
(err) =>
new TransactionBuilderError({
message: `Failed to fetch protocol parameters: ${err.message}`
})
)
)
const protocolParams = yield* buildOptions.fullProtocolParameters
? Effect.succeed(buildOptions.fullProtocolParameters)
: !config.provider
? Effect.fail(
new TransactionBuilderError({
message: "Provider required to fetch poolDeposit for pool registration"
})
)
: config.provider.effect.getProtocolParameters().pipe(
Effect.mapError(
(err) =>
new TransactionBuilderError({
message: `Failed to fetch protocol parameters: ${err.message}`
})
)
)
const poolDeposit = protocolParams.poolDeposit

// Create PoolRegistration certificate
Expand Down
38 changes: 19 additions & 19 deletions packages/evolution/src/sdk/builders/operations/Propose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Effect, Ref } from "effect"

import * as ProposalProcedure from "../../../ProposalProcedure.js"
import * as ProposalProcedures from "../../../ProposalProcedures.js"
import { TransactionBuilderError, TxBuilderConfigTag, TxContext } from "../TransactionBuilder.js"
import { BuildOptionsTag, TransactionBuilderError, TxBuilderConfigTag, TxContext } from "../TransactionBuilder.js"
import type { ProposeParams } from "./Operations.js"

/**
Expand All @@ -29,28 +29,28 @@ import type { ProposeParams } from "./Operations.js"
*/
export const createProposeProgram = (
params: ProposeParams
): Effect.Effect<void, TransactionBuilderError, TxContext | TxBuilderConfigTag> =>
): Effect.Effect<void, TransactionBuilderError, TxContext | TxBuilderConfigTag | BuildOptionsTag> =>
Effect.gen(function* () {
const ctx = yield* TxContext
const config = yield* TxBuilderConfigTag
const buildOptions = yield* BuildOptionsTag

// 1. Get govActionDeposit from protocol parameters via provider
if (!config.provider) {
return yield* Effect.fail(
new TransactionBuilderError({
message: "Provider required to fetch govActionDeposit for governance proposal"
})
)
}

const protocolParams = yield* config.provider.effect.getProtocolParameters().pipe(
Effect.mapError(
(err) =>
new TransactionBuilderError({
message: `Failed to fetch protocol parameters: ${err.message}`
})
)
)
const protocolParams = yield* buildOptions.fullProtocolParameters
? Effect.succeed(buildOptions.fullProtocolParameters)
: !config.provider
? Effect.fail(
new TransactionBuilderError({
message: "Provider required to fetch govActionDeposit for governance proposal"
})
)
: config.provider.effect.getProtocolParameters().pipe(
Effect.mapError(
(err) =>
new TransactionBuilderError({
message: `Failed to fetch protocol parameters: ${err.message}`
})
)
)
const govActionDeposit = protocolParams.govActionDeposit

// 2. Construct ProposalProcedure with fetched deposit
Expand Down
Loading
Loading