Skip to content

Conversation

@solidsnakedev
Copy link
Collaborator

No description provided.

…mios v6

Ogmios v6 with explicit JSON notation expects raw Plutus script bytes
without CBOR tag envelope. Previously we were sending the full CBOR
encoding [tag, bytes] which caused 'couldn't decode plutus script' errors.

Now we send:
- Plutus scripts: raw bytes only (script.bytes)
- Native scripts: inner script structure CBOR

This fixes script evaluation when using reference scripts with Kupmios provider.
Copilot AI review requested due to automatic review settings January 12, 2026 16:40
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements comprehensive improvements to provider implementations for fetching and resolving UTxOs with complete script and datum information. The changes focus on three main areas: enhancing Blockfrost provider with pagination and full UTxO resolution, fixing Kupmios/Ogmios script encoding, and adding new transaction builder options for better control over evaluation and script data hashing.

Changes:

  • Enhanced Blockfrost provider with pagination support, full UTxO resolution (scripts/datums), and improved error handling
  • Fixed Plutus script CBOR encoding in Kupmios and Ogmios providers (removed double encoding, proper handling of native vs Plutus scripts)
  • Added PoolKeyHash bech32 encoding/decoding support and new transaction builder options for controlling evaluator behavior

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/evolution/src/sdk/provider/internal/Ogmios.ts Fixed Plutus script encoding to use raw bytes without CBOR envelope, added NativeScripts import for proper native script handling
packages/evolution/src/sdk/provider/internal/KupmiosEffects.ts Removed unnecessary double CBOR encoding for Plutus scripts from Kupo
packages/evolution/src/sdk/provider/internal/HttpUtils.ts Refactored header handling for better code organization
packages/evolution/src/sdk/provider/internal/BlockfrostEffect.ts Added pagination, full UTxO resolution with script/datum fetching, improved error handling with 404 support, and switched to more reliable evaluation endpoint
packages/evolution/src/sdk/provider/internal/Blockfrost.ts Updated schemas to match actual Blockfrost API responses, added Conway governance parameters, improved evaluation result handling
packages/evolution/src/sdk/builders/phases/Evaluation.ts Updated comments to clarify additionalUtxos handling strategy
packages/evolution/src/sdk/builders/TxBuilderImpl.ts Enhanced cost model detection to include reference scripts, added scriptDataFormat option support
packages/evolution/src/sdk/builders/TransactionBuilder.ts Added passAdditionalUtxos and scriptDataFormat options to BuildOptions
packages/evolution/src/PoolKeyHash.ts Implemented bech32 encoding/decoding support for pool IDs
.changeset/sour-bats-grin.md Documented all changes for the release

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

})
)
},
{ concurrency: "unbounded" }
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concurrency setting "unbounded" for Effect.forEach could potentially overwhelm the Blockfrost API with concurrent requests, especially when processing transaction outputs that may have many matching UTxOs. Consider using a bounded concurrency value (e.g., { concurrency: 10 }) to match the concurrency used in getUtxos and getUtxosWithUnit functions, which helps prevent rate limiting issues and provides more predictable resource usage.

Suggested change
{ concurrency: "unbounded" }
{ concurrency: 10 }

Copilot uses AI. Check for mistakes.
? fetchDatum(utxo.data_hash).pipe(
Effect.map((d) => d as DatumOption.DatumOption | undefined),
Effect.catchAll(() => Effect.succeed(
new DatumOption.DatumHash({ hash: Bytes.fromHex(utxo.data_hash!) }) as DatumOption.DatumOption | undefined
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple uses of the non-null assertion operator (!) on utxo.data_hash at lines 259, 354, and 447. While these are guarded by conditional checks, using the non-null assertion operator can be risky and may mask potential issues. Consider refactoring to use the already-checked variable or explicit type narrowing to avoid the need for the non-null assertion.

Copilot uses AI. Check for mistakes.
Comment on lines 344 to 354
const datumEffect = utxo.inline_datum
? Effect.succeed(
new DatumOption.InlineDatum({
data: PlutusData.fromCBORHex(utxo.inline_datum)
}) as DatumOption.DatumOption | undefined
)
: utxo.data_hash
? fetchDatum(utxo.data_hash).pipe(
Effect.map((d) => d as DatumOption.DatumOption | undefined),
Effect.catchAll(() => Effect.succeed(
new DatumOption.DatumHash({ hash: Bytes.fromHex(utxo.data_hash!) }) as DatumOption.DatumOption | undefined
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple uses of the non-null assertion operator (!) on utxo.data_hash. While guarded by conditional checks, using the non-null assertion operator can be risky and may mask potential issues. Consider refactoring to use the already-checked variable or explicit type narrowing to avoid the need for the non-null assertion.

Suggested change
const datumEffect = utxo.inline_datum
? Effect.succeed(
new DatumOption.InlineDatum({
data: PlutusData.fromCBORHex(utxo.inline_datum)
}) as DatumOption.DatumOption | undefined
)
: utxo.data_hash
? fetchDatum(utxo.data_hash).pipe(
Effect.map((d) => d as DatumOption.DatumOption | undefined),
Effect.catchAll(() => Effect.succeed(
new DatumOption.DatumHash({ hash: Bytes.fromHex(utxo.data_hash!) }) as DatumOption.DatumOption | undefined
const dataHash = utxo.data_hash
const datumEffect = utxo.inline_datum
? Effect.succeed(
new DatumOption.InlineDatum({
data: PlutusData.fromCBORHex(utxo.inline_datum)
}) as DatumOption.DatumOption | undefined
)
: dataHash
? fetchDatum(dataHash).pipe(
Effect.map((d) => d as DatumOption.DatumOption | undefined),
Effect.catchAll(() => Effect.succeed(
new DatumOption.DatumHash({ hash: Bytes.fromHex(dataHash) }) as DatumOption.DatumOption | undefined

Copilot uses AI. Check for mistakes.
Comment on lines 444 to 449
? fetchDatum(utxo.data_hash).pipe(
Effect.map((d) => d as DatumOption.DatumOption | undefined),
Effect.catchAll(() => Effect.succeed(
new DatumOption.DatumHash({ hash: Bytes.fromHex(utxo.data_hash!) }) as DatumOption.DatumOption | undefined
))
)
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple uses of the non-null assertion operator (!) on utxo.data_hash. While guarded by conditional checks, using the non-null assertion operator can be risky and may mask potential issues. Consider refactoring to use the already-checked variable or explicit type narrowing to avoid the need for the non-null assertion.

Suggested change
? fetchDatum(utxo.data_hash).pipe(
Effect.map((d) => d as DatumOption.DatumOption | undefined),
Effect.catchAll(() => Effect.succeed(
new DatumOption.DatumHash({ hash: Bytes.fromHex(utxo.data_hash!) }) as DatumOption.DatumOption | undefined
))
)
? (() => {
const dataHash = utxo.data_hash
return fetchDatum(dataHash).pipe(
Effect.map((d) => d as DatumOption.DatumOption | undefined),
Effect.catchAll(() =>
Effect.succeed(
new DatumOption.DatumHash({ hash: Bytes.fromHex(dataHash) }) as DatumOption.DatumOption | undefined
)
)
)
})()

Copilot uses AI. Check for mistakes.
Eff.gen(function* () {
const result = yield* Eff.try({
try: () => {
const decoded = bech32.decode(fromA as any, false)
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type assertion as any is used here to bypass type checking for the bech32.decode function. This is a code smell that could hide potential type mismatches. Consider removing the type assertion and ensuring the input string type matches the expected signature of bech32.decode, or add a proper type guard if needed.

Suggested change
const decoded = bech32.decode(fromA as any, false)
const decoded = bech32.decode(fromA, false)

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +84
return new Uint8Array(bytes)
},
catch: () => new ParseResult.Type(ast, fromA, `Failed to decode Bech32 pool id: ${fromA}`)
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decoded bech32 bytes should be validated to ensure they match the expected length for a PoolKeyHash (28 bytes, as defined by Hash28.BYTES_LENGTH). Without this check, invalid pool IDs with incorrect byte lengths could pass through validation, potentially causing issues downstream when the PoolKeyHash is used.

Suggested change
return new Uint8Array(bytes)
},
catch: () => new ParseResult.Type(ast, fromA, `Failed to decode Bech32 pool id: ${fromA}`)
if (bytes.length !== Hash28.BYTES_LENGTH) {
throw new ParseResult.Type(
ast,
fromA,
`Invalid PoolKeyHash length: expected ${Hash28.BYTES_LENGTH} bytes, got ${bytes.length}`
)
}
return new Uint8Array(bytes)
},
catch: (error) =>
error instanceof ParseResult.Type
? error
: new ParseResult.Type(ast, fromA, `Failed to decode Bech32 pool id: ${fromA}`)

Copilot uses AI. Check for mistakes.
: output.data_hash
? fetchDatum(output.data_hash).pipe(
Effect.map((d) => d as DatumOption.DatumOption | undefined),
Effect.catchAll(() => Effect.succeed(undefined))
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When fetching a datum by hash fails, the code falls back to creating a DatumHash with the original hash. However, the fallback in the getUtxosByOutRef function at line 518 returns undefined instead of falling back to DatumHash like in other functions (e.g., getUtxos at line 259, getUtxosWithUnit at line 354, getUtxoByUnit at line 447). This inconsistency means that if datum resolution fails for outputs referenced in getUtxosByOutRef, the datum information is completely lost rather than preserved as a hash reference. Consider using the same fallback pattern across all UTxO resolution functions for consistency.

Suggested change
Effect.catchAll(() => Effect.succeed(undefined))
Effect.catchAll(() =>
Effect.succeed(
new DatumOption.DatumHash({ hash: output.data_hash! }) as DatumOption.DatumOption
)
)

Copilot uses AI. Check for mistakes.
@solidsnakedev solidsnakedev merged commit 9008db3 into main Jan 12, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants