diff --git a/.changeset/blockfrost-nullable-params.md b/.changeset/blockfrost-nullable-params.md new file mode 100644 index 00000000..1a535b79 --- /dev/null +++ b/.changeset/blockfrost-nullable-params.md @@ -0,0 +1,5 @@ +--- +"@evolution-sdk/evolution": patch +--- + +Blockfrost protocol parameters now accept `null` for the 15 fields the Blockfrost OpenAPI spec marks nullable (`max_val_size`, `cost_models`, `cost_models_raw`, `price_mem`, `price_step`, `max_tx_ex_mem`, `max_tx_ex_steps`, `max_block_ex_mem`, `max_block_ex_steps`, `collateral_percent`, `max_collateral_inputs`, `coins_per_utxo_size`, `min_fee_ref_script_cost_per_byte`, `drep_deposit`, `gov_action_deposit`). Previously one `null` failed the whole `getProtocolParameters` decode; the decoded `null` now reaches the transform, whose existing defaults apply. diff --git a/packages/evolution/src/sdk/provider/internal/Blockfrost.ts b/packages/evolution/src/sdk/provider/internal/Blockfrost.ts index aca84b5f..190c4c3e 100644 --- a/packages/evolution/src/sdk/provider/internal/Blockfrost.ts +++ b/packages/evolution/src/sdk/provider/internal/Blockfrost.ts @@ -27,28 +27,33 @@ export const BlockfrostProtocolParameters = Schema.Struct({ key_deposit: Schema.BigInt, min_utxo: Schema.optional(Schema.BigInt), max_tx_size: Schema.BigInt, - max_val_size: Schema.optional(Schema.BigInt), + // The Blockfrost OpenAPI spec marks the fields below nullable: a `null` must + // decode (to null) instead of failing the whole response; the transform's + // `?? 0n` / `|| 0` defaults then apply. + max_val_size: Schema.optionalWith(Schema.BigInt, { nullable: true }), utxo_cost_per_word: Schema.optional(Schema.BigInt), - cost_models: Schema.optional( - Schema.Record({ key: Schema.String, value: Schema.Record({ key: Schema.String, value: Schema.BigInt }) }) + cost_models: Schema.optionalWith( + Schema.Record({ key: Schema.String, value: Schema.Record({ key: Schema.String, value: Schema.BigInt }) }), + { nullable: true } ), - cost_models_raw: Schema.optional( - Schema.Record({ key: Schema.String, value: Schema.Array(Schema.BigInt) }) + cost_models_raw: Schema.optionalWith( + Schema.Record({ key: Schema.String, value: Schema.Array(Schema.BigInt) }), + { nullable: true } ), - price_mem: Schema.optional(Schema.NumberFromString), - price_step: Schema.optional(Schema.NumberFromString), - max_tx_ex_mem: Schema.optional(Schema.BigInt), - max_tx_ex_steps: Schema.optional(Schema.BigInt), - max_block_ex_mem: Schema.optional(Schema.BigInt), - max_block_ex_steps: Schema.optional(Schema.BigInt), + price_mem: Schema.optionalWith(Schema.NumberFromString, { nullable: true }), + price_step: Schema.optionalWith(Schema.NumberFromString, { nullable: true }), + max_tx_ex_mem: Schema.optionalWith(Schema.BigInt, { nullable: true }), + max_tx_ex_steps: Schema.optionalWith(Schema.BigInt, { nullable: true }), + max_block_ex_mem: Schema.optionalWith(Schema.BigInt, { nullable: true }), + max_block_ex_steps: Schema.optionalWith(Schema.BigInt, { nullable: true }), max_block_size: Schema.BigInt, - collateral_percent: Schema.optional(Schema.BigInt), - max_collateral_inputs: Schema.optional(Schema.BigInt), - coins_per_utxo_size: Schema.optional(Schema.BigInt), - min_fee_ref_script_cost_per_byte: Schema.optional(Schema.NumberFromString), + collateral_percent: Schema.optionalWith(Schema.BigInt, { nullable: true }), + max_collateral_inputs: Schema.optionalWith(Schema.BigInt, { nullable: true }), + coins_per_utxo_size: Schema.optionalWith(Schema.BigInt, { nullable: true }), + min_fee_ref_script_cost_per_byte: Schema.optionalWith(Schema.NumberFromString, { nullable: true }), // Conway era governance parameters - drep_deposit: Schema.optional(Schema.BigInt), - gov_action_deposit: Schema.optional(Schema.BigInt) + drep_deposit: Schema.optionalWith(Schema.BigInt, { nullable: true }), + gov_action_deposit: Schema.optionalWith(Schema.BigInt, { nullable: true }) }) export type BlockfrostProtocolParameters = Schema.Schema.Type diff --git a/packages/evolution/test/provider/Blockfrost.test.ts b/packages/evolution/test/provider/Blockfrost.test.ts index 0ea0ae96..93aece8d 100644 --- a/packages/evolution/test/provider/Blockfrost.test.ts +++ b/packages/evolution/test/provider/Blockfrost.test.ts @@ -171,6 +171,46 @@ describe("Blockfrost getProtocolParameters", () => { }) }) + it("accepts null for every nullable protocol parameter and falls back to the defaults", async () => { + // The OpenAPI spec marks these 15 fields nullable; one null must not fail the decode + const payload = JSON.parse(PROTOCOL_PARAMETERS) as Record + for (const field of [ + "max_val_size", + "cost_models", + "cost_models_raw", + "price_mem", + "price_step", + "max_tx_ex_mem", + "max_tx_ex_steps", + "max_block_ex_mem", + "max_block_ex_steps", + "collateral_percent", + "max_collateral_inputs", + "coins_per_utxo_size", + "min_fee_ref_script_cost_per_byte", + "drep_deposit", + "gov_action_deposit" + ]) { + payload[field] = null + } + stubRoutes({ "/epochs/latest/parameters": JSON.stringify(payload) }) + + const params = await Effect.runPromise(BlockfrostEffect.getProtocolParameters(BASE, PROJECT_ID)) + + expect(params.maxValSize).toBe(0) + expect(params.priceMem).toBe(0) + expect(params.priceStep).toBe(0) + expect(params.maxTxExMem).toBe(0n) + expect(params.maxTxExSteps).toBe(0n) + expect(params.coinsPerUtxoByte).toBe(0n) + expect(params.collateralPercentage).toBe(0) + expect(params.maxCollateralInputs).toBe(0) + expect(params.minFeeRefScriptCostPerByte).toBe(0) + expect(params.drepDeposit).toBe(0n) + expect(params.govActionDeposit).toBe(0n) + expect(params.costModels).toEqual({ PlutusV1: {}, PlutusV2: {}, PlutusV3: {} }) + }) + it("falls back to the named cost models as numbers", async () => { stubRoutes({ "/epochs/latest/parameters": PROTOCOL_PARAMETERS.replace(/"cost_models_raw": \{[^}]*\},/, "") })