Skip to content

Commit c7ab056

Browse files
Merge pull request #210 from IntersectMBO/fix/provider-error-handling-and-blockfrost-value-format
fix(provider): standardize error handling, fix Blockfrost multi-asset format, accept tag-258/plain CBOR
2 parents 3cb1a0c + 0786d35 commit c7ab056

10 files changed

Lines changed: 821 additions & 279 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@evolution-sdk/evolution": patch
3+
---
4+
5+
Fix Blockfrost evaluateTx failing on multi-asset UTxOs by correcting the value format sent to the Ogmios endpoint. Standardize error handling across all providers with consistent catchAll + wrapError pattern. Add JSONWSP fault detection to Blockfrost evaluation responses. Accept both CBOR tag-258 and plain array encodings in TransactionBody decoding.

packages/evolution/src/TransactionBody.ts

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,8 @@ const decodeAuxiliaryDataHash = ParseResult.decodeEither(AuxiliaryDataHash.FromB
191191
const decodeScriptDataHash = ParseResult.decodeEither(ScriptDataHash.FromBytes)
192192
const decodeKeyHash = ParseResult.decodeEither(KeyHash.FromBytes)
193193

194-
const decodeInputs = ParseResult.decodeUnknownEither(CBOR.tag(258, Schema.Array(TransactionInput.FromCDDL)))
194+
const decodeTaggedInputs = ParseResult.decodeUnknownEither(CBOR.tag(258, Schema.Array(TransactionInput.FromCDDL)))
195+
const decodeUntaggedInputs = ParseResult.decodeUnknownEither(Schema.Array(TransactionInput.FromCDDL))
195196

196197
/**
197198
* CDDL schema for TransactionBody struct structure.
@@ -314,10 +315,12 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra
314315
}),
315316
decode: (fromA) =>
316317
E.gen(function* () {
317-
// Required fields - access via helper
318-
const inputsTag = fromA.get(0n)
319-
const decodedInputs = yield* decodeInputs(inputsTag)
320-
const inputs = decodedInputs.value
318+
// Required fields - accept both tag-258 (Conway) and plain array (Babbage)
319+
const inputsRaw = fromA.get(0n)
320+
const taggedResult = decodeTaggedInputs(inputsRaw)
321+
const inputs = E.isRight(taggedResult)
322+
? taggedResult.right.value
323+
: yield* decodeUntaggedInputs(inputsRaw)
321324

322325
// const inputsArray = inputsTag.value
323326
// const inputsLen = inputsArray.length
@@ -370,14 +373,16 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra
370373
const scriptDataHashBytes = fromA.get(11n) as Uint8Array | undefined
371374
const scriptDataHash = scriptDataHashBytes ? yield* decodeScriptDataHash(scriptDataHashBytes) : undefined
372375

373-
const collateralInputsTag = fromA.get(13n) as
374-
| {
375-
_tag: "Tag"
376-
tag: 258
377-
value: ReadonlyArray<typeof TransactionInput.CDDLSchema.Type>
378-
}
376+
// Accept both tag-258 (Conway) and plain array (Babbage) for collateral inputs
377+
const collateralInputsRaw = fromA.get(13n) as
378+
| { _tag: "Tag"; tag: 258; value: ReadonlyArray<typeof TransactionInput.CDDLSchema.Type> }
379+
| ReadonlyArray<typeof TransactionInput.CDDLSchema.Type>
379380
| undefined
380-
const collateralInputsArray = collateralInputsTag ? collateralInputsTag.value : undefined
381+
const collateralInputsArray = collateralInputsRaw
382+
? (collateralInputsRaw as any)._tag === "Tag"
383+
? (collateralInputsRaw as any).value
384+
: collateralInputsRaw
385+
: undefined
381386
let collateralInputs: NonEmptyArray<TransactionInput.TransactionInput> | undefined
382387
if (collateralInputsArray) {
383388
const len = collateralInputsArray.length
@@ -388,14 +393,16 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra
388393
collateralInputs = arr as NonEmptyArray<TransactionInput.TransactionInput>
389394
}
390395

391-
const requiredSignersTag = fromA.get(14n) as
392-
| {
393-
_tag: "Tag"
394-
tag: 258
395-
value: ReadonlyArray<Uint8Array>
396-
}
396+
// Accept both tag-258 (Conway) and plain array (Babbage) for required signers
397+
const requiredSignersRaw = fromA.get(14n) as
398+
| { _tag: "Tag"; tag: 258; value: ReadonlyArray<Uint8Array> }
399+
| ReadonlyArray<Uint8Array>
397400
| undefined
398-
const requiredSignersArray = requiredSignersTag ? requiredSignersTag.value : undefined
401+
const requiredSignersArray = requiredSignersRaw
402+
? (requiredSignersRaw as any)._tag === "Tag"
403+
? (requiredSignersRaw as any).value
404+
: requiredSignersRaw
405+
: undefined
399406
let requiredSigners: NonEmptyArray<KeyHash.KeyHash> | undefined
400407
if (requiredSignersArray) {
401408
const len = requiredSignersArray.length
@@ -411,14 +418,16 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra
411418
const collateralReturn = collateralReturnData ? yield* decodeTxOutput(collateralReturnData) : undefined
412419
const totalCollateral = fromA.get(17n) as Coin.Coin | undefined
413420

414-
const referenceInputsTag = fromA.get(18n) as
415-
| {
416-
_tag: "Tag"
417-
tag: 258
418-
value: ReadonlyArray<typeof TransactionInput.CDDLSchema.Type>
419-
}
421+
// Accept both tag-258 (Conway) and plain array (Babbage) for reference inputs
422+
const referenceInputsRaw = fromA.get(18n) as
423+
| { _tag: "Tag"; tag: 258; value: ReadonlyArray<typeof TransactionInput.CDDLSchema.Type> }
424+
| ReadonlyArray<typeof TransactionInput.CDDLSchema.Type>
420425
| undefined
421-
const referenceInputsArray = referenceInputsTag ? referenceInputsTag.value : undefined
426+
const referenceInputsArray: ReadonlyArray<typeof TransactionInput.CDDLSchema.Type> | undefined = referenceInputsRaw
427+
? (referenceInputsRaw as any)._tag === "Tag"
428+
? (referenceInputsRaw as any).value
429+
: referenceInputsRaw
430+
: undefined
422431
let referenceInputs: NonEmptyArray<TransactionInput.TransactionInput> | undefined
423432
if (referenceInputsArray) {
424433
const len = referenceInputsArray.length
@@ -430,15 +439,15 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra
430439
}
431440
const votingProceduresData = fromA.get(19n) as typeof VotingProcedures.CDDLSchema.Type | undefined
432441
const votingProcedures = votingProceduresData ? yield* decodeVotingProcedures(votingProceduresData) : undefined
433-
const proposalProceduresTag = fromA.get(20n) as
434-
| {
435-
_tag: "Tag"
436-
tag: 258
437-
value: ReadonlyArray<typeof ProposalProcedure.CDDLSchema.Type>
438-
}
442+
// Accept both tag-258 (Conway) and plain array (Babbage) for proposal procedures
443+
const proposalProceduresRaw = fromA.get(20n) as
444+
| { _tag: "Tag"; tag: 258; value: ReadonlyArray<typeof ProposalProcedure.CDDLSchema.Type> }
445+
| ReadonlyArray<typeof ProposalProcedure.CDDLSchema.Type>
439446
| undefined
440-
const proposalProceduresArray = proposalProceduresTag
441-
? (proposalProceduresTag.value as ReadonlyArray<typeof ProposalProcedure.CDDLSchema.Type>)
447+
const proposalProceduresArray: ReadonlyArray<typeof ProposalProcedure.CDDLSchema.Type> | undefined = proposalProceduresRaw
448+
? (proposalProceduresRaw as any)._tag === "Tag"
449+
? (proposalProceduresRaw as any).value
450+
: proposalProceduresRaw
442451
: undefined
443452
const proposalProcedures = proposalProceduresArray
444453
? new ProposalProcedures.ProposalProcedures({

packages/evolution/src/sdk/provider/internal/Blockfrost.ts

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
* Internal module for Blockfrost provider implementation
44
*/
55

6-
import { Schema } from "effect"
6+
import { Effect, Schema } from "effect"
77

88
import * as CoreAssets from "../../../Assets/index.js"
99
import * as PoolKeyHash from "../../../PoolKeyHash.js"
1010
import * as Redeemer from "../../../Redeemer.js"
1111
import type { EvalRedeemer } from "../../EvalRedeemer.js"
1212
import type * as Provider from "../Provider.js"
13+
import { ProviderError } from "../Provider.js"
1314

1415
// ============================================================================
1516
// Blockfrost API Response Schemas
@@ -158,7 +159,7 @@ export const JsonwspOgmiosEvaluationResponse = Schema.Struct({
158159
version: Schema.optional(Schema.String),
159160
servicename: Schema.optional(Schema.String),
160161
methodname: Schema.optional(Schema.String),
161-
result: Schema.Struct({
162+
result: Schema.optional(Schema.Struct({
162163
EvaluationResult: Schema.optional(
163164
Schema.Record({
164165
key: Schema.String, // "spend:0", "mint:1", etc.
@@ -169,7 +170,11 @@ export const JsonwspOgmiosEvaluationResponse = Schema.Struct({
169170
})
170171
),
171172
EvaluationFailure: Schema.optional(Schema.Unknown)
172-
}),
173+
})),
174+
fault: Schema.optional(Schema.Struct({
175+
code: Schema.optional(Schema.String),
176+
string: Schema.optional(Schema.String)
177+
})),
173178
reflection: Schema.optional(Schema.Unknown)
174179
})
175180

@@ -258,17 +263,48 @@ export const transformDelegation = (blockfrostDelegation: BlockfrostDelegation):
258263
*/
259264
export const transformJsonwspOgmiosEvaluationResult = (
260265
jsonwspResponse: JsonwspOgmiosEvaluationResponse
261-
): Array<EvalRedeemer> => {
266+
): Effect.Effect<Array<EvalRedeemer>, ProviderError> => {
267+
// Handle JSONWSP fault response (Ogmios backend error)
268+
if (jsonwspResponse.type === "jsonwsp/fault") {
269+
const faultMessage = jsonwspResponse.fault?.string ?? "unknown fault"
270+
return Effect.fail(
271+
new ProviderError({
272+
message: `Blockfrost evaluation fault: ${faultMessage}`,
273+
cause: jsonwspResponse
274+
})
275+
)
276+
}
277+
278+
// Handle missing result field
279+
if (!jsonwspResponse.result) {
280+
return Effect.fail(
281+
new ProviderError({
282+
message: `Blockfrost evaluation returned no result`,
283+
cause: jsonwspResponse
284+
})
285+
)
286+
}
287+
262288
// Check for evaluation failure
263289
if (jsonwspResponse.result.EvaluationFailure) {
264290
const failure = jsonwspResponse.result.EvaluationFailure
265-
throw new Error(`Script evaluation failed: ${JSON.stringify(failure)}`)
291+
return Effect.fail(
292+
new ProviderError({
293+
message: `Blockfrost script evaluation failed`,
294+
cause: failure
295+
})
296+
)
266297
}
267298

268299
// Handle success case
269300
const evaluationResult = jsonwspResponse.result.EvaluationResult
270301
if (!evaluationResult) {
271-
throw new Error("No evaluation result returned from Blockfrost")
302+
return Effect.fail(
303+
new ProviderError({
304+
message: `Blockfrost evaluation returned no result`,
305+
cause: "No EvaluationResult in response"
306+
})
307+
)
272308
}
273309

274310
const result: Array<EvalRedeemer> = []
@@ -291,5 +327,5 @@ export const transformJsonwspOgmiosEvaluationResult = (
291327
})
292328
}
293329

294-
return result
330+
return Effect.succeed(result)
295331
}

0 commit comments

Comments
 (0)